本文已参与「新人创作礼」活动,一起开启掘金创作之路。
1. Sentinel简介
Sentinel被称为分布式系统的流量防卫兵,是阿里开源流控框架,从服务限流、降级、熔断等多个维度保护服务,Sentinel提供了简洁易用的控制台,可以看到接入应用的秒级数据,并且可以在控制台设置一些规则保护应用,它比Hystrix支持的范围广,如Spring Cloud、Dubbo、gRPC都可以整合。
资源是Sentinel最关键的概念,遵循Sentinel API的开发规范定义资源,就能将应用保护起来。
而规则可以通过控制面板配置,也可以和资源联合起来,规则可以在控制台修改并且即时生效。
名词解释:
-
限流:不能让流量一窝蜂的进来,否则可能会冲垮系统,需要限载流量,一般采用排队的方式有序进行
- 对应生活中的小例子:比如在一个景区,一般是不会让所有人在同一时间进去的,会限制人数,排队进入,让景区内人数在一个可控范围,因为景区的基础设施服务不了那么多人。
-
降级:即使在系统出故障的情况下,也要尽可能的提供服务,在可用和不可用之间找一个平衡点,比如返回友好提示等。
- 例如现在稍有规模的电商系统,为了给用户提供个性化服务,一般都有推荐系统,假设现在推荐系统宕机了,不应该在推荐商品一栏不给用户展示商品,反而可以降低一点要求,保证给用户看到的是友好界面,给用户返回一些准备好的静态数据。
-
熔断:直接拒绝访问,然后返回友好提示,一般是根据请求失败率或请求响应时间做熔断。
- 熔断好比家里的保险盒,当线路过热时,就会跳闸,以免烧坏电路。
2. Sentinel和同类产品对比
Sentinel、Hystrix、Resilience4j的异同
基础特性 | Sentinel | Hystrix | Resilience4j |
---|---|---|---|
限流 | QPS、线程数、调用关系 | 有限的支持 | Rate LImiter |
注解的支持 | 支持 | 支持 | 支持 |
动态规则配置 | 支持多种数据源 | 支持多种数据源 | 有限支持 |
实时统计信息 | 滑动窗口 | 滑动窗口 | Ring Bit Buffer |
熔断降级策略 | 平均响应时间、异常比例、异常数 | 异常比例 | 平均响应时间、异常比例 |
控制台 | 可配置各种规则,接口调用的秒级信息,机器发现等 | 简单监控 | 不提供控制台,可对接其它监控平台 |
流量整形 | 支持预热、排队模式 | 不支持 | 简单的Rate Limiter模式 |
系统自适应限流 | 支持 | 不支持 | 不支持 |
扩展性 | 多个扩展点 | 插件的形式 | 接口的形式 |
常用适配框架 | Servlet、Spring Cloud、Dubbo、gRPC等 | Servlet、Spring Cloud、Netflix | Spring Boot、Spring Cloud |
3. 下载和运行
github地址

