Sentinel
学习目标
- Sentinel的控制规则
- 流控规则
- 降级规则
- 热点规则
- 使用自定义错误信息替换默认的提示信息
1. 介绍
分布式系统的流量防卫兵,以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。

官网:https://github.com/alibaba/Sentinel,https://sentinelguard.io/zh-cn/
中文文档:https://github.com/alibaba/Sentinel/wiki/介绍

2. 下载运行
地址:https://github.com/alibaba/Sentinel/releases

其实就是一个jar包,执行:**java -jar sentinel-dashboard-1.8.5.jar **

启动成功后,浏览器访问:http://192.168.56.102:8080/#/login

用户:sentinel,密码:sentinel
注意:Sentinel默认是8080端口,所以启动前确保8080没有被占用
3. 集成
1. 启动nacos

2. 项目配置
修改Consumer这个项目
1. pom.xml
使用Sentinel,需要以下几个依赖
<!-- sentinel -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!-- sentinel 持久化-->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
2. application.properties
增加以下配置:
#Sentinel的访问端口
spring.cloud.sentinel.transport.dashboard=localhost:8080
#Sentinel客户端通信端口,该端口负责跟Sentinel应用通信,默认是8719
spring.cloud.sentinel.transport.port=8719
3. Controller
新增 SentinelController
@RestController
public class SentinelController {
@GetMapping("/yase")
public String yase(){
return "。。。。。。yase";
}
@GetMapping("/daji")
public String daji(){
return "。。。。。。daji";
}
}
3. 结果
启动Sentinel后,再启动项目,查看结果

发现还是空的,这是因为Sentinel采用的是懒加载模式,需要先访问项目,才能显示,如:

访问时,多刷新几次

4. 配置

在这里可以配置一些 流控、降级、热点等规则
1. 流控规则
1. 配置解释
| 资源名 | 唯一名称,默认是请求路径 |
|---|---|
| 针对来源 | Sentinel可以针对调用者进行限流,填写服务名,默认default(不区分来源) |
| 阈值类型/单机阈值 |
- QPS(每秒钟的请求数量):当该接口的QPS达到阈值时,进行限流
- 线程数:当调用该接口的线程数达到阈值时,进行限流
|
| 是否集群 | 略 |
| 流控模式 | - 直接:接口达到限流条件时,直接限流
- 关联:当关联的资源达到阈值时,限流自己
- 链路:待定
|
| 流控效果 | - 快速失败:直接失败,抛异常
- Warm Up:预热,配置的阈值不会立即生效,需要经过一段时间的预热,具体看下方**Warm Up **示例
- 排队等待:匀速排队,让请求以匀速通过,选择这种时,阈值类型必为QPS
|
2. QPS

这样配置表示:访问/yase接口,每秒限制访问1次,如果超出阈值,直接返回失败信息
保存后可在流控规则中查看:

然后再浏览器中访问:http://localhost:8200/yase

3. 线程数
修改 /yase 接口
@GetMapping("/yase")
public String yase() throws InterruptedException {
System.out.println(Thread.currentThread().getName()+"。。。。。。"+ LocalDateTime.now());
Thread.sleep(5000);//休眠5秒钟,目的是验证同时访问这个方法的线程数量
return "。。。。。。yase";
}
重启项目,配置 /yase 接口的流控规则为线程数

线程数:类似Semaphore(信号量),限制同时访问该资源的线程数量,表示同时只能有一个线程访问这个接口
浏览器上同时开两个页签访问:http://localhost:8200/yase


上图中,5秒内,只有一个线程成功访问 /yase 资源
4. 关联模式
根据关联资源的QPS,限流自己
修改代码,还原 /yase 接口
@GetMapping("/yase")
public String yase() throws InterruptedException {
return "。。。。。。yase";
}
修改 /yase 接口的规则

设置 /daji 接口的流控规则

解释:**/yase 接口的实际QPS + /daji 接口的当前请求数(其实就是1) > /daji 接口配置的阈值,限流 **
测试:
这时候浏览器上高频率的 /daji 接口,不会限流,因为 /yase 接口的实际QPS 是 0

