重学SpringCloud系列八之分布式系统流量卫兵sentinel

Posted 大忽悠爱忽悠

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了重学SpringCloud系列八之分布式系统流量卫兵sentinel相关的知识,希望对你有一定的参考价值。

重学SpringCloud系列八之分布式系统流量卫兵sentinel


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版本的确是无法生效,所以这一节内容暂时放在这里!后续有发展我们再继续更新!


实战流控效果-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服务降级规则之后,在时间窗口期内(上图绿色框配置)对资源的访问将被降级,即:断路器被打开。时间窗口期之后关闭断路器。

需要注意的是:

  1. RT平均响应时长最大可配值为4900毫秒。当配置超过4900的时候,默认等于4900。
  2. 官方之所以设置RT最大值为4900,是因为当一次请求超过5秒就到了用户能忍受的极限。
  3. 如果确实希望修改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、线程数、关联、链路限流、冷启动及匀速排队平均响应时长、异常比例、异常数量
解决问题的方向外部流量压力导致问题内部编码及处理能力导致的问题
ExceptionFlowException(BlockException)DegradeException(BlockException)
降级处理方法BlockHandlerfallback或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、线程数、关联、链路限流、冷启动及匀速排队、热点规则限流平均响应时长、异常比例、异常数量
解决问题的方向外部流量压力导致问题内部编码及处理能力导致的问题
ExceptionFlowException(BlockException)DegradeException(BlockException)
降级处理方法BlockHandlerfallback或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的主要内容,如果未能解决你的问题,请参考以下文章

重学SpringCloud系列四之分布式配置中心---上

重学SpringCloud系列一之微服务入门

重学SpringCloud系列二之服务注册与发现---上

重学SpringCloud系列三之服务注册与发现---下

重学SpringCloud系列五之服务注册与发现---中

重学SpringCloud系列九微服务网关-GateWay