可以直接下载jar包运行jar包,也可以下载源码编译运行
因为它是springboot项目,下载jar包后直接运行jar包即可
java -jar sentinel-dashboard-1.8.3.jar
默认端口8080,如果需要修改,可以增加-Dserver.port参数,启动命令修改为java -jar -Dserver.port=9000 sentinel-dashboard-1.8.3.jar
,即可将程序端口改为9000
默认账号sentinel,默认密码sentinel,登录后页面是空白的,是因为sentinel采用懒加载的方式,只有证正使用它,功能才会展示出来
4. 项目集成Sentinel
4.1 创建提供者服务
pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 热部署 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<!-- 健康监控 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- 服务注册/服务发现需要引入的 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- nacos配置中心依赖 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!-- sentinel组件依赖 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
</dependencies>
bootstrap.yml
server:
port: 8082 #程序端口号
spring:
application:
name: provider # 应用名称
cloud:
sentinel:
transport:
port: 8719 # 启动HTTP Server,并且该服务与Sentinel仪表进行交互,是Sentinel仪表盘可以控制应用,如被占用,则从8719依次+1扫描
dashboard: 127.0.0.1:8080 # 指定仪表盘地址
nacos:
discovery:
server-addr: 127.0.0.1:8848 # nacos服务注册、发现地址
config:
server-addr: 127.0.0.1:8848 # nacos配置中心地址
file-extension: yml # 指定配置内容的数据格式
management:
endpoints:
web:
exposure:
include: '*' # 公开所有端点
写一个简单的Controller给消费者调用
@RestController
public class TestController {
@GetMapping("/test")
public String test(){
return "provider test方法" + RandomUtils.nextInt(0,100);
}
}
4.2 创建消费端服务
pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 热部署 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<!-- 健康监控 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- 服务注册/服务发现需要引入的 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- nacos配置中心依赖 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!-- sentinel组件依赖 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
</dependencies>
bootstrap.yml
server:
port: 8081 #程序端口号
spring:
application:
name: consumer # 应用名称
cloud:
sentinel:
transport:
port: 8719 # 启动HTTP Server,并且该服务与Sentinel仪表进行交互,是Sentinel仪表盘可以控制应用,如被占用,则从8719依次+1扫描
dashboard: 127.0.0.1:8080 # 指定仪表盘地址
nacos:
discovery:
server-addr: 127.0.0.1:8848 # nacos服务注册、发现地址
config:
server-addr: 127.0.0.1:8848 # nacos配置中心地址
file-extension: yml # 指定配置内容的数据格式
management:
endpoints:
web:
exposure:
include: '*' # 公开所有端点
新建一个controller去调用服务提供者
@RestController
public class TestController {
// 这里的服务地址填写注册到Nacos的应用名称
private final String SERVER_URL = "http://provider";
@Resource
private RestTemplate restTemplate;
/**
* 调用提供者test接口
* @return
*/
@GetMapping("/test")
public String test(){
// 调用提供者的 /test 接口
return restTemplate.getForObject(SERVER_URL+"/test",String.class);
}
/**
* sentinel测试组件
* @return
*/
@GetMapping("/sentinelTest")
public String sentinelTest(){
return "TestController#sentinelTest " + RandomUtils.nextInt(0,10000);
}
}
4.3 使用RestTemplate+Ribbon远程调用
增加config配置类,实例化RestTemplate对象
@Configuration
public class GenericConfiguration {
@LoadBalanced//标记此注解后,RestTemplate就具有了客户端负载均衡的能力
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
在没有加任何Sentinel规则的情况下接口正常调用
C:\Users\81950>curl http://localhost:8081/test
provider test方法13
C:\Users\81950>curl http://localhost:8081/test
provider test方法47
C:\Users\81950>curl http://localhost:8081/sentinelTest
TestController#sentinelTest 8873
C:\Users\81950>curl http://localhost:8081/sentinelTest
TestController#sentinelTest 3357
4.4 使用Sentinel常用规则
调用过接口之后,Sentinel控制台出现了很多共功能
在使用Sentinel功能前,需要准备一个模拟发起请求的工具,测试限流这些规则效果用curl手动发起请求不太现实,这里将用到JMeter发起大规模请求。
下载地址
jmeter.apache.org/download_jm…
4.4.1 流控规则
1、流控规则主要是设置QPS或线程数等参数保护应用,针对某个资源的设置。添加规则前需要先调用接口。
一些流控关键词的含义:
资源名:资源名称,唯一即可
针对来源:对调用者进行限流,填写应用名称(一般是spring.application.name的值),指定对哪个服务进行限流(默认default是全部限制)
阈值类型
- QPS:每秒能接受的请求数
- 线程数:能使用的业务线程数
流控模式
- 直接:达到条件后,直接执行某个流控效果
- 关联:如果关联资源达到条件,就限流自身
- 链路:记录从入口资源的流量,达到条件也只限流入口资源
流控效果
- 快速失败:达到条件后,直接返回失败的结果
- Warm Up:预热,给一个缓冲时间,初始值是阈值/codeFactor(默认为3),慢慢达到设置的阈值
- 排队等待:让系统匀速处理请求,而不是一次处理很多,过一会则处于空闲状态。
(1)QPS——直接——快速失败
QPS(Query Per Second)是指每秒可处理的请求数。
流控规则设置
在Sentinel控制台选择“簇点链路”,选择“列表视图”,资源名为/sentinelTest进行流控
阀值类型为QPS,单机阈值为1
即增加了一条直接-快速失败的流控规则
测试
使用JMeter设置线程数为10,发起请求
从结果可以看出,超过限制QPS超过阈值1就被接管了,直接返回失败结果Blocked by Sentinel (flow limiting)
(2)QPS——直接——Warm Up
Warm Up是预热,即初始i请求QPS等于阈值/coldFactor,cold-Factor的默认值为3,经过预热时长1秒后单机阈值为100
流控规则设置
编辑流控规则,流控效果选择Warm Up,预热时长1秒,单机阈值100
测试
因为Jmeter线程数10,只循环依次,可能执行完还不到1秒,所以把循环次数改为10,对比1秒前和1秒后的效果。
虽然预热前1秒几乎请求都是失败的,但过了1秒后大部分都是请求成功的,流量缓慢增加,给冷系统一个缓冲时间,避免一下子把系统给压垮。
(3)QPS——直接——排队等待
让请求以均匀的速度通过,如果请求超过了阈值就等待,如果等待超时就返回失败
编辑流控规则,单机阈值依旧为1,超时时间15000毫秒,JMeter循环执行次数1
QPS设置为1,在调用过程中几乎是1秒发一个请求,超时时间如果短一些,一定会有很多失败。
(4)QPS——关联——快速失败
如果访问关联接口B到达了阈值,就让接口A返回失败,这种规则适用于资源之间,具有资源争抢或者依赖关系。
增加一个接口sentinelTestB
/**
* sentinel测试组件B
* @return
*/
@GetMapping("/sentinelTestB")
public String sentinelTestB(){
// 调用提供者的 /test 接口
return "TestController#sentinelTestB " + RandomUtils.nextInt(0,10000);
}
流控规则设置
修改流控规则,主要改的就是流控效果:关联
测试
把JMeter循环次数设置为永远,JMeter请求sentinelTestB接口,模拟一直超过阈值,然后使用curl命令请求sentinelTest接口,结果如下
C:\Users\81950>curl http://localhost:8081/sentinelTest
Blocked by Sentinel (flow limiting)
关联资源B请求达到阈值,而请求sentinelTest接口直接被限流
(5)线程数——直接
限制处理请求的业务线程数,达到阈值就会限流
流控规则设置
阈值类型选择“并发线程数”,单机阈值1,流控模式“直接”。
测试
JMeter线程数改为10,Ramp-Up时间为0.5,循环次数为10
从结果中看出很多请求被限流
因为设置的阈值很小,所以明显业务线程已经处理不过来了,业务线程正在处理任务的时候再来的请求就被限流了。
4.4.2 熔断规则
Sentinel1.8.0 及以上版本主要有三个策略:慢调用比例,异常比例,异常数。
(1)慢调用比例
选择以慢调用比例作为阈值,需要设置允许的慢调用 RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用。当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断。
熔断规则设置
同样在簇点链路选择列表视图,对资源/sentinelTest,选择熔断,熔断策略为慢比例调用,最大RT为500,比例阈值1,熔断时长10秒,最小请求数5,统计时长1000ms
熔断条件
在1000毫秒,也就是1秒内,如果发送到/sentinelTest的请求数数量大于5,并且在这些请求中,所有请求的响应时长(因为比例与阈值为1,所以是所有的请求响应时长)都大于500毫秒,也就是都大于0.5秒的时候,进入熔断状态。
模拟耗时操作
修改代码,让线程睡眠1秒
/**
* sentinel测试组件
* @return
*/
@GetMapping("/sentinelTest")
public String sentinelTest() throws InterruptedException {
TimeUnit.SECONDS.sleep(1);
return "TestController#sentinelTest " + RandomUtils.nextInt(0,10000);
}
测试
JMeter设置线程数10,循环次数永远
10个线程,在一秒的时间内全部发送完, 又因为接口响应时长为暂停1秒,所以响应一个请求的时长都大于一秒,所以在统计时长1000毫秒也就是1秒内会进入熔断状态
使用curl测试接口,此时已经进入熔断状态
C:\Users\81950>curl http://localhost:8081/sentinelTest
Blocked by Sentinel (flow limiting)
停止JMeter测试,超过熔断时长10m后再使用curl测试,接口正常访问
C:\Users\81950>curl http://localhost:8081/sentinelTest
TestController#sentinelTest 5483
(2)异常比例
当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%。
熔断规则设置
在1000毫秒(1秒)中当最少接收5个请求得情况下,错误率达到20%,接下来10秒开启熔断。
为了测试出效果,将接口故意使程序报错
/**
* sentinel测试组件
* @return
*/
@GetMapping("/sentinelTest")
public String sentinelTest() throws InterruptedException {
// TimeUnit.SECONDS.sleep(1);
int i = 1/0; // 除数不能为0,此处必然会报错
return "TestController#sentinelTest " + RandomUtils.nextInt(0,10000);
}
测试
JMeter设置1个线程1秒内执行10次,查看结果
当异常总数比例超过设定的比例时,进入熔断状态,要等过了时间窗口期10秒才能恢复
(3)异常数
当单位统计时长内的异常数目超过阈值之后会自动进行熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。
熔断规则设置
熔断策略为异常数,异常数2,熔断时长5秒,最小请求数5,表示如果1秒内异常数达到2个,则该接口再接下来的5秒钟进入熔断状态
测试
JMeter使用1个线程1秒内执行10次,结果如下
异常数到达两个之后进入熔断状态,要过了熔断时长5秒后才能恢复。
4.4.3 系统规则
4.4.1流控规则和4.4.2降级规则是针对某个资源而言的,而系统规则是针对整个应用的,当前服务都会应用这个系统规则,相对来说就更加的粗粒度了,属于应用级别的入口流量控制。
(1)LOAD
负载,当系统负载超过设定值,且并发线程数超过预估系统容量就会触发保护机制。
此规则仅对Linux机器生效,因此将项目修改项目配置文件后吗,打包放到服务器上运行。
系统规则设置
在系统规则界面,新增系统保护规则,阈值类型为LOAD,阈值为1
测试
使用JMeter使用100个线程调用接口,循环次数设置为永远
(2)RT
整个应用上所有资源平均的响应时间,而不是某个固定资源
(3)线程数
设定真个应用所能使用的业务线程数阈值,而不是固定某个资源
(4)入口QPS
整个应用所使用的是每秒处理的请求数,而不是固定某个资源
(5)CPU使用率
应用占用CPU的百分比,同样仅对Linux机器生效
都是超过设定值则被阻断,不再演示
4.4.4 授权规则
授权规则是根据调用方判断资源的请求是否被允许,Sentinel控制台提供了黑白名单的授权类型,如果配置了白名单,表示只允许白名单的应用调用该资源时通过,如果配置了黑名单,表示黑名单的应用调用该资源不通过,其余的均能够通过。
授权规则的配置需要服务提供者配置授权规则
创建CustomRequestOriginParser类来实现RequestOriginParser接口,用于获取参数,然后将返回结果值交给Sentinel流控匹配处理
@Component
public class CustomRequestOriginParser implements RequestOriginParser {
@Override
public String parseOrigin(HttpServletRequest httpServletRequest) {
// 区分来源:本质通过request域获取来源标识
String origin = httpServletRequest.getParameter("origin");
// 授权规则必须判断
if(StringUtils.isEmpty(origin)){
throw new RuntimeException("origin不能为空");
}
// 最后返回origin交给sentinel流控匹配处理
return origin;
}
}
新增授权规则
对/sentinelTestC资源新增授权,流控应用填写app,授权类型黑名单
分别调用接口
C:\Users\81950>curl http://127.0.0.1:8081/sentinelTestC?origin=app
Blocked by Sentinel (flow limiting)
C:\Users\81950>curl http://127.0.0.1:8081/sentinelTestC?origin=pc
TestController#sentinelTestC 9667
配置了黑名单,表示黑名单的应用调用该资源不通过,其余的均能够通过
给提供者增加CustomRequestOriginParser类,并通过远程调用访问test接口,对provider的/test资源新增授权规则
修改consumer的/test接口
/**
* 调用提供者test接口
* @return
*/
@GetMapping("/test")
public String test(){
// 调用提供者的 /test 接口
return restTemplate.getForObject(SERVER_URL+"/test?origin=consumer",String.class);
}
测试消费方的test接口,即携带origin远程调用提供方的test接口,成功
C:\Users\81950>curl http://127.0.0.1:8081/test
provider test方法11
4.5 使用@SentinelResource注解
@SentinelResource注解根据实际情况实现定制化功能,对应用的保护更加细粒度。
之前到达一定阈值时,sentinel给的提示时Blocked by Sentinel (flow limiting),这显然是不友好的,所以需要自定义错误页面,又或者只针对某个参数限流等,实现更精细化的控制。
4.5.1 blockHandler属性——负责响应控制面板配置
blockHandler主要是针对达到控制面板的限制条件做一个自定义的“兜底”操作,而不是返回默认的Blocked by Sentinel (flow limiting)。
添加一个接口/blockHandlerTest,资源名称为blockHandlerTest,如果违反Sentinel控制台的规则,就会自动进入blockHandlerTestHandler
@RestController
public class HandlerTestController {
@GetMapping("/blockHandlerTest")
// 资源名称为blockHandlerTest 违反规则后的兜底方法是blockHandlerTestHandler
@SentinelResource(value = "blockHandlerTest", blockHandler = "blockHandlerTestHandler")
public String blockHandlerTest(String params) {
return "HandlerTestController#blockHandlerTest " + RandomUtils.nextInt(0, 1000);
}
/**
* 接口blockHandlerTest的兜底方法
*
* @param params
* @param blockException
* @return
*/
public String blockHandlerTestHandler(String params, BlockException blockException) {
return "HandlerTestController#blockHandlerTestHandler "
+ RandomUtils.nextInt(1, 1000)
+ " "
+ blockException.getMessage();
}
}
blockHandler指定的兜底方法的返回值类型要和原方法一致,并且该方法除了原有的参数(方法签名),还要新增BlockExceptionca参数
新增流控规则
测试
JMeter使用10个线程循环10次
从结果可与i看到,发起第二个请求时,QPS已经达到1个了,然后进入到自定义的blockHandler方法
4.5.2 热点规则
热点就是在一定时期内访问特别频繁,如果访问某个资源很频繁,有可能只是某些参数访问量很大。
Sentinel不仅支持以资源为粒度的限制,还可以更细化,针对资源下的参数进行限制,其实就是对这个接口的请求参数设置限定。
@RestController
public class HotspotTestController {
/**
* 热点参数测试接口
*
* @return
*/
@GetMapping("/testHotKeyA")
@SentinelResource(value = "testHotKeyA", blockHandler = "blockTestHotKeyA")
public String testHotKeyA(
@RequestParam(value = "orderId", required = false) String orderId,
@RequestParam(value = "userId", required = false) String userId) {
return "HotspotTestController#testHotKeyA " + RandomUtils.nextInt(0, 1000);
}
/**
* 热点参数测试接口testHotKeyA的兜底方法
* @param orderId
* @param userId
* @param blockException
* @return
*/
public String blockTestHotKeyA(String orderId, String userId, BlockException blockException) {
return "HotspotTestController#blockTestHotKeyA "
+ RandomUtils.nextInt(0, 1000)
+ " "
+ blockException.getMessage();
}
}
新增热点规则
限流模式只能是QPS,参数索引为0,代表是orderId参数,单机阈值为1,统计窗口时长为5秒,也就是在5秒内统计到QPS大于1,接口就会被阻断,进入到自定义的blockTestHotKeyA方法,
测试
JMeter使用10个线程循环10次测试testHotKeyA接口,需要加上orderId参数
从结果可以看出,发起第2个请求时,5秒内统计到的QPS已经大于1了,所以进入自定义的blockTestHotKeyA方法
另外还可以对参数的值单独设置阈值,也可以添加多个值
JMeter中orderId传值111,统计时间5秒内并不会达到限流阀值500,也就不会进入blockHandler方法
4.5.3 fallback属性——负责业务异常
fallback属性的方法是对业务异常的“兜底”,如果业务代码报了异常(除了exceptionToIgnore属性排除掉的异常类型),就会进入fallback属性配置的方法。
增加fallbackTest接口代码,定义fallback的方法fallbackHandler
@RestController
public class FallbackTestController {
/**
* 测试fallback的方法
* @param params
* @return
*/
@GetMapping("/fallbackTest")
// 资源名称为fallbackTest,异常后的兜底方法为fallbackHandler
@SentinelResource(value = "fallbackTest",fallback = "fallbackHandler")
public String fallbackTest(String params) {
int res = 1 / 0; // 此处模拟报错
return "FallbackTestController#fallbackTest "
+ RandomUtils.nextInt(0, 1000);
}
/**
* 接口fallbackTest的兜底方法
* @param params
* @return
*/
public String fallbackHandler(String params){
return "FallbackTestController#fallbackHandler "
+ RandomUtils.nextInt(0, 1000);
}
}
接口测试必然时报错的,因为使用了fallback属性设置了兜底方法,所以一报错就进入fallbackHandlerfa方法
C:\Users\81950>curl http://127.0.0.1:8081/fallbackTest
FallbackTestController#fallbackHandler 704
4.5.4 fallback+blockHandler
增加sentinelUnionTest接口代码,资源名为sentinelUnionTest,并且调用该接口是必然报错的
@RestController
public class UnionTestController {
/**
* sentinel组件测试方法fallback和blockHandler联合
* @return
*/
@GetMapping("/sentinelUnionTest")
@SentinelResource(
value = "sentinelUnionTest",
blockHandler = "sentinelUnionTestBlockHandler",
fallback = "sentinelUnionTestFallback")
public String sentinelUnionTest() {
int res = 1 / 0; // 此处必然报错
return "UnionTestController#sentinelUnionTest "
+ RandomUtils.nextInt(0, 1000);
}
/**
* sentinelUnionTest的兜底方法
* @return
*/
public String sentinelUnionTestFallback() {
return "UnionTestController#sentinelUnionTestFallback "
+ RandomUtils.nextInt(0, 1000);
}
/**
* sentinelUnionTest的兜底方法
* @param blockException
* @return
*/
public String sentinelUnionTestBlockHandler(BlockException blockException) {
return "UnionTestController#sentinelUnionTestBlockHandler "
+ RandomUtils.nextInt(0, 1000)
+ " "
+ blockException.getMessage();
}
}
控制台新增sentinelUnionTest资源的流控规则
JMeter使用10个线程循环10次调用/sentinelUnionTest接口
从结果可以看出,发出第一个请求时,并没有达到QPS阈值的条件,方法内部报错后,进入了fallback属性定义的兜底方法,发送第二个请求时已经达到了QPS阈值条件,进入了blockHandler属性定义的方法。
4.5.5 exceptionsToIgnore忽略异常
fallback可以针对所有的类型的异常,@SentinelResource注解的exceptionsToIgnore属性表示忽略异常,不会纳入异常统计,也就会跳过fallback属性。
@RestController
public class ExceptionsTestController {
/**
* 测试exceptionsToIgnore的方法
* @return
*/
@GetMapping("/exceptionsToIgnoreTest")
@SentinelResource(
value = "exceptionsToIgnoreTest", // 资源名称为exceptionsToIgnoreTest
fallback = "exceptionsToIgnoreTestFallback", // 异常后的兜底方法为exceptionsToIgnoreTestFallback
exceptionsToIgnore = {ArithmeticException.class} // 忽略ArithmeticException异常
)
public String exceptionsToIgnoreTest() {
int res = 1 / 0; // 此处模拟报错
return "ExceptionsTestController#exceptionsToIgnoreTest "
+ RandomUtils.nextInt(0, 1000);
}
/**
* 接口exceptionsToIgnoreTest的兜底方法
* @return
*/
public String exceptionsToIgnoreTestFallback() {
return "ExceptionsTestController#exceptionsToIgnoreTestFallback "
+ RandomUtils.nextInt(0, 1000);
}
}
接口测试
1/0必然会抛出ArithmeticException算数异常,方法内异常本应该进入fallback定义的方法,因为设置了exceptionsToIgnores属性,忽略了ArithmeticException异常,所以异常照常返回。
4.5.6 代码优化
4.5.4中的代码中,fallback和blockHandler的处理方法都写在同一个类里,一来这样不符合程序单一的原则,毕竟controller层有很多之外的逻辑,二来别的类也不好复用其方法。
新需求:把具体的处理函数单独放在一个类
@SentinelResource注解还有两个属性,分别是blockHandlerClass
和fallbackClass
,通过源码查看其注释
优化代码
为fallback建立单独的类ExceptionHandler
public class ExceptionHandler {
/**
* 接口sentinelUnionTest的兜底方法,放到单独类后必须时static
* @return
*/
public static String sentinelUnionTestFallback(){
return "单独类ExceptionHandler#sentinelUnionTestFallback "
+ RandomUtils.nextInt(0,1000);
}
}
为blockHandler建立单独的类BlockHandler
public class BlockHandler {
/**
* sentinelUnionTest的兜底方法,放到单独类后必须时static
* @param blockException
* @return
*/
public static String sentinelUnionTestBlockHandler(BlockException blockException) {
return "单独类BlockHandler#sentinelUnionTestBlockHandler "
+ RandomUtils.nextInt(0, 1000)
+ " "
+ blockException.getMessage();
}
}
修改UnionTestController中的代码,指定fallback和blockHandler的类
@RestController
public class UnionTestOptimizeController {
/**
* sentinel组件测试方法fallback和blockHandler联合
* 指定fallback和blockHandler的类
* @return
*/
@GetMapping("/sentinelUnionTestOptimize")
@SentinelResource(
value = "sentinelUnionTestOptimize",
blockHandler = "sentinelUnionTestBlockHandler",
blockHandlerClass = BlockHandler.class,
fallback = "sentinelUnionTestFallback",
fallbackClass = ExceptionHandler.class
)
public String sentinelUnionTest() {
int res = 1 / 0; // 此处必然报错
return "UnionTestController#sentinelUnionTest "
+ RandomUtils.nextInt(0, 1000);
}
}
设置sentinelUnionTestOptimize资源流控规则,QPS为1,使用JMeter使用10个线程测试接口
结果与4.5.4中是一致的,在发出第一个请求时,没有达到QPS阈值的条件,方法内部报错,进入了fallback定义的方法,发出第二个请求的时候达到了QPS的阈值条件,直接进入blockHandler定义的方法,证明把fallback和blockHandler的函数移到单独类中是可行的。