文章目录
1.分布式架构简介
1.1.分布式架构
SOA:Service Oriented Architecture 面向服务的架构,其中包含多个功能,服务之间通过相互依赖最终提供一些列的功能,一个功能,一个服务,各个服务之间通过网络调用。
微服务:将⼀个⼤的单体应⽤进⾏细粒度的服务化拆分,每个拆分出来的服务各⾃独⽴打包部署,各个服务之间 通过⽹络调⽤。
优点:
- 易开发、理解和维护
- 独立的部署和启动
缺点:
- 分布式系统->分布式事务
- 需要管理多个服务->服务治理
1.2.常见的微服务架构解决方案
- ServiceComb
- 华为内部的CSE框架,一个微服务的开源解决方案,文档不多,通信领域比较强。
- dubbo
- zookeeper+dubbo+springmvc/springboot
- 官⽅地址:http://dubbo.apache.org/#!/?lang=zh-cn
- 通信方式:rpc
- 注册中心:zookeeper/redis/nacos
- 配置中心:diamond/nacos
- SpringCloud
- 全家桶+轻松嵌⼊第三⽅组件(Netflix 奈⻜)
- 官⽹:https://spring.io/projects/spring-cloud
- 通信⽅式:http restful
- 注册中⼼:eruka
- 配置中⼼:config
- 断路器:hystrix
- ⽹关:zuul/gateway
- 分布式追踪系统:sleuth+zipkin
- Spring Cloud Alibaba
- 全家桶+阿⾥⽣态多个组件组合+SpringCloud⽀持
- 官⽹ https://spring.io/projects/spring-cloud-alibaba
- 通信⽅式:http restful
- 注册中⼼:nacos
- 配置中⼼:nacos
- 断路器:sentinel
- ⽹关:gateway
- 分布式追踪系统:sleuth+zipkin
1.3.分布式系统核心组件图
2.AlibabaCloud架构环境准备
2.1.创建maven聚合项目
(1)版本说明
- Spring5以上
- SpringBoot2.x以上
- AlibabaCloud 版本 2.2.x https://spring.io/projects/spring-cloud-alibaba#learn
- SpirngCloud版本 Hoxton https://spring.io/projects/spring-cloud
(2)创建父工程项目ali-cloud,删除src目录
<!-- 一般来说父级项目的packaging都为pom,packaging默认类型jar类型-->
<packaging>pom</packaging>
<properties>
<!--JDK版本,如果是jdk8则这里是 1.8-->
<java.version>1.8</java.version>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<spring.boot.version>2.3.3.RELEASE</spring.boot.version>
<spring.cloud.version>Hoxton.SR8</spring.cloud.version>
<alibaba.cloud.version>2.2.1.RELEASE</alibaba.cloud.version>
<mybatisplus.boot.starter.version>3.4.0</mybatisplus.boot.starter.version>
<lombok.version>1.18.16</lombok.version>
<commons.lang3.version>3.9</commons.lang3.version>
<commons.codec.version>1.15</commons.codec.version>
<springfox.boot.starter.version>3.0.0</springfox.boot.starter.version>
<docker.image.prefix>xdclass-cloud</docker.image.prefix>
<!--跳过单元测试-->
<skipTests>true</skipTests>
</properties>
<!--锁定版本-->
<dependencyManagement>
<dependencies>
<!--https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-dependencies/2.3.3.RELEASE-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring.boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-dependencies/Hoxton.SR8-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring.cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--https://mvnrepository.com/artifact/com.alibaba.cloud/spring-cloud-alibaba-dependencies/2.2.1.RELEASE-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${alibaba.cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--mybatis plus和springboot整合-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatisplus.boot.starter.version}</version>
</dependency>
<!--https://mvnrepository.com/artifact/org.projectlombok/lombok/1.18.16-->
<!--scope=provided,说明它只在编译阶段生效,不需要打入包中, Lombok在编译期将带Lombok注解的Java文件正确编译为完整的Class文件-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<!--<scope>provided</scope>-->
</dependency>
<!--接口文档依赖-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>${springfox.boot.starter.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<!-- 代码库 -->
<repositories>
<repository>
<id>maven-ali</id>
<url>http://maven.aliyun.com/nexus/content/groups/public//</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
<updatePolicy>always</updatePolicy>
<checksumPolicy>fail</checksumPolicy>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>public</id>
<name>aliyun nexus</name>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
<!--module不用添加打包版本信息-->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring.boot.version}</version>
<configuration>
<fork>true</fork>
<addResources>true</addResources>
</configuration>
</plugin>
</plugins>
</build>
(3)父工程下创建常量类
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--项目中添加 spring-boot-starter-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--数据库连接-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--MybatisPlus驱动-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<!-- 代码自动生成依赖 begin 上线后不需要-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.4.1</version>
<!--<scope>provided</scope>-->
</dependency>
<!-- velocity -->
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.0</version>
<!--<scope>provided</scope>-->
</dependency>
<!-- 代码自动生成依赖 end-->
<!--swagger ui接口文档依赖-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
</dependencies>
(4)创建order-service、video-service、user-service
<dependencies>
<dependency>
<groupId>net.xdclass</groupId>
<artifactId>common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
2.2.配置MyBatis
(1)分别在order-service、user-service、video-service的yml文件中加入mybatis配置
server:
port: 8001 #(端口号)
spring:
application:
name: user-service #(服务名称)
#数据库配置
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.10.88:3306/user?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai #(配置数据库地址)
username: root
password: 123456
#配置plus打印sql日志
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
(2)测试Mybatis
- VideoServiceImpl
@Service
public class VideoServiceImpl implements VideoService {
@Autowired
private VideoMapper videoMapper;
@Override
public Video findById(int id) {
return videoMapper.findById(id);
}
}
- VideoMapper
public interface VideoMapper {
@Select("select * from video where id = #{id}")
Video findById(int id);
}
- VideoController
@RestController
@RequestMapping("/api/v1/video")
public class VideoController {
@Autowired
private VideoService videoService;
@GetMapping("/find_video/{video_id}")
public JsonData findVideo(@PathVariable("video_id") int id){
System.out.println(id);
Video video = videoService.findById(id);
return JsonData.buildSuccess(video);
}
}
- 测试
3.RestTemplate服务间调用
(1)订单微服务主类注入RestTemplate
@SpringBootApplication
@MapperScan("net.xdclass.mapper")
public class OrderApplication {
public static void main(String[] args){
SpringApplication.run(OrderApplication.class);
}
@Bean
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
(2)Controller中调用
@RestController
@RequestMapping("/api/v1/order")
public class OrderController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("/save/{video_id}")
public JsonData save(@PathVariable("video_id") int id){
Video video = restTemplate.getForObject("http://localhost:7001/api/v1/video/find_video/" + id, Video.class);
System.out.println("video:"+video);
VideoOrder videoOrder = new VideoOrder();
videoOrder.setCreateTime(new Date());
videoOrder.setVideoImg(video.getCoverImg());
videoOrder.setVideoImg(video.getCoverImg());
videoOrder.setVideoTitle(video.getTitle());
return JsonData.buildSuccess(videoOrder);
}
}
3.Nacos服务治理
3.1.服务注册中心简介
(1)什么是注册中心
服务注册:服务提供者provider,启动的时候想注册中心上报自己的网络信息.
服务发现:服务消费者consumer,启动的时候想注册中心上报自己的网络信息,拉取provider的相关网络信息.
核心:服务管理,注册中心有个注册表,心跳机制动态维护,服务实例在启动时注册到服务表,关闭时注销.
(2)为什么要用
微服务应用和机器越来越多,调用方需要知道接口的网络地址,如果靠配置文件的方式去控制网络地址,对于动态新增机器,维护带来很大问题,主流的注册中心:zookeeper、Eureka、consul、etcd、Nacos。
(3)nacos的特性
3.2.Nacos注册中心实战
(1)安装部署Nacos
#上传Nacos压缩包到服务器,解压
unzip nacos-server-1.3.2.zip
#配置jdk、maven环境,解压jdk、maven安装包
#jdk
JAVA_HOME=/usr/local/jdk8
export JAVA_HOME
CLASSPATH=.:$JAVA_HOME/lib
export CLASSPATH
PATH=$PATH:$JAVA_HOME/bin:$CLASSPATH
export PATH
#maven
MAVEN_HOME=/usr/local/maven
export MAVEN_HOME
PATH=$PATH:$MAVEN_HOME/bin
export PATH
(2)启动Nacos
cd /usr/local/nacos/bin
./startup.sh
#启动后会发现启动不成功,因为nacos默认是以集群方式启动的,要修改启动方式
#需修改配置文件
export MODE="standalone"
(3)开放防火墙端口,nacos默认端口8848,重启防火墙
firewall-cmd --add-port=8848/tcp --permanent
firewall-cmd --reload
(4)Nacos访问页面,默认账号密码为nacos
3.3.微服务配置Nacos
(1)yml文件中配置服务注册
spring:
application:
name: order-service
cloud:
nacos:
discovery:
server-addr: 192.168.10.88:8848 #nacos地址
(2)主类配置@EnableDiscoveryClient注解
@EnableDiscoveryClient
(3)验证服务注册
3.5.微服务间服务调用案例
@RestController
@RequestMapping("/api/v1/order")
public class OrderController {
@Autowired
private DiscoveryClient discoveryClient;
@Autowired
private RestTemplate restTemplate;
@GetMapping("/save/{video_id}")
public JsonData save(@PathVariable("video_id") int id){
// Video video = restTemplate.getForObject("http://localhost:7001/api/v1/video/find_video/" + id, Video.class);
//list获取服务的元数据信息,包括IP,端口,服务名称等等
List<ServiceInstance> list = discoveryClient.getInstances("video-service");
ServiceInstance serviceInstance = list.get(0);
Video video = restTemplate.getForObject("http://"+serviceInstance.getHost()+":"+serviceInstance.getPort()+
"/api/v1/video/find_video?id="+id,Video.class);
System.out.println("video:"+video);
VideoOrder videoOrder = new VideoOrder();
videoOrder.setCreateTime(new Date());
videoOrder.setVideoImg(video.getCoverImg());
videoOrder.setVideoImg(video.getCoverImg());
videoOrder.setVideoTitle(video.getTitle());
return JsonData.buildSuccess(videoOrder);
}
4.Ribbon负载均衡策略
4.1.什么是Ribbon
Ribbon是⼀个客户端负载均衡⼯具,通过Spring Cloud封装,可以轻松和AlibabaCloud整合。
订单微服务开启Ribbon,添加注解@LoadBalanced
@Bean
@LoadBalanced
public RestTemplate restTemplate() { return new RestTemplate();}
controller中要修改成服务名称调用,ribbon是根据服务名称调用的
4.2.Ribbon源码分析
(1)大体流程
- 首先请求通过注册中心获取provider的列表
- 通过一定的策略选择一个节点
- 在返回给RestTemplate进行调用
(2)Ribbon的作用主要在于第二步选择节点
- 首先请求进来先进入注解
- 点进LoadBalancerClient接口
点进LoadBalancerClient的实现类RibbonLoadBalancerClient,只有这一个实现类
- 主要看getServer方法是选择负载均衡策略的方法
- loadBalancer默认传的是DynamicServerListLoadBalancer,但是DynamicServerListLoadBalancer没有chooseServer方法,所以掉用的是父类BaseLoadBalancer的。
- 默认BaseLoadBalancer定义的rule就是RoundRobinRule规则
(3)RibbonLoadBalancerClient继承体系
4.3.Ribbon负载均衡策略调整
(1)Ribbon支持的负载均衡策略
策略类 | 命名 | 描述 |
---|---|---|
RandomRule | 随机策略 | 随机选择Server |
RoundRobinRule | 轮询策略 | 按照顺序选择server(默认) |
RetryRule | 重试策略 | 当选择server不成功,短期内尝试选择一个可用的server |
AvailabilityFilteringRule | 可用过滤策略 | 过滤一直失败并被标记为circuit tripped的server,过滤掉那些高并发连接的server |
WeightedResponseTimeRule | 响应时间加权重策略 | 根据server的响应时间分配权重,以响应时间作为权重,响应时间越短的服务器被选中的概率越⼤,综合了各种因素,⽐如:⽹络,磁盘,io等,都直接影响响应时间 |
ZoneAvoidanceRule | 区域权重策略 | 综合所在区域的性能,和server的可用性,轮询选择server |
(2)配置yml
#使用随机负载均衡策略
video-service:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
(3)测试、分别启动三个VideoServer:9001、9006、9007
策略选择:
- 如果每个机器配置一样,则建议不修改策略(推荐)
- 如果部分机器配置强,则可以改为WeightedResponseTimeRule
5.Open-Feign实现远程掉用
5.1.Feign简介
- Ribbon存在的问题:不规范、风格不统一、维护性比较差。
- Fegin:SpringCloud提供的伪HTTP客户端(本质还是Http),封装了Http调用流程,更适合面向接口化让java接口注释的方式调用Http请求。
- Nacos支持Fegin可以直接集成实现负载均衡的效果。
- 官方文档:https://spring.io/projects/spring-cloud-openfeign
5.2.集成Feign实现远端调用
- Fegin让方法调用更加解耦
(1)common项目加入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
(2)主类配置注解@EnableFeignClients
@EnableFeignClients
(3)编写Fegin的service接口
@EnableFeignClient(name="video-service")
public interface VideoFeginService{
@GetMapping("/api/v1/video/find_by_id")
JsonData findById(@RequestParam("id") int id)
}
(4)Post方式调用
@PostMapping("/api/v1/video/save")
JsonData save(@RequestBody Video video);
5.3.Ribbon和Feign的选择
Feign默认集成了ribbon
写起来更加思路清晰和方便
采用注解方式进行配置,配置熔断等方式方便
6.分布式架构CAP定理
6.1.CAP定理简介
-
CAP定理:指的是在一个分布式系统中,Consistency(一致性)、 Availability(可用性)、Partition tolerance(分区容错性),三者不可同时获得
- 一致性(C):所有节点都可以访问到最新的数据
- 可用性(A):每个请求都是可以得到响应的,不管请求是成功还是失败
- 分区容错性(P):除了全部整体网络故障,其他故障都不能导致整个系统不可用
-
CAP理论就是说在分布式存储系统中,最多只能实现上面的两点。而由于当前的网络硬件肯定会出现延迟丢包等问题,所以分区容忍性是我们必须需要实现的。所以我们只能在一致性和可用性之间进行权衡
CA: 如果不要求P(不允许分区),则C(强一致性)和A(可用性)是可以保证的。但放弃P的同时也就意味着放弃了系统的扩展性,也就是分布式节点受限,没办法部署子节点,这是违背分布式系统设计的初衷的
CP: 如果不要求A(可用),每个请求都需要在服务器之间保持强一致,而P(分区)会导致同步时间无限延长(也就是等待数据同步完才能正常访问服务),一旦发生网络故障或者消息丢失等情况,就要牺牲用户的体验,等待所有数据全部一致了之后再让用户访问系统
AP:要高可用并允许分区,则需放弃一致性。一旦分区发生,节点之间可能会失去联系,为了高可用,每个节点只能用本地数据提供服务,而这样会导致全局数据的不一致性
6.2.CAP下的注册中心的选择
常见的注册中心:zk、eureka、nacos
Naocs | Eureka | Consul | Zookeeper | |
---|---|---|---|---|
一致性协议 | CP+CA | AP | CP | CP |
健康检查 | TCP/HTTP/MYSQL/ClientBeat | 心跳 | TCP/HTTP/gRPC/Cmd | Keep Alive |
雪崩保护 | 有 | 有 | 无 | 无 |
访问协议 | HTTP/DNS | HTTP | HTTP/DNS | TCP |
SpringCloud集成 | 支持 | 支持 | 支持 | 支持 |
- Zookeeper:CP设计,保证了一致性,集群搭建的收,某个节点失败,则会进行选举leader,或者半数以上的节点无法用,则无法提供服务,一次可用性无法满足。
- Eureka:AP原则,无主从节点,一个节点挂了,自动切换其他节点可以使用,去中心化。
- 结论:
- 分布式系统中P,肯定要满足,所以只能在CA中二选一
- 没有最好的选择,最好的选择是根据业务场景来进行架构设计
- 如果要求一致性,则选择zookeeper/Nacos,如金融行业 CP
- 如果要求可用性,则Eureka/Nacos,如电商系统 AP
- CP : 适合支付、交易类,要求数据强一致性,宁可业务不可用,也不能出现脏数据
- AP: 互联网业务,比如信息流架构,不要求数据强一致,更想要服务可用
6.3.BASE理论
什么是base理论
CAP中的一致性和可用性进行一个权衡的结果,核心思想就是:我们无法做强一致,但每个应用都可以根据自身的业务特点,采取响应的方式来是系统达到最终的一直性,来自ebay的架构师提出。
- Basically Available(基本可用)
- 假设系统出现了不可预知的故障,但是还能用,可能会有性能或者功能上的影响。
- Soft state(软状态)
- 允许系统中的数据存在中间状态,并且认为该状态不影响系统的整体可用性,即允许系统在多个不同节点的数据副本存在数据延时
- Eventually consistent(最终一致性)
- 系统能够保证在没有其他新的更新操作的情况下,数据最终一定能够达到一致的状态,因此所有客户端对系统的数据访问最终都能够获取到最新的值
7.流量防卫兵Sentinel
7.1.高并发下微服务存在问题
微服务拆分多个系统,服务之间相互依赖,可能会由于系统负载过高,突发流量或者网络等各种异常情况导致服务不可用。
7.2.面向失败编程
- 限流:
- 漏斗,不管流量有多大,均匀的流入容器,令牌桶算法,漏桶算法。
- 熔断:
- 保险丝,熔断服务,为了防止整个系统故障,包含当前和下游服务:下单服务->商品服务->用户服务->(出现异常)->熔断风险服务。
- 降级:
- 抛弃一些非核心的接口和数据,返回兜底数据。
- 隔离:
- 服务和资源相互隔离,比如网络资源,机器资源,线程资源等,不会因为某个服务的资源不足而抢占其他服务的资源。
- 熔断和降级的相互交集
- 相同点:
- 从可用性和可靠性出发,为例防止系统崩溃,最终都是让用户感知到某些服务是不可用的。
- 不同点:
- 服务熔断一般是下游服务故障导致的,而服务降级一般是从整体系统负荷考虑,由调用方控制。
- 相同点:
7.3.Sentinel简介
(1)什么是Sentinel
- 阿里巴巴开源的分布式系统流控工具。
- 以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。
- 丰富的应用场景:消息削峰填谷、集群流量控制、实时熔断下游不可用等。
- 晚辈的实时监控:Sentinel同时提供监控功能。
- 提供开箱即用与其他开源框架整合,SpringCloud、Dubbo等。
(2)核心概念
- 资源:是Sentinel中的核心概念之一,可以是java程序中的任何内容,可以是服务或者方法,就是我们想要保护的东西。
- 规则:定义怎样的方式保护资源,主要包括流控规则、熔断降级等。
7.4.微服务项目Sentinel搭建
(1)Sentinel分为两个部分
- 核心库(java客户端):不依赖任何框架/库,能够运行于所有java运行时环境,同时对Dubbo、SpringCloud等框架也有较好的支持。
- 控制台(Dashboard):基于SpringBoot开发,打包后可以直接运行,不需要额外的Tomcat等应用容器
(2)微服务引入Sentinel依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
(3)微服务yml中引入Sentinel
sentinel:
transport:
dashboard: 127.0.0.1:8080
port: 9999
#dashboard: ip:8080 控制台访问地址
#port: 9999 本地启的端口,随机算哪个不能被占用的,会在应⽤对应的机器上启动⼀个 Http Server,该 Server 会与 Sentinel 控制台做交互, 若被占⽤,则开始+1⼀次扫描
(4)启动sentinel的jar包,控制台访问,默认的账号密码都是sentinel
java -jar dashboard-1.8.0.jar
注意:微服务注册上去后,由于Sentinel是懒加载模式,所以需要访问微服务后才会在控制台出现
7.5.Sentinel流量控制功能
(1)流量控制(flow control)
原理是监控应用流量的QPS或者并发线程数等指标,当达到指定的阈值时对流量进行控制,以避免被瞬时的流量高峰冲垮,从而保障应用的高可用性。
(2)两种规则
- 基于统计并发线程数的流量控制
并发数控制用于保护业务线程池不被满调用耗尽
Sentinel并发控制不负责创建和管理线程池,而是简单统计当前请求上下文的线程数目(正在执行的调用数目)
如果超出阈值,新的请求会被立即拒绝,效果类似于信号隔离。
- 基于统计QPS的流量控制
当QPS超过某个阈值的时候,则采取措施进行流量控制
7.6.Sentinel流控规则效果
(1)直接拒绝
- 默认的流量控制方式,当QPS超过任意规则的阈值后,新的请求就会被立刻拒绝掉。
(2)Warm up
- 冷启动/预热,如果系统在从之前长期处于空闲的状态,我们希望处理请求的数量是缓步增多,达到预期的时间以后,达到系统处理请求的最大值
(3)匀速排队
- 严格控制请求通过的时间,让每个请求匀速的通过,对应的是漏桶算法,主要用于处理突发性的流量,如消息队列,一下涌进大量消息,我们不希望返回失败,而是空闲时间慢慢处理。
- 注意:
- 匀速排队等待策略是Leaky Bucket算法结合虚拟队列等待机制实现。
- 匀速排队等待策略不止吃QPS>1000的场景
7.7.Sentinel熔断降级规则
(1)熔断降级
- 对调用链路中不稳定的资源进行熔断降级也是保障高可用的重要措施之一
- 对不稳定的弱依赖服务调用进行熔断降级,暂时切断不稳定调用,避免局部不稳定因素导致整体的雪崩
- 熔断降级作为保护自身的手段,通常在客户端进行配置
(2)Sentinel熔断降级策略
-
慢比例调用(响应时间):选择以慢比例调用比例作为阈值,需要设置允许的慢调用RT(及最大的响应时间),请求的响应时间大于该值则统计为慢调用。
- 最大RT:设置访问的最大时间限制
- 比例阈值:【0.1-1.0】之间,指所有请求中最大RT请求数的比例
- 熔断时长:超过时间后尝试恢复
- 最小请求数:熔断触发的最小请求数
-
异常比例:当单位统计市场内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断
- 比例阈值:【0.0-1.0】之间,指所有请求中发生异常请求的比例
- 熔断时长:查过比例阈值系统停止提供服务的时长
- 最小请求数:熔断触发的最小请求数
-
异常数:单位统计时长内的异常数目超过阈值之后会自动进行熔断
- 异常数:发生异常的数量
- 熔断时长:超过熔断时间后自动恢复
- 最小请求数:熔断触发的最小请求数
7.8.Sentinel自定义异常
(1)Sentinel⾃定义异常简介
- 限流和熔断返回的数据有问题,微服务交互基本都是json格式,所以需要⾃定义异常信息
(2)AlibabCloud版本升级,不兼容问题
- v2.1.0到v2.2.0后,Sentinel⾥⾯依赖进⾏了改动,且不向下兼容
- 自定义降级返回数据
/*旧版实现UrlBlockHandler并且重写blocked方法*/
@Component
public class MyUrlBlockHandler implements UrlBlockHandler {
@Override public void blocked(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, BlockException e) throws IOException { //降级业务处理 }
}
/*旧版实现BlockExceptionHandler并且重写blocked方法*/
@Component
public class MyBlockExceptionHandler implements BlockExceptionHandler {
@Override public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, BlockException e) throws Exception { //降级业务处理 }
}
(3)Sentinel自定义异常类
- 异常种类
FlowException //限流异常
DegradeException //降级异常
ParamFlowException //参数限流异常
SystemBlockException //系统负载异常
AuthorityException //授权异常
- 代码编写
@Component
public class XdBlockExceptionHandler implements BlockExceptionHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception {
Map<String,Object> backMap = new HashMap<>();
if(e instanceof FlowException){
backMap.put("code",-1);
backMap.put("msg","限流异常");
}else if (e instanceof DegradeException){
backMap.put("code",-2);
backMap.put("msg","熔断异常");
}else if (e instanceof ParamFlowException){
backMap.put("code",-3);
backMap.put("msg","热点异常");
}else if (e instanceof SystemBlockException){
backMap.put("code",-4);
backMap.put("msg","系统规则异常");
}else if (e instanceof AuthorityException){
backMap.put("code",-5);
backMap.put("msg","认证异常");
}
//设置返回json数据
response.setStatus(200);
response.setHeader("content-Type","application/json;charset=UTF-8");
response.getWriter().write(JSON.toJSONString(backMap));
}
}
(4)案例测试
- 限流异常
- 降级异常
7.9.Sentinel整合Open-Feign
(1)加入依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
(2)开启Feign对Sentinel的支持
feign:
sentinel:
enabled: true
(3)创建容错类,实现对应的服务接口,记得加注解
@Service
public class VideoServiceFallback implements VideoService {
@Override
public JsonData findVideo(int id) {
Video video = new Video();
video.setTitle("熔断降级数据");
return null;
}
@Override
public JsonData save(Video video) {
return null;
}
}
(4)配置feign容错类
@FeignClient(value = "xdclass-video-service", fallback = VideoServiceFallback.class)
8.微服务核心组件API Gateway
8.1.API Gateway应用场景
(1)什么是网关
API Gateway,是系统的唯一对外的入口,介于客户端和服务端之间的中间层,处理非业务功能,提供路由请求、鉴权、监控、缓存、限流等功能
(2)统一接入
- 智能路由
- AB测试、灰度测试
- 负载均衡、容灾处理
- 日志埋点(类似nginx日志)
(3)流量控制
- 限流处理
- 服务降级
(4)安全防护
- 鉴权处理
- 监控
- 机器网络隔离
(5)主流的网关
- zuul:是Netflix开源的微服务网关,和Eureka、Ribbon、Hystrix等组件配合使用,依赖组件比较多,性能较差。
- kong:由Mashape公司开源的,基于Nginx的API gateway
- nginx+lua:高性能的HTTP反向代理服务器,lua是脚本语言,让Nginx执行脚本,并且高并发、非阻塞的处理各种请求。
- SpringCloud Gateway:Spring公司专门开发的网关,替代zuul,基于Spring5+Reactor技术开发,在spring2.x中可以直接jar包方式运行
8.2.API Gateway配置实战
(1)先添加网关的依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
(2)配置启动类
@SpringBootApplication
public class GatewayApplication {
public static void main(String[] args){
SpringApplication.run(GatewayApplication.class);
}
}
(3)配置yml文件
server:
port: 8888
spring:
application:
name: gateway-service
cloud:
gateway:
routes:
- id: video-service #唯一标识
uri: http://127.0.0.1:9001 #转发服务的地址端口
order: 1 #优先级,1表示最大
predicates: #断言,配置那个路径才转发
- Path=/video-server/**
filters: #过滤器,请求在传递过程中通过过滤器修改
- StripPrefix=1 #1表示过滤掉第一层前缀
(4)效果演示
8.3.API Gateway整合Nacos
(1)添加nacos依赖
<!--添加nacos客户端-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
(2)启动类开启支持
@EnableDiscoveryClient
(3)修改配置文件
server:
port: 8888
spring:
application:
name: gateway-service
cloud:
gateway:
routes:
- id: video-service
uri: lb://video-service #注意这块要写nacos中注册的服务名,lb://服务名
order: 1
predicates:
- Path=/video-server/**
filters:
- StripPrefix=1
discovery:
locator:
enabled: true #开启网关拉取nacos服务
nacos:
discovery:
server-addr: 192.168.10.88:8848 #配置nacos地址
(4)效果演示
8.4.API Gateway交互流程
- 客户端向SpringCloud Gateway发出请求
- 如果网关处理程序映射确定请求与路由匹配
- 则将其发送到网关web处理程序
- 通过特定过滤器链运行,前置处理-后置处理
- 过滤器生命周期
- PRE:这种过滤器在请求被路由之间调用,一般用于鉴权,限流等。
- POST:这种过滤器在路由到微服务以后执行,一般用于修改响应结果,比如增加header信息,打点日志等
- 网关过滤分类
- 局部过滤器GatewayFilter:应用在某个路由上,每个过滤器工厂都对应一个实现类,并且这些类的名称必须以 GatewayFilterFactory 结尾
- 全局过滤器:作用全部路由上
8.5.Gateway内置的路由断言
(1)什么是Gateway路由断言
- Predicate来源于java8,接受输入参数,返回一个布尔值结果
- SpringCloud Gateway中Spring利用Predicate的特性实现了各种路由匹配规则
- 转发判断条件,Spring Cloud Gateway支持多种方式,常见如:Path、Query、Method、Header等
- 支持多个Predicate,但是必须全部Predicate满足才可以进行路由转发
- 参数编写规则XXXRoutePredicateFactory,使用XXX作为参数配置
predicates:
- Host=
- Path=
- Method=
- Header=
- Query=
- Cookie=
8.6.Gateway配置定时下线接口
#需求:接口需要在指定时间进行下线,过后不可以在被访问
#使用Before,只有当时时间小于设定时间,路由才会匹配请求
#格式:
predicates:
- Before=2020-09-09T01:01:01.000+08:00
8.7.Gateway配置必传参数
- Query=source
8.8.自定义全局过滤器实现鉴权
(1)代码编写
@Component
public class UserGlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//写业务逻辑
String token = exchange.getRequest().getHeaders().getFirst("token");
if(StringUtils.isBlank(token)){
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
//否则就继续往下执行
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 0;
}
}
(2)测试
- 注意:网关不要加太多的业务逻辑,否则会影响性能。
9.微服务链路追踪系统
9.1.微服务链路追踪系统的作用
(1)微服务中常见的问题
- 微服务中链路调用时间过长怎么定位
- 微服务中链路调用出现异常怎么排查
(2)链路追踪系统
-
分布式应用架构虽然满足了应用横向扩展的需求,但是运维和诊断的过程变得越来越复杂,例如会遇到接口诊断困难、应用性能诊断复杂、架构分析复杂等难题,传统的监控工具并无法满足,分布式链路系统由此诞生
-
核心:将一次请求分布式调用,使用GPS定位穿起来,记录每个调用的耗时、性能等日志,并通过可视化工具展示出来。
-
注意:AlibabaCloud全家桶还没对应的链路追踪系统,我们使用Sleuth和zipkin(内部使用的鹰眼)
9.2.整合链路追踪组件Sleuth
(1)什么是Sleuth
- 一个组件,专门记录链路数据的开源组件
(2)微服务项目添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
(3)日志各个字段含义
#示例:[order-service,96f95a0dd81fe3ab,852ef4cfcdecabf3,false]
第一个:服务名称,spring.application.name
第二个:trace id,整个服务调用链路只用这一个trace id
第三个:span id,标记每个微服务的唯一标识,基本工作单元
第四个:是否要将该信息输出到zipkin服务中收集和展示
9.3.整合链路追踪可视化Zipkin
(1)什么是zipkin
大规模分布式系统的APM工具(Application Performance Management),基于Google Dapper的基础实现,和sleuth结合可以提供可视化web界面分析调用链路耗时情况
(2)同类产品
- 鹰眼(EagleEye)
- CAT
- twitter开源zipkin,结合sleuth
- Pinpoint,运用JavaAgent字节码增强技术
- StackDriver Trace (Google)
(3)zipkin启动
- zipkin启动非常容易,直接启动jar即可
java -jar zipkin-server-2.12.9-exec.jar
- 默认端口9411
(4)zipkin组成:Collector、Storage、Restful API、Web UI组成
9.4.链路追踪Zipkin+Sleuth整合
(1)微服务项目添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
(2)配置zikpin地址和采样百分比配置
spring:
application:
name: api-gateway
zipkin:
base-url: http://127.0.0.1:9411/ #zipkin地址
discovery-client-enabled: false #不用开启服务发现
sleuth:
sampler:
probability: 1.0 #采样百分比
默认为0.1,即10%,这里配置1,是记录全部的sleuth信息,是为了收集到更多的数据(仅供测试用)。
在分布式系统中,过于频繁的采样会影响系统性能,所以这里配置需要采用一个合适的值。
9.5.链路追踪日志持久化
(1)存在的问题
- 服务重启会导致链路追踪系统的数据丢失
(2)配置mysql持久化
- 官方提供了mysql的脚本
nohup java -jar zipkin-server-2.12.9-exec.jar --STORAGE_TYPE=mysql --MYSQL_HOST=127.0.0.1 --MYSQL_TCP_PORT=3306 --MYSQL_DB=zipkin_log --MYSQL_USER=root --MYSQL_PASS=xdclass.net &
10.微服务分布式配置中心
10.1.微服务下的分布式配置中心
(1)现在微服务存在的问题
- 配置文件增多,不好维护
- 修改配置文件需要重新发布
(2)什么是配置中心
-
统一管理配置,快速切换各个环境的配置
-
相关产品
- 百度的disconf 地址:https://github.com/knightliao/disconf
- 阿⾥的diamand 地址:https://github.com/takeseem/diamond
- springcloud的configs-server: 地址:http://cloud.spring.io/spring-cloud-config/
- 阿⾥的Nacos:既可以当服务治理,⼜可以当配置中⼼,Nacos = Eureka + Config
10.2.微服务整合Nacos配置中心
(1)添加项目依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId></dependency>
(2)配置bootstrap.yml配置文件
- 配置文件优先级讲解
- 不能使用原先的application.yml,需要使用bootstrap.yml作为配置文件
- 配置读取优先级bootstrap.yml > application.yml
- 配置实操
- 订单服务迁移配置
- 增加bootstrap.yml
spring:
application:
name: order-service
cloud:
nacos:
config:
server-addr: 192.168.10.88:8848 #Nacos配置中心地址
file-extension: yaml #文件扩展格式
profiles:
active: dev
(3)nacos控制台配置
(4)Data Id组成结构
${prefix}-${spring.profiles.active}.${file-extension}
prefix:默认为spring.application.name的值
spring.profiles.active:即为当前环境对应的profile如果当前的spring.profiles.active为空时,对应的连接符-也将不存在,dataId的拼接格式变为${prefix}.${file-extension}
file-exetension:配置内容的数据格式,可以通过配置项 spring.cloud.nacos.config.file-extension 来配置。⽬前只⽀持 properties 和 yaml 类型。
(5)测试配置是否生效
-
访问网址:http://192.168.10.88:8848/nacos/#/newconfig?serverId=center&namespace=&edasAppName=&edasAppId=&searchDataId=&searchGroup=
-
启动微服务
10.3.配置中心Nacos动态配置下发
(1)什么是动态刷新配置
- 修改了配置文件,程序不能自动更新
- 动态刷新就可以解决这个问题
(2)添加属性值url
(3)controller增加@RefreshScope
- 注意:这块一定要加在controller上,不要加在启动类上
10.4.Nacos配置中心持久化
(1)进入到nacos安装目录下的conf将脚本在mysql中执行
(2)配置nacos的配置文件
(3)重启nacos
/usr/local/nacos/bin/startup.sh
(4)测试效果
10.5.搭建Nacos服务集群
(1)准备三台机器,分别部署nacos
192.168.10.90:8848
192.168.10.91:8848
192.168.10.92:8848
- 安装步骤这里省略
(2)编写cluster.conf配置文件
#编写内容,三台机器分别编写
192.168.10.90:8848
192.168.10.91:8848
192.168.10.92:8848
(3)配置mysql持久化
#三天机器分别配置
### If use MySQL as datasource:
spring.datasource.platform=mysql
### Count of DB:
db.num=1
### Connect URL of DB:
db.url.0=jdbc:mysql://192.168.10.88:3306/nacos?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC
db.user=root
db.password=123456
(4)访问nacos可以三台集群节点
11.微服务容器化部署
11.1.前期依赖准备
(1)父工程确定版本
<properties>
<java.version>1.8</java.version>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<spring.boot.version>2.3.3.RELEASE</spring.boot.version>
</properties>
(2)每个项目添加镜像打包前缀
<docker.image.prefix>docker-cloud</docker.image.prefix> <!--镜像打包前缀-->
(3)每个项目添加打包配置
<build>
<finalName>alibaba-cloud-gateway</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<fork>true</fork>
<addResources>true</addResources>
</configuration>
</plugin>
<plugin>
<groupId>com.spotify</groupId>
<artifactId>dockerfile-maven-plugin</artifactId>
<version>1.4.10</version>
<configuration>
<repository>${docker.image.prefix}/${project.artifactId}</repository>
<buildArgs>
<JAR_FILE>target/${project.build.finalName}.jar</JAR_FILE>
</buildArgs>
</configuration>
</plugin>
</plugins>
</build>
11.2.DockerFile打包微服务镜像
(1)编写DockerFile文件,注意一定要命名成dockerfile
FROM adoptopenjdk/openjdk11:jre11u-nightly
COPY target/打包后的jar名 打包后的jar名
ENTRYPOINT ["java","-jar","/打包后的jar名"]
(2)执行打包命令
mvn install -Dmaven.test.skip=true dockerfile:build
(3)启动容器
docker run 镜像ID -p 指定端口
(4)查看容器日志
docker logs -f 容器ID
标签:知识点,服务,spring,配置,nacos,Alibaba,video,SpringCloud,cloud
From: https://blog.51cto.com/u_15646271/6019234