特别声明:整理自慕课网大目师兄的微服务视频,链接: https://coding.imooc.com/learn/list/358.html
前情提要:在 nacos上注册了 content-center和 user-center两个服务, content-center使用 Feign调用 user-center服务,使用 Ribbon做负载均衡, sentinel实现服务容错! sentinel使用简单,精通就难了,可以配置的选择很多,可以操作的余地也很足!
1.为什么需要服务容错?
1.1 雪崩效应
雪崩效应(级联故障):如图所示,C,D服务调用B服务,B服务调用A服务,如果A服务突然发生故障,那么B服务调用A服务就得不到返回,直到请求超时,在超时之前的那段时间内,请求一直在等待,简直欲穿秋水,一个请求就是一个线程,线程一直处于阻塞状态,会一直占用服务器的资源,比如内存,cpu;如果B的并发很高,这样阻塞的线程很多,那么B服务器再也没有资源去创建新的线程,于是B也挂了,然后C,D服务又请求不到B服务了…然后C,D服务也挂了。因为A服务的故障,BCD没有做任何处理,都挂了,这就是雪崩效应
1.2 什么是服务容错
我的理解就是服务容错的目的就是保护自己服务的正常运行。比如B服务碰到上面那种请求等待导致线程太多的时候,我就限流,某某请求的线程不得超过10个,超过10个就不再接受新的请求,直接返回被限流错误提示,这样B服务就不会因为太多线程耗尽内存而亡了
1.3 常见的容错方案
- 超时
最容易想到的方案,把请求时间设置的很短,这样线程释放的足够快,B服务就不会那么容易被耗死了
体现思想:天下武功,唯快不破! - 限流
我们经过测试,发现某个请求能承受的最大QPS(每秒查询率)是1000,那我们就把这个请求的QPS阈值设置成800,超过了这个阈值,请求直接返回被限流的错误(限流功能仅仅是Setinel众多功能当中的一个)
体现思想:世相万变,我心不变,他弱由他弱,清风拂山岗,他强由他强,明月照大江! - 仓壁模式
创造很多的隔断间,每个隔断间都有一个自己的线程池,你自己的线程池满了,后续请求按照线程池的规则排队,还是啥的就行了,不会影响到其它的隔断间。可以把Controller当作隔断间,也可以是其它的,反正就是这个意思
体现思想:不把鸡蛋放一个篮子里! - 断路器模式
检测一定时间内的错误率,错误数,比如5秒之内错误率,错误次数,达到某个阈值,就认为B服务所调用的A服务挂了,我就跳闸,就不会堆积那么多线程等待了,然后在10秒之后(称作断路器时间窗口),断路器变成半开状态,此状态向A再发送仅仅一次请求,如果这次请求又失败了,我再跳闸,默默的等10秒(断路器时间窗口,不是非得10秒,我只是举例子,值自己可以设定),断路器又变成半开状态,再次向A发送一次请求,如果这次请求成功了,断路器就彻底恢复,闸彻底闭合(断路器模式也是Setinel众多功能当中的一个)
体现思想:可以自我修复的高级保险丝!
2.部署Sentinel控制台
Sentinel和nacos有点像,都有个控制台
2.1 下载Sentinel.jar包
- 下载地址
https://github.com/alibaba/Sentinel/releases,我们用的spring-cloud-aibaba是0.9版本的,生产上我们应该下载同版本的Sentinel.jar。学习测试就选最新的V1.6.3,点击下载
2.2 运行Sentinel.jar包
Sentinel的端口是8080,所以你得把8080端口先空出来
一般执行下面这个命令就行了,不过窗口关闭,服务也会停掉
java -jar sentinel-dashboard-1.6.3.jar
这个命令是linux系统后台窗口运行的,窗口关闭,服务也不会停止
nohup java -jar sentinel-dashboard-1.6.3.jar &
2.3 登录Sentinel控制台
ip+端口号访问服务,用户名和密码都是:sentinel,我用本地的sentinel
3.微服务整合Sentinel
依旧三板斧
3.1 加依赖
sentinel建立在springboot的actuator组件上
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
3.2 写注解
暂无注解
3.3 写配置
指定sentinel 控制台地址
spring:
cloud:
sentinel:
transport:
# 指定 sentinel 控制台地址
dashboard: localhost:8080
management:
endpoints:
web:
exposure:
# 暴露出所有actuator监控的端点
include: '*'
3.4 懒加载
sentinel是懒加载的,所以得访问一下content-center服务的http://localhost:8081/poem/testRibbon接口,再刷新Sentinel控制台,就能看到content-center了
4.簇点链路
- 给资源添加规则的入口
- 被请求的路径,都称作资源,会在簇点链路里显示出来,没有被请求的就不会显示出来
5.流控规则限流
5.1 参数解释
参数名 | 含义 |
---|---|
资源名 | 即限流规则的作用对象 |
针对来源 | 流控针对的调用来源(服务级别),若为 default 则不区分调用来源 |
阈值类型 | QPS(每秒查询率) 或并发线程数(感觉这个就是仓壁模式) |
单机阈值 | 限流阈值,超过这个值就执行流控规则 |
流控模式 | 直接,关联,链路 |
流控效果 | 直接拒绝、Warm Up、匀速排队 |
是否集群 | 这个功能好像还不能用 |
比较难以理解的就是流控模式和流控效果
5.1.1 流控模式
基于调用关系的流量控制
- 直接
直接针对资源本身 - 关联
当两个资源之间具有资源争抢或者依赖关系的时候,这两个资源便具有了关联。当关联的资源达到阈值,就限流自己,让自己让出系统资源给关联的资源,也就是说在限流自己保护关联资源 - 链路
资源通过调用关系,相互之间构成一棵调用树,老抽象了,下面的common和chain/b就是树的关系,这就是一条链路
5.1.2 流控效果
- 直接拒绝
当QPS或线程数超过规则的阈值后,新的请求就会被立即拒绝,拒绝方式为抛出FlowException。这种方式适用于对系统处理能力确切已知的情况下,比如通过压测确定了系统的准确水位时 - Warm Up
当流量突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮。通过"冷启动",让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮 - 匀速排队
严格控制请求通过的间隔时间,也即是让请求以均匀的速度通过,对应的是漏桶算法。这种方式主要用于处理间隔性突发的流量,例如消息队列。想象一下这样的场景,在某一秒有大量的请求到来,而接下来的几秒则处于空闲状态,我们希望系统能够在接下来的空闲期间逐渐处理这些请求,而不是在第一秒直接拒绝多余的请求
5.2 测试三种流控模式
5.2.1 直接
默认就是直接
- 添加规则
对资源testSentinel/QPSFlowControl添加如下流控规则:如果QPS(每秒查询率)阈值超过1,就直接返回失败
- 测试
快速多次刷新,可以看到,通过6次请求,拒绝14次,规则起作用了
5.2.2 关联
- 写了两个请求
@RestController
@Slf4j
@RequestMapping("/testSentinel")
public class FlowControl {
@GetMapping("related/a")
public String flowRelatedA(){
log.info("我是被限流的资源......");
return "related/a";
}
@GetMapping("related/b")
public String flowRelatedB(){
log.info("我是被关联资源,不是限流我......");
return "related/b";
}
- 配置/testSentinel/related/a的限流
如果/testSentinel/related/b的QPS达到1,那么限流/testSentinel/related/a
- 使用Postman 25秒内访问/testSentinel/related/b 50次,每500ms一次,QPS=2 > 阈值1,满足限流条件
postman请求之前,可以正常访问/testSentinel/related/a
启动postman请求之后:
从控制台可以看到/testSentinel/related/b 50次请求全通过,但是/testSentinel/related/a的请求就被拒绝了,测试通过
5.2.3 链路
- 测试代码,两个请求资源:/testSentinel/chain/a,/testSentinel/chain/b
flowService.common() 注解为sentinel资源,a和b都使用了common方法
@RequestMapping("/testSentinel")
public class FlowController {
@Autowired
FlowService flowService;
@GetMapping("chain/a")
public String chainA(){
flowService.common();
return "chain/a";
}
@GetMapping("chain/b")
public String chainB(){
flowService.common();
return "chain/b";
}
@Service
@Slf4j
public class FlowService {
@SentinelResource
public void common(){
log.info("common......");
}
}
- 配置common的限流,链路入口资源/testSentinel/chain/a
如果/testSentinel/chain/a的QPS达到1,那么限流/testSentinel/chain/a。而/testSentinel/chain/b毫无影响
- 测试
疯狂刷新 http://localhost:8081/testSentinel/chain/a,被限流了
疯狂刷新 http://localhost:8081/testSentinel/chain/b,毫无阻碍!符合链路规则
5.3 测试三种流控效果
5.3.1 快速失败
上一节测试的默认都是这种流控效果,返回限流消息或者500错误
5.3.2 Warm Up
初始阈值为设定阈值/codeFactor(默认3),经过预热时长,阈值升高到设定阈值,让可通过的流量缓慢增长
- 修改单击阈值为6,流控效果选择 Warm Up
实际上初始阈值=6/3,为2,经过10秒之后,阈值升高到6
- 测试
使用postman测试,0.1秒请求一次,总共请求200次,QPS为10 ,持续20秒 http://localhost:8081/testSentinel/QPSFlowControl,
sentinel的实时监控台
21:06:46秒时,通过QPS为2,符合 6/3=2
21:06:58秒开始,通过QPS为6,后面稳定6,直到QPS自己降下来,预热时间
58-46=12秒,略大于设置的10秒。总体基本符合限流设置!
5.3.3 排队等待
这个限流效果只对阈值类型QPS的起作用
- 设置/testSentinel/QPSFlowContro限流规则
设置之前清除它其它的容错规则,以免被干扰。设置阈值2,超时时间10秒,
规则效果:QPS超过阈值的请求,不放弃请求,而是让请求排队等待被执行,服务以一秒2次的速度处理排队的请求,如果某个请求的等待时间超过10秒,才会返回失败
- 测试
使用postman以一秒10次的速度请求http://localhost:8081/testSentinel/QPSFlowControl 100次,可以看到QPS一直是2,100请求全部通过,没有请求被丢弃,符合规则预期!
6.降级规则限流
6.1 三种降级策略
6.1.1 RT (Response Time)
6.1.1.1 释义
当1s内持续进入5个请求,对应时刻的平均响应时间(秒级统计,单位毫秒)均超过阈值ms,断路器打开,返回请求失败,知道时间窗口设置的时间结束,关闭降级
- RT默认上限是4900 ms
如需改动,可以通过下面的配置项来设置
-Dcsp.sentinel.statistic.max.rt=xxx
6.1.1.2 测试
- 测试准备代码
@RestController
@Slf4j
@RequestMapping("/down")
public class DownController {
@GetMapping("rt/a")
public String rtA(){
log.info("rtA....");
return "rt/a";
}
}
- 新增降级规则
1s内持续进入5个请求,平均响应时间 > 1ms,就返回错误提示,直到4秒之后恢复正常
- 疯狂刷新 http://localhost:8081/down/rt/a,考验手速的时候到了,RT肯定会大于1ms的,于是降级了
歇4秒,再次访问,正常了
6.1.2 异常比率
6.1.2.1 释义
当资源的每秒请求量 >= 5,并且每秒异常总数占通过量的比值超过阈值,资源进入降级状态;接下的时间窗口之后,降级恢复
异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%
6.1.2.2 测试
- 写个必抛异常的方法
要想异常被统计,必须 Tracer.trace(t)
@GetMapping("rt/b")
public String rtB() {
try {
log.info("rtB....");
throw new RuntimeException("throw runtime ");
} catch (Throwable t) {
Tracer.trace(t);
}
return "rt/b";
}
- 配置规则
资源的每秒请求量 >= 5,秒级异常比例 > 0.1,降级,4秒之后恢复
- 测试
疯狂刷新 http://localhost:8081/down/rt/b,必报异常,所有异常比率 > 0.1
4秒之后在刷新:
6.1.3 异常数
6.1.3.1 释义
资源近1分钟的异常数目超过阈值之后会进行降级,统计时间窗口是分钟级别的,所以时间窗口最好大于60s
6.1.3.2 测试
比较好理解,不测试了