原理:
在 DefaultController 的 canPass 方法打断点,
注意:下方断点是51行

但是,postman不断访问** /yase**


浏览器访问/daji,结果:

原理:
在 DefaultController 的 canPass 方法打断点,
注意:下方断点是52行

实际场景:支付接口正在经历着巨大的访问压力,这个时候对下单的接口进行限流就很有必要了
5. 链路模式
统计从指定资源到本资源的请求数量,如果超过阈值,报错
新增 WangzheService
@Service
public class WangzheService {
//Sentinel默认只标记Controller中的方法为资源,如果要标记其它方法,需要利用@SentinelResource注解
@SentinelResource("xishi")
public void xishi(){
System.out.println("追求西施");
}
}
修改 /yase 和 /daji 接口
@Autowired
private WangzheService wangzheService;
@GetMapping("/yase")
public String yase() throws InterruptedException {
wangzheService.xishi();
return "。。。。。。yase";
}
@GetMapping("/daji")
public String daji(){
wangzheService.xishi();
return "。。。。。。daji";
}
修改 application.properties,增加
# 默认为true,表示将调用链路收敛,会导致链路流控效果无效,需要改成false
spring.cloud.sentinel.web-context-unify=false
重新启动项目,并且访问 :http://localhost:8200/yase,可以看到:

对 xishi 这个资源设置流控规则

上图:如果从 /yase 到 xishi 的请求频率,超过1秒1次,则报错
浏览器高频率访问 /yase 接口,会看到下面错误

但是高频率访问 /daji 接口,没有问题

6. Warm Up
当系统长期处于低QPS的情况下,流量突然增加,瞬间时流量过大,有可能把系统压垮。这种方式,让通过的流量缓慢增加,在指定时间内逐渐增加到阈值上限。
- 恢复代码

- 修改 /yase 接口的配置:

这样,阈值是10,预热时间是5秒,在5秒后,让阈值上升到10,也就是说,前5秒访问时,阈值不是10,前5秒阈值=10/coldFactor, coldFactor 是冷加载因子,默认为3,所以初始阈值为 10 / 3 = 3
这样配置,在浏览器中刷新访问/testa,前5秒会有限流信息,5秒后就看不到了
7. 排队等待
控制请求通过的间隔时间,也即是让请求匀速通过。
场景:在某一秒有大量的请求到来,而接下来的一段时间则处于空闲状态,我们希望系统能够在接下来的空闲期间逐渐处理这些请求,而不是直接拒绝多余的请求。
修改 /yase 接口
@GetMapping("/yase")
public String yase() throws InterruptedException {
System.out.println(Thread.currentThread().getName()+"。。。。。。"+ LocalDateTime.now());
return "。。。。。。yase";
}
重启项目后,编辑 流控规则

上图:让请求排队,每秒处理一个请求
这样子,即使不断访问,系统也是默认每秒处理一个请求

8. 规则持久化
重新启动服务,发现之前配置的规则都没有了,这很不合适
下面把配置好的规则持久化到nacos中
1. pom.xml
<!-- sentinel-nacos -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
2. nacos

[
{
"resource":"/yase",
"limitApp":"default",
"grade":1,
"count":1,
"strategy":0,
"controlBehavior":0,
"clusterMode":false
}
]
配置解释:
- resource:资源名
- limitApp:来源应用
- grade:阈值类型:0表示线程数,1表示QPS
- count:阈值
- strategy:流控模式:0表示直接,1表示关联,2表示链路
- controlBehavior:流控效果:0表示快速失败,1表示Warm Up,2表示排队等待
- clusterMode:是否集群
3. properties
修改 application.properties,增加:
# nacos 地址
spring.cloud.sentinel.datasource.ds1.nacos.server-addr=localhost:8848
#这个data-id需要跟nacos的对应
spring.cloud.sentinel.datasource.ds1.nacos.data-id=mz-sentinel
spring.cloud.sentinel.datasource.ds1.nacos.group-id=DEFAULT_GROUP
spring.cloud.sentinel.datasource.ds1.nacos.data-type=json
# flow:流控规则,其他规则查看: com.alibaba.cloud.sentinel.datasource.RuleType
spring.cloud.sentinel.datasource.ds1.nacos.rule-type=flow
4. 结果
重启服务后,浏览器访问:http://localhost:8200/yase,可以发现Sentinel中已经有了流控规则

