重学SpringCloud系列八之分布式系统流量卫兵sentinel
Posted 大忽悠爱忽悠
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了重学SpringCloud系列八之分布式系统流量卫兵sentinel相关的知识,希望对你有一定的参考价值。
重学SpringCloud系列八之分布式系统流量卫兵sentinel
- sentinel简介与安装
- 客户端集成与实时监控
- 实战流控规则-QPS限流
- 实战流控规则-线程数限流
- 实战流控规则-关联限流
- 实战流控规则-链路限流
- 实战流控效果-WarmUp
- 实战流控效果-匀速排队
- BlockException处理
- 实战熔断降级-RT
- 实战熔断降级-异常数与比例
- DegradeException处理
- 注解与异常的归纳总结
- Feign降级及异常传递拦截
- 动态规则nacos集中存储
- 热点参数限流
- 系统自适应限流
sentinel简介与安装
一、Sentinel 是什么?
随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。
Sentinel 分为两个部分:
- 核心库(Java 客户端)不依赖任何框架/库,能够运行于所有 Java 运行时环境,同时对 Dubbo / Spring Cloud 等框架也有较好的支持。在一定程度上可以将Sentinel Java客户端理解为“流量防火墙”
- 控制台(Dashboard)基于 Spring Boot 开发,打包后可以直接运行,不需要额外的 Tomcat 等应用容器。Sentinel控制台不仅能展示服务流控、熔断降级相关的数据,还可以通过配置的方式动态的为Sentinel客户端下发流量控制的指令。
二、单机版下载安装
Sentinel Dashboard作为一个监控控制台,只是作为信息收集展示和命令发送端来使用。它的运行并不会真正的影响业务服务,通常不需要集群部署。
下载
我们需要下载并安装的是DashBoard控制台,下载地址:https://github.com/alibaba/Sentinel/releases。下载如下图所示的sentinel-dashboard-x.y.z.jar
启动
注意:启动 Sentinel 控制台需要 JDK 版本为 1.8 及以上版本。
使用如下命令启动控制台:
nohup java -Dserver.port=8774 \\
-Dcsp.sentinel.heartbeat.client.ip=192.168.161.3 \\
-Dproject.name=sentinel-dashboard -jar \\
sentinel-dashboard-1.7.2.jar &
-Dserver.port=8080
用于指定 Sentinel 控制台端口为8774
。默认是8080。我们给它改成不常用的端口。-Dcsp.sentinel.heartbeat.client.ip=192.168.161.3
控制台部署的地址,指定控制台后客户端会自动向该地址发送心跳包。 (多网卡环境下如果不做这个配置,会报出连接超时的异常)-Dproject.name=sentinel-dashboard
指定Sentinel控制台程序的名称
从 Sentinel 1.6.0 起,Sentinel 控制台引入基本的登录功能,默认用户名和密码都是sentinel
。当然也可以通过JVM参数的方式进行修改。
-Dsentinel.dashboard.auth.username=sentinel
用于指定控制台的登录用户名为sentinel
;-Dsentinel.dashboard.auth.password=123456
用于指定控制台的登录密码为123456
;如果省略这两个参数,默认用户和密码均为sentinel
;-Dserver.servlet.session.timeout=7200
用于指定 Spring Boot 服务端 session 的过期时间,如7200
表示 7200 秒;60m
表示 60 分钟,默认为 30 分钟;
Sentinel本身就是一个Spring Boot应用,所以修改Jar包内部的application.properties文件也是可以修改配置的。
开放防火墙端口
如果是部署在linux机器上,如CentOS7需要开放防火墙端口:
firewall-cmd --zone=public --add-port=8774/tcp --permanent
firewall-cmd --reload
登录
访问:http://192.168.161.3:8774
暂时空空如也
客户端集成与实时监控
当前客户端是基于以下内容之上:
- 正确集成了OpenFeign(Ribbon)及其配置
- 正确集成了nacos及其配置
可以依据自己的项目,决定是否需要引入上面的框架
二、微服务集成Sentinel客户端
通过maven坐标在微服务模块aservice-rbac、aservice-sms中加入sentinel客户端
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
在项目的配置中加上sentinel配置,因为我是用了nacos,所以去nacos修改配置文件。如果你的服务没有使用配置中心,在application.yml里面配置就可以了。
spring:
cloud:
sentinel:
transport:
#配置Sentinel dashboard地址
port: 8719
#Sentinel会启动一个http server和dashboard进行通信,而这个server默认占用端口是9719
#假如8719端口被占用,会自动+1,直到找到没有被占用的端口为止
dashboard: 192.168.161.3:8774
只需要完成上述的配置,代码不需要有任何的调整,我们就可以通过实时监控查看服务内的流量QPS以及平均响应时长等信息。
需要注意的是只有服务接口被访问的情况下,在sentinel里面才可以看到监控信息。
下节,我们在此基础之上为大家介绍sentinel的流量控制!
实战流控规则-QPS限流
一、如何添加流控规则
在菜单左侧的“簇点链路”和流控规则都可以针对“服务接口”添加流控规则
- 当我们的服务接口资源被访问的时候,就会出现在“簇点链路”列表中,我们可以针对该服务接口资源配置流程控制规则。在“簇点链路”还可以配置降级规则、热点以及授权(后文章会讲到)
- 在流控规则页面也有“新增流控规则”按钮,添加完成之后的流控规则,出现在流控规则页面列表中。
QPS流控
点击“新增流控规则”按钮之后,弹出如下的配置页面。本节以QPS限流为例为大家讲解流控规则的配置
- 资源名称:表示我们针对哪个接口资源进行流控规则配置,如:“/sysuser/pwd/reset”
- 针对来源:表示针对哪一个服务访问当前接口资源的时候进行限流,default表示不区分访问来源。如填写服务名称:aservice-xxxx,表示aservice-xxxx访问前接口资源的时候进行限流,其他服务访问该接口资源的时候不限流。
- 阈值类型/单机阈值:QPS,每秒钟请求数量。上图配置表示每秒钟超过1次请求的时候进行限流。
- 流控模式:直接,当达到限流标准时就直接限流
- 流控效果:快速失败。很简单的说就是达到限流标准后,请求就被拦截,直接失败。(HTTP状态码:429 too many request)
- 是否集群:默认情况下我们的限流策略都是针对单个服务的,sentinel提供了集群限流的功能。笔者个人意见是:除非你的微服务规模特别大,一般不要使用集群模式。集群模式需要各节点与token server交互才可以,会增加网络交互次数,一定程度上会拖慢你的服务响应时间。
上面的限流规则用一句话说:对于任何来源的请求,当超过每秒1次的标准之后就直接限流,访问失败抛出异常(BlockException)!
其他的限流配置我们后面文章再为大家讲解。
二、限流效果测试
使用Postman向“/sysuser/pwd/reset”发送请求,慢点点击发送(一秒一次),返回正常结果:
使用Postman向“/sysuser/pwd/reset”发送请求,快点点击发送(超过一秒一次),返回结果如下:
说明QPS限流规则生效,被限制的请求直接返回失败数据!
实战流控规则-线程数限流
一、线程数限流
- 资源名称:表示我们针对哪个接口资源进行流控规则配置,如:“/sysuser/pwd/reset”
- 针对来源:表示针对哪一个服务访问当前接口资源的时候进行限流,default表示不区分访问来源。如填写服务名称:aservice-xxxx,表示aservice-xxxx访问前接口资源的时候进行限流,其他服务访问该接口资源的时候不限流。
- 阈值类型/单机阈值:线程数。表示开启n个线程处理资源请求。
- 流控模式:直接,当所有线程都被占用时,新进来的请求就直接限流
- 流控效果:快速失败。很简单的说就是达到限流标准后,请求就被拦截,直接失败。(HTTP状态码:429 too many request)
上面的限流规则用一句话说:对于任何来源的请求,aservice-rbac服务端“/sysuser/pwd/reset”资源接口的2个线程都被占用的时候,其他访问失败!
二、流控效果测试
参考Hystrix章节的《Jemeter模拟触发服务熔断》创建Jemeter接口测试用例。为了更明显的触发流控规则:配置一秒钟发送30个请求。
- sentinel控制台线程数单机阈值=1,aservice-rbac服务开启1个线程处理“/sysuser/pwd/reset”资源请求,所以30 * 5/6的请求被限流。
- sentinel控制台线程数单机阈值=2,aservice-rbac服务开启2个线程处理“/sysuser/pwd/reset”资源请求,所以30 * 4/6的请求被限流。
- sentinel控制台线程数单机阈值=3,aservice-rbac服务开启3个线程处理“/sysuser/pwd/reset”资源请求,所以30 * 3/6的请求被限流。
下图中绿色表示请求成功,红色表示请求失败(被限流)!
我的测试结果比较圆满,很景精确的解释了线程数与限流请求数之间的关系。但是在实际生产环境下,环境、网络等原因流控结果不一定是如此精确的和线程数成比例的。
实战流控规则-关联限流
一、关联限流
关联限流:针对B接口配置关联限流规则,当B接口配置关联限流规则达到标准时, 关联资源A接口访问被限流。
上图的配置表示:
- 对关联资源接口“/sysuser/pwd/reset”使用QPS的限流规则,每秒钟只处理一个请求。(这个规则只是一个统计标准,并不会对“/sysuser/pwd/reset”真的限流)
- 当大量的并发请求达到“/sysuser/pwd/reset”关联资源接口的限流标准的时候,“/sysrole/query”资源将被限流。流控效果是快速失败。
需要注意的是:
- 在关联限流配置中,虽然我们对关联资源“/sysuser/pwd/reset”进行了限流规则配置,但该配置对“/sysuser/pwd/reset”并不生效。
- sentinel会统计请求流量,根据流量是否触发关联资源“/sysuser/pwd/reset”的限流标准,去限制“/sysrole/query”资源。
大家注意不要把限流关系弄反了!限流规则是为了限制“资源”,而不是“关联资源”!
关联的资源如果并发访问量过大,自己先挂了,关联对象一点事没有,舍己为人
二、流控效果测试
参考Hystrix章节的《Jemeter模拟触发服务熔断》创建Jemeter接口测试用例。为了更明显的触发流控规则:配置10秒钟发送300个请求“/sysuser/pwd/reset”。之所以10秒钟是为了给我们留出去操作访问“/sysrole/query”接口的时间。
- 针对“/sysuser/pwd/reset”10秒300请求,虽然为该接口配置限流规则(1秒1次),但是访问并未失败(下图中的绿色盾牌表示访问成功,并没有被限流)
- 与此同时访问“/sysrole/query”资源接口,被限流了!
实战流控规则-链路限流
一、什么是链路限流
如上图所示
- 我们针对getUserByUserName资源进行流控规则配置,入口为:"/sysuser/info”。
- 期望实现的效果是从"/sysuser/info”访问getUserByUserName资源被限流,从“/sysorg/tree”入口访问getUserByUserName资源不被限流。
二、将一个服务层函数标记为资源
在SysuserService方法中添加@SentinelResource
注解将该函数标记为资源,成为资源之后,就可以为它添加流控规则。
Controller层方法,不用加@SentinelResource
注解,默认就被标记为资源。
资源添加完成之后,从“簇点链路”页面看,资源调用呈现树形结构。
三、增加流控规则
上面的规则配置的含义是:
- 从"/sysuser/info”访问getUserByUserName资源被限流,从其他入口访问getUserByUserName资源不被限流。
- 限流规则是:1秒钟只允许访问一次,超出之后访问失败。
四、但是
从网友使用的反馈情况来看,链路流控规则的使用情况并不是很稳定。很多网友反映以上的流控规则并不生效。笔者经过反复实验,我是用的sentinel 1.7.2版本的确是无法生效,所以这一节内容暂时放在这里!后续有发展我们再继续更新!
- github上面网友提出的issue,官方并未关闭。(如果是关闭状态,说明这个问题已经被解决了)
https://github.com/alibaba/spring-cloud-alibaba/issues/1275 - 其他网友反应的问题。
https://coding.imooc.com/learn/questiondetail/159466.html
实战流控效果-WarmUp
之前的章节主要为大家介绍流控规则的配置,其中流控规则中的流控效果配置有三种
- 快速失败:就是流量达到阀值或线程量占满,直接返回错误报异常
- Warm Up:在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间(本节为大家介绍)
- 匀速排队:让请求以均匀的速度通过,下一节为大家介绍
一、什么是Warm Up
Warm Up(RuleConstant.CONTROL_BEHAVIOR_WARM_UP
)方式,即预热/冷启动方式。当系统流量长期处于低水位的情况下,当流量突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮。通过"冷启动",让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮。
为什么冷系统容易被压垮?
一般在我们系统内部会有线程池,比如:数据库连接线程池。在系统较为空闲的时候,数据库连接线城池内只有少量的连接。假设突然大量的请求并发而至,数据库连接池会去创建新的连接,用来支撑高并发请求。但是这个连接创建的过程需要时间,有可能这边连接池内新连接还没创建完成,这些少量的连接支撑不住就会被压垮。
所以在类似这种场景下,Warm Up让流量缓慢爬升,从而给数据库连接池创建连接一个缓冲的时间,就显得非常有必要了!
二、如何配置Warm UP
Warm Up配置有三个要素:
- 冷启动因子coldFactoer,默认等于3
- 预热时长(配置项)
- QPS单机阈值(配置项)
举例:当预热时长=8,QPS单机阈值=3
- 当并发请求到达的时候,实际的单机阈值是:QPS单机阈值配置/coldFactoer=3/3=1,也就是每秒钟只能一个请求访问成功。
- 预热时长为8秒,实际的单机阈值在8秒钟内逐步由1 -> 2 -> 3,最终等于QPS单机阈值配置。
三、测试一下
参考Hystrix章节的《Jemeter模拟触发服务熔断》创建Jemeter接口测试用例。为了更明显的触发流控规则:配置20秒钟发送60个请求“/sysuser/pwd/reset”。
红色表示请求失败(被限流),绿色表示请求成功
大家从上面的测试结果可以看出:
- 冷启动初期,三个请求能成功一个(单机阈值=1)
- 中期,三个请求能成功2个(单机阈值=2)
- 后期WarmUp限流配置到达阈值3的时候,所有的请求都能被成功处理(单机阈值=3)
实战流控效果-匀速排队
一、什么是匀速排队
匀速排队:就是让请求以均匀的速度通过,阈值类型必须是QPS。
上图的配置表示的是:“/sysuser/pwd/reset”资源服务接口,每秒钟匀速通过2个请求。当每秒请求大于2的时候,多余的请求排队等待,等待的时间是500ms。如果500ms以内请求得不到处理,就被限流访问失败!
二、测试效果
参考Hystrix章节的《Jemeter模拟触发服务熔断》创建Jemeter接口测试用例。为了更明显的触发流控规则:配置20秒钟发送60个请求“/sysuser/pwd/reset”。
2.1.请求超时0.5秒(上文图中配置)
从上图的请求结果可以看出:60个请求20秒发完,平均一秒3个请求,我们配置的匀速通过阈值是2。所以每秒处理2个请求,另外一个请求等待之后超过超时时间0.5秒(500ms),访问失败!
2.2.将超时时间调大为5秒
刚才我们的超时时间为0.5秒,所以请求很容易就超时了。现在将超时时间修改为5秒,再次发送请求。
请求刚开始发送的时候,我们配置的匀速通过阈值是2,所以每秒处理2个请求。先发送的请求先进入排队队列,在5秒之内发送的请求几乎都被成功处理了,后来队列里面的请求积压的越来越多,导致后面不断有请求超时(超过5秒)。
BlockException处理
在前面的若干节内容中为大家介绍了sentinel流控规则的配置,只要我们正确的集成了sentinel客户端(核心库),我们几乎不需要做任何的代码开发。就可以实现应用服务限流。
当请求并发达到限流的标准之后,我们再请求资源服务接口,就会得到上文中的响应结果。原始的响应结果实际包含了三部分内容:
- HTTP状态码:429 too many request,太多的请求无法处理
- 提示信息:Blocked by Sentinel (flow limiting),请求被Sentinel的限流策略拦截
- 后台服务实际上抛出了BlockException(准确的说是FlowException继承自BlockException)
其实从笔者自身的实践来看,把上面的原始的限流响应结果交给前端做一个统一的处理,转换成友好的提示信息比如:“系统服务繁忙请稍后再试!”。是非常好的做法:统一、简单、易维护! 但是java服务端也给出了BlockException(准确的说是FlowException继承自BlockException)响应的异常拦截处理方案,我们也有必要学习一下。以备适应更多的个性化场景。
一、BlockHandler
如果我们希望请求被限流之后,给用户一些相对友好的信息,而不是Blocked by Sentinel (flow limiting);或者服务被限流之后做一些特殊的业务处理。我们需要通过SentinelResource注解的BlockHandler属性来实现。
- value:资源名称,必需项。需要注意的是:如果controller的mappingUrl是"/sysuser/pwd/reset",那么SentinelResource的value属性就不要再配置成"/sysuser/pwd/reset"。会导致异常等处理逻辑偶尔失效。
- blockHandler :当资源服务接口被限流之后,就执行该属性指定的方法。
- 对应处理 BlockException 的方法名称,可选项。
- blockHandler 方法修饰符必须是 public,返回类型需要与原方法相匹配。
- 参数类型需要和原方法相匹配并且最后加一个额外的参数,类型为 BlockException。
上面的代码+控制台资源访问限流配置(QPS=1),然后我们使用Postman快速向"/sysuser/pwd/reset"发送请求,结果如下(不再是系统默认的限流响应信息,对于前端更加友好):
二、blockHandlerClass与BlockHandler搭配适用
按照上面的使用BlockHandler进行限流之后的异常处理,blockHandler 方法和项目的实际业务处理方法在一个类中,如果我们希望把限流处理方法独立出来,需要使用到blockHandlerClass。
我们可以把通用的blockHandler 异常处理方法单独抽取到一个类中
- 方法必须是static否则无法正确被解析
- 方法参数定义必须和实际业务处理方法一致,在此基础上新增一个BlockException参数
代码如下:
public class SysuserControllerHandler
public static AjaxResponse pwdresetBlockHandler(@RequestParam Integer userId,
BlockException blockException)
return AjaxResponse.error(CustomExceptionType.SYSTEM_ERROR,
"尊敬的客户您好,系统服务繁忙,请稍后再试!");
在实际的业务处理方法上,blockHandlerClass与BlockHandler搭配适用找到限流之后的处理方法。
上面的代码+控制台资源访问限流配置(QPS=1),然后我们使用Postman快速向"/sysuser/pwd/reset"发送请求,结果如下(不再是系统默认的限流响应信息,对于前端更加友好):
三、总结
大家可以看到使用blockHandlerClass虽然可以达到实际业务处理方法与blockHandler 方法解耦目的,但是我们仍然需要几乎针对每一个函数进行限流方法的开发,因为每个方法的参数是不同的。所以这些方法都只适用于个性化配置,不是全局配置。
那么sentinel针对服务限流,有没有全局的默认的BlockHandler呢?答案是没有(2020年5月)。但是从笔者的实践来看这真的不影响什么,把最原始的限流响应结果交给前端做一个统一的处理,给出友好的提示信息比如:“系统服务繁忙请稍后再试!”。是非常好的做法:统一、简单、易维护!
实战熔断降级-RT
Sentinel熔断降级会在调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高),对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联错误。当资源被降级后,在接下来的降级时间窗口之内,对该资源的调用都自动熔断(默认行为是抛出DegradeException
)。
一共有三种熔断降级策略:
- RT平均响应时长
- 异常比例
- 异常数量
先为大家介绍第一种:RT平均响应时长降级策略。
一、RT降级策略
以下的两个条件同时满足才能触发RT服务降级。
- 一秒钟内通过5个以上的请求,全部超时
- 超时是指超过RT平均响应时长的配置值(上图红色框配置)
触发RT服务降级规则之后,在时间窗口期内(上图绿色框配置)对资源的访问将被降级,即:断路器被打开。时间窗口期之后关闭断路器。
需要注意的是:
- RT平均响应时长最大可配值为4900毫秒。当配置超过4900的时候,默认等于4900。
- 官方之所以设置RT最大值为4900,是因为当一次请求超过5秒就到了用户能忍受的极限。
- 如果确实希望修改RT可配最大值,使用
-Dcsp.sentinel.statistic.max.rt=xxx
进行配置
问题:为什么要1秒超时5次才降级?
答:如果一个服务的请求量比较小,几秒钟才有一次请求,不巧这1次请求因为瞬时网络原因失败了,进而导致服务降级,这种情况是我们不愿意看到的。服务降级仍然是主要指针对高并发情况下,导致的服务资源紧张的情况下生效。
- 不能因为有一例新冠患者,就进行封城!同样道理,不能因为个别情况,就进行服务降级!
- minRequestAmount=5秒钟,是在DeGradeRule中的配置
RuleConstant.DEGRADE_DEFAULT_MIN_REQUEST_AMOUNT
。
二、RT熔断降级测试
为了更容易触发降级,我们将平均响应时长设置为0.1秒(我们的方法执行时间肯定大于0.1秒)。降级之后的时间窗口是10秒。
参考Hystrix章节的《Jemeter模拟触发服务熔断》创建Jemeter接口测试用例。为了更明显的触发降级规则:配置1秒钟发送10个请求“/sysuser/pwd/reset”。
- 下图为服务熔断降级之前的响应结果。需要注意的是:虽然超过了0.1秒平均响应时长,但是可以正常响应结果。
- 下图为服务熔断降级之后的响应结果,已经超过5次(一秒内发送的),所有请求处理均超过100ms。sentinel对于异常请求的临界状态判断并未执行的十分严格,有的时候是在第6次请求之后才进入服务降级。
哎,大家看“尊敬的客户您好,系统服务繁忙,请稍后再试!”这个响应结果是不是有点眼熟?没错,它就是我们上一节BlockHandler的处理方法。sentinel服务降级之后,有2种处理方法:
- fallback方法(defaultFallback方法)处理DegradeException,即:处理服务熔断降级。fallback服务降级处理方法我们后面的章节讲!
- 当服务降级没有定义fallback方法的时候,就会去执行BlockHandler方法。二者都定义了的话,也是执行BlockHandler方法。BlockHandler优先
三、sentinel流控规则与降级规则的区别
初学者咋一看sentinel流控规则与降级规则,好像没什么区别啊?为什么不统一叫做规则配置?还要去分开呢?为了方便大家的理解,我们来比较一下。
比较点 | 流控规则 | 降级规则 |
---|---|---|
内容比较 | QPS、线程数、关联、链路限流、冷启动及匀速排队 | 平均响应时长、异常比例、异常数量 |
解决问题的方向 | 外部流量压力导致问题 | 内部编码及处理能力导致的问题 |
Exception | FlowException(BlockException) | DegradeException(BlockException) |
降级处理方法 | BlockHandler | fallback或BlockHandler |
实战熔断降级-异常数与比例
本节为大家介绍降级规则的另外两种:异常比例和异常数量。首先我们人为的在代码中加入一个被除数为0的运行时异常。
一、异常比例降级
异常比例降级的触发条件:
- 资源的每秒请求数量>=5
- 请求异常响应的数量占总请求数量的超过“异常比例配置”
当以上的两个条件都满足的时候,资源进入降级状态。在接下来的“时间窗口”内的请求都将被降级,执行fallback方法或者BlockHandler方法。
测试:
参考Hystrix章节的《Jemeter模拟触发服务熔断》创建Jemeter接口测试用例。为了更明显的触发降级规则:配置1秒钟发送10个请求“/sysuser/pwd/reset”。
从以上响应结果可以看出:在5次异常请求之后(被除数为0),异常请求占比100%(大于0.5),资源接口进入降级状态执行BlockHandler方法。BlockHandler方法参看《BlockException处理》和《实战熔断降级-RT》中的定义。
二、异常数量降级
明白了异常比例降级,异常数量降级就不难理解了。 异常数量降级的触发条件:近1分钟内异常请求数量超过“异常数配置”,资源进入降级状态。在接下来的“时间窗口”内的请求都将被降级,执行fallback方法或者BlockHandler方法。
需要注意的是:时间窗口的配置必须大于等于60,否则将导致结束熔断状态之后,再次无缘无故进入熔断状态。
测试:
参考Hystrix章节的《Jemeter模拟触发服务熔断》创建Jemeter接口测试用例。为了更明显的触发降级规则:配置1秒钟发送10个请求“/sysuser/pwd/reset”。
从以上响应结果可以看出:在6次异常请求之后(被除数为0),资源接口进入降级状态执行BlockHandler方法。BlockHandler方法参看《BlockException处理》和《实战熔断降级-RT》中的定义。
DegradeException处理
一、fallback方法
fallback 函数用于两种场景下提供 fallback处理逻辑:
- 业务上抛出运行时异常的时候
- 资源触发降级规则的时候
如:下面我们认为制造了被除数为0的异常,会执行fallback方法:pwdresetFallback给出响应结果。
- 返回值类型必须与原函数返回值类型一致;
- 方法参数列表需要和原函数一致,或者可以额外多一个
Throwable
类型的参数用于接收对应的异常。
二、fallback与fallbackClass结合使用
fallback 函数默认需要和原方法在同一个类中。若希望将fallback函数与业务解耦,将其单独拆分到一个类中,则可以指定fallbackClass
为对应的类的Class
对象。
- fallbackClass需要和fallback结合一起使用,注意对应的函数必需为 static 函数,否则无法解析
public class SysuserControllerHandler
public static AjaxResponse pwdresetFallback(@RequestParam Integer userId,Throwable e)
return AjaxResponse.error(CustomExceptionType.SYSTEM_ERROR,
"尊敬的客户您好,系统服务繁忙,请稍后再试!(pwdresetFallBack)");
三、blockHolder与fallback关系
需要特别的注意:当blockHandler和fallback方法同时定义,且资源触发的降级规则时候,降级处理逻辑由blockHandler来执行。这种情况下,blockHandler优先级高于fallback方法。
1.1.业务异常测试
人为制造上面代码中的被除数为0异常,通过Postman发送请求结果如下:
执行的是fallback方法,业务上抛出运行时异常的时候由fallback方法处理。与blockHandler无关!
1.2.资源触发降级规则
使用jmeter测试,当降级规则被触发之后,执行的是blockHandler,而不是fallback。这种情况下,blockHandler优先级高于fallback方法。
把blockHandler删除之后,再次使用jmeter测试,当降级规则被触发之后,执行的是fallback。
四、通用降级处理方法
defaultFallback
(since 1.6.0):通常用于通用的 fallback 逻辑(即可以用于很多服务或方法)。
- 返回值类型必须与原函数返回值类型一致;
- 方法参数列表需要为空,或者可以额外多一个
Throwable
类型的参数用于接收对应的异常。 - defaultFallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定
fallbackClass
为对应的类的Class
对象,注意对应的函数必需为 static 函数,否则无法解析。
public class SysuserControllerHandler
public static AjaxResponse defaultFallback()
return AjaxResponse.error(CustomExceptionType.SYSTEM_ERROR,
"尊敬的客户您好,系统服务繁忙,请稍后再试!(defaultFallback)");
只要返回值类型一致,在任何的资源上都可以使用defaultFallback,通用!
若同时配置了 fallback 和 defaultFallback,则只有 fallback 会生效。个性化配置大于通用配置!
注解与异常的归纳总结
一、区分流控规则及降级规则
这部分虽然我们之前就说过,本节我们有必要再看一下。能正确的区分流控规则与降级规则,是理解Exception处理的基础。初学者咋一看sentinel流控规则与降级规则,好像没什么区别啊?为什么不统一叫做规则配置?还要去分开呢?为了方便大家的理解,我们来比较一下。
比较点 | 流控规则 | 降级规则 |
---|---|---|
内容比较 | QPS、线程数、关联、链路限流、冷启动及匀速排队、热点规则限流 | 平均响应时长、异常比例、异常数量 |
解决问题的方向 | 外部流量压力导致问题 | 内部编码及处理能力导致的问题 |
Exception | FlowException(BlockException) | DegradeException(BlockException) |
降级处理方法 | BlockHandler | fallback或BlockHandler |
二、异常处理关系图
我们先后已经为大家介绍了sentinel的流控规则和熔断降级,流控规则被触发之后,程序将去执行BlockHolder方法。那么熔断被触发之后,程序去执行什么方法呢?sentinel为我们提供了一个专门处理服务降级的方法:fallback。
正如上图所示:
- FlowException代表限流规则被触发
- DegradeException代表熔断降级规则被触发
- FlowException和DegradeException都是BlockException的子类
- BlockHandler是可以用来被处理限流及熔断降级的方法
- fallback用来处理DegradeException,即处理熔断降级的方法
- fallback不仅可以用来处理熔断降级,还可以用来处理业务上的运行时异常
三、fallback与blockHandler用法总结
- 针对资源触发限流规则:只有blockHandler 能自定义处理逻辑
- 针对业务上抛出运行时异常:只有fallback和defaultFallback能自定义处理逻辑。fallback 优先级高于 defaultFallback
- 针对资源触发降级规则:blockHandler > fallback > defaultFallback
笔者自身的最佳实践(不绝对)
- 不自定义blockHandler,限流信息直接返回给前端处理。前端将限流信息转换成如:“系统繁忙,请稍后再试!”
- 对于大部分的业务,使用defaultFallback定义全局通用降级处理逻辑。如:“系统内部出现错误,请联系管理员进行处理!”
- 对于重点业务,降级逻辑个性化较强的业务,比如需要返回缓存。这种情况使用fallback。
- 一旦使用fallback就用fallbackClass,一旦使用blockHolder就用blockHolderClass,降低代码耦合
四、SentinelResource注解用法总结
注意:SentinelResource注解方式不支持 private 方法。
@SentinelResource
用于定义资源,并提供可选的异常处理和 fallback 配置项。@SentinelResource
注解包含以下属性:
-
value
:资源名称,必需项(不能为空) -
entryType
:entry 类型,可选项(默认为EntryType.OUT
) -
blockHandler
/blockHandlerClass
:blockHandler
对应处理BlockException
的函数名称,可选项。blockHandler 函数访问范围需要是public
,返回类型需要与原方法相匹配,参数类型需要和原方法相匹配并且最后加一个额外的参数,类型为BlockException
。blockHandler 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定blockHandlerClass
为对应的类的Class
对象,注意对应的函数必需为 static 函数,否则无法解析。 -
fallback
/fallbackClass
:fallback 函数名称,可选项,用于在抛出异常的时候提供 fallback 处理逻辑。fallback 函数可以针对所有类型的异常(除了exceptionsToIgnore
里面排除掉的异常类型)进行处理。fallback 函数签名和位置要求: -
返回值类型必须与原函数返回值类型一致;
-
方法参数列表需要和原函数一致,或者可以额外多一个
Throwable
类型的参数用于接收对应的异常。 -
fallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定
fallbackClass
为对应的类的Class
对象,注意对应的函数必需为 static 函数,否则无法解析。 -
defaultFallback
(since 1.6.0):默认的 fallback 函数名称,可选项,通常用于通用的 fallback 逻辑(即可以用于很多服务或方法)。默认 fallback 函数可以针对所有类型的异常(除了 -
exceptionsToIgnore
里面排除掉的异常类型)进行处理。若同时配置了 fallback 和defaultFallback,则只有 fallback 会生效。defaultFallback 函数签名要求: -
返回值类型必须与原函数返回值类型一致;
-
方法参数列表需要为空,或者可以额外多一个
Throwable
类型的参数用于接收对应的异常。 -
defaultFallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定
fallbackClass
为对应的类的Class
对象,注意对应的函数必需为 static 函数,否则无法解析。 -
exceptionsToIgnore
(since 1.6.0):用于指定哪些异常被排除掉,不会计入异常统计中,也不会进入 fallback 逻辑中,而是会原样抛出。
注:1.6.0 之前的版本 fallback 函数只针对降级异常(
DegradeException
)进行处理,不能针对业务异常进行处理。
Feign降级及异常传递拦截
一、在FeignClient接口配置fallback降级
参考《服务熔断降级hystrix》章节中的《Hystrix结合Feign服务降级》学习,二者的实现方法及目的是一样的,所以此处简写,只写不同的部分。
- 在服务配置文件中打开feign结合sentinel的开关
feign:
sentinel:
enabled: true
在FeignClient注解增加fallback处理实现类,如:SmsServiceFallback。
@FeignClient(name="ASERVICE-SMS",fallback = SmsServiceFallback.class)
public interface SmsService
@PostMapping(value = "/sms/send")
AjaxResponse send(@RequestParam("phoneNo") String phoneNo,
@RequestParam("content") String content);
书写SmsServiceFallback代码,该类要实现FeignClient注解的接口函数。当使用Feign客户端远程调用SmsService .send方法,如果远程服务不可达(网络不可达或宕机),就会快速执行SmsServiceFallback.send方法作为fallback。
@Component
public class SmsServiceFallback implements SmsService
@Override
public AjaxResponse send(String phoneNo, String content)
return AjaxResponse.error(CustomExceptionType.SYSTEM_ERROR
,"短信发送接口失败!");
二、远程服务调用异常传递的问题
参考《服务熔断降级hystrix》章节中的《远程服务调用异常传递的问题》学习。把文章中的hystrix当成sentinel看,把HystrixCommand注解当成SentinelResource注解看。问题是一样的,原理是一样的。所以此处简写,只写不同的部分。
人为制造一个被除数为0的异常,将会执行SmsControllerHandler 类中的sendFallback方法。
public class SmsControllerHandler
public static AjaxResponse sendFallback(@RequestParam String phoneNo,
以上是关于重学SpringCloud系列八之分布式系统流量卫兵sentinel的主要内容,如果未能解决你的问题,请参考以下文章