这时修改nacos中的配置,可以在sentinel中实时看到


但是在sentinel中编辑规则,并不会同步到nacos中,官方文档也已经说明:
https://github.com/alibaba/Sentinel/wiki/%E5%9C%A8%E7%94%9F%E4%BA%A7%E7%8E%AF%E5%A2%83%E4%B8%AD%E4%BD%BF%E7%94%A8-Sentinel

2. 降级规则
先删除之前的流控规则
1. 慢调用比例
规则解释:

最大RT:请求最大响应时间,请求的响应时间大于该值则认为是为慢调用
慢调用比例:根据上图,连续访问3次,如果有2次都是慢调用,慢调用比例 = 2 / 3 = 0.66
配置解释:在3秒内,如果请求个数>=2 && 慢调用的比例>0.5,则接下来5秒内熔断
熔断时不接受新的请求
熔断结束后,若接下来的一个请求响应时间<200ms, 结束熔断,否则再次熔断。
修改 /yase 接口
@GetMapping("/yase")
public String yase() throws InterruptedException {
System.out.println(Thread.currentThread().getName()+"。。。。。。"+ LocalDateTime.now());
Thread.sleep(300);// 300毫秒后再返回
return "。。。。。yase";
}
重启项目后,设置 /yase 资源的降级规则

浏览器访问:http://localhost:8200/yase,前3秒内都正常相应
由于每次相应时间都超过200ms,3秒后熔断,5秒内不接受新的请求

5秒后再次访问,如果响应时间还是>200ms,再次熔断5秒
2. 异常比例

配置解释:在3秒内,如果请求个数>=2 && 异常的比例>0.5,则接下来5秒内熔断
熔断结束后,若接下来的一个请求响应成功, 结束熔断,否则再次熔断。
修改 /yase 接口
public static int count = 0;
@GetMapping("/yase")
public String yase() throws InterruptedException {
System.out.println(Thread.currentThread().getName()+"。。。。。。"+ LocalDateTime.now());
if(count<3){//前3次访问都让他报错,之后的访问正常响应
count++;
int i = 0;
int n = 2 / i;
}
return "。。。。。。yase";
}
// 增加一个接口,修改count的值,方便测试
@GetMapping("/count")
public String count() {
count =0;
return count+"";
}
重启项目后,设置 /yase 资源的降级规则

浏览器访问:http://localhost:8200/yase,每次都报错,3秒后熔断,5秒内不接受新的请求

之后再次访问,因为count值>=3,不报错,所以不再熔断

3. 异常数

配置解释:在3秒内,如果请求个数>=2 && 异常的数量>2,则接下来5秒内熔断
熔断结束后,若接下来的一个请求响应成功, 结束熔断,否则再次熔断。
演示:省略
3. 热点规则
1. 基础
新建一个带参数的方法
@GetMapping("/diaochan")
@SentinelResource("diaochan")//向Sentinel注册一个资源,资源名:diaochan,配置热点规则需要使用这个注解
public String diaochan(int id){
return "。。。。。。diaochan"+"----"+id;
}
新增 热点规则:

表示:针对 **diaochan **资源的 id 参数进行限流
规则:每秒的访问数量不能大于1
这样在浏览器中连续对 diaochan 资源进行访问,结果:

查看控制台,发现:

ParamFlowException:Sentinel中的参数限流异常,表示对指定参数限流成功
2. 友好提示
但是浏览器展示错误信息非常不合适,对代码进行修改
@GetMapping("/diaochan")
@SentinelResource(value = "diaochan", blockHandler = "err")//声明一个方法,处理异常
public String diaochan(int id){
return "。。。。。。diaochan"+"----"+id;
}
public String err(int id, BlockException e){
return "。。。。。。资源错误。。。。。。" + "-----" + id;
}
重启后,重新配置规则,再次高频率访问:

注意:blockHandler只能处理Sentinel抛出的异常,不能处理我们自己的业务代码异常
@GetMapping("/diaochan")
@SentinelResource(value = "diaochan", blockHandler = "err")//声明一个方法,处理异常
public String diaochan(int id){
int i = 1/0;// 这样的异常,blockHandler不能处理
return "。。。。。。diaochan"+"----"+id;
}
private String err(int id, BlockException e){
return "。。。。。。资源错误。。。。。。" + "-----" + id;
}

针对这种情况,之后再说
3. 参数例外项
可以针对具体的参数值,设置阈值

配置解释:id=1的数据,QPS是100,其他的QPS还是1
连续访问id=2的数据

连续访问id=1的数据

4. 系统规则
对整个服务进行限流,在请求达到我们的服务之前就拦截住

配置规则:

配置解释:如果整个服务每秒接收的请求数量>1,就限流
阈值类型:
- Load(仅对 Linux/Unix-like 机器生效):当系统 load1 超过阈值,且系统当前的并发线程数超过系统容量时才会触发系统保护。系统容量由系统的 maxQps * minRt 计算得出。设定参考值一般是 CPU cores * 2.5。
- CPU usage(1.5.0+ 版本):当系统 CPU 使用率超过阈值即触发系统保护(取值范围 0.0-1.0)。
- RT:当单台机器上所有入口流量的平均 RT 达到阈值即触发系统保护,单位是毫秒。
- 线程数:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护。
- 入口 QPS:当单台机器上所有入口流量的 QPS 达到阈值即触发系统保护。
5. SentinelResource

1. 两种限流方式
- url限流:限流后返回Sentinel的默认提示
- 资源名限流:限流后可以指定返回结果
问题:
- url限流:可以自定义提示
- 需要实现 BlockExceptionHandler 接口
- 资源名限流:可以自定义提示,需要SentinelResource注解
2. SentinelResource
缺点:代码入侵很高,而且每个方法都这样干,代码太膨胀
1. 统一blockHandler
新建类:
public class CustomHandle {
public static String test_err(int i, BlockException e){
return "限流啦。。。。。。";
}
//可以定义多个方法
}
修改 /diaochan 方法
@GetMapping("/diaochan")
@SentinelResource(value = "diaochan",
blockHandlerClass = CustomHandle.class,//指定类
blockHandler = "test_err")//指定方法,方法的参数数量要对应
public String diaochan(int id){
return "。。。。。。diaochan"+"----"+id;
}
演示:省略
2. fallback
其实就是服务降级,这个可以处理我们程序的业务异常
新建 CustomFallBack 类:
public class CustomFallBack {
public static String teste_fallBack(int i, Throwable e){
return "出错啦!!!问题是:"+e.getMessage();
}
}
修改接口
@GetMapping("/diaochan")
@SentinelResource(value = "diaochan",
blockHandlerClass = CustomHandle.class, blockHandler = "test_err",
fallbackClass = CustomFallBack.class, fallback = "teste_fallBack")
public String diaochan(int id){
if(id == 2){
int i = 1/0;
}
return "。。。。。。diaochan"+"----"+id;
}
结果:

3. exceptionsToIgnore属性
指定的异常不走fallback
@GetMapping("/zhenji")
@SentinelResource(value = "zhenji",fallbackClass = CustomFallBack.class,fallback = "teste_fallBack",
exceptionsToIgnore = {ArithmeticException.class})
public String zhenji(){
int i = 2/0;
return "。。。。。。zhenji";
}

3. BlockExceptionHandler
新建立一个类,实现 BlockExceptionHandler 接口
@Component
public class MyBlockExceptionHandler implements BlockExceptionHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception {
response.setHeader("Content-Type","application/json;charset=UTF-8");
String message = "{\"code\":444,\"msg\":\"访问人数过多\"}";
response.getWriter().write(message);
}
}
对 /daji 接口设置流控规则

结果:




浙公网安备 33010602011771号