Sentinel3降级、热点、系统规则

1.Sentinel降级简介

熔断降级概述
除了流量控制以外,对调用链路中不稳定的资源进行熔断降级也是保障高可用的重要措施之一。一个服务常常会调用别的模块,可能是另外的一个远程服务、数据库,或者第三方 API 等。例如,支付的时候,可能需要远程调用银联提供的 API;查询某个商品的价格,可能需要进行数据库查询。然而,这个被依赖服务的稳定性是不能保证的。如果依赖的服务出现了不稳定的情况,请求的响应时间变长,那么调用服务的方法的响应时间也会变长,线程会产生堆积,最终可能耗尽业务自身的线程池,服务本身也变得不可用。
现代微服务架构都是分布式的,由非常多的服务组成。不同服务之间相互调用,组成复杂的调用链路。以上的问题在链路调用中会产生放大的效果。复杂链路上的某一环不稳定,就可能会层层级联,最终导致整个链路都不可用。因此我们需要对不稳定的弱依赖服务调用进行熔断降级,暂时切断不稳定调用,避免局部不稳定因素导致整体的雪崩。熔断降级作为保护自身的手段,通常在客户端(调用端)进行配置。

  • RT(平均响应时间,秒级)

  • 平均响应时间 超出阈值 且 在时间窗口内通过的请求>=5,两个条件同时满足后触发降级。

  • 窗口期过后关闭断路器。
  • RT最大4900(更大的需要通过-Dcsp.sentinel.statistic.max.rt=XXXX才能生效)。
  • 异常比列(秒级)
  • QPS >= 5且异常比例(秒级统计)超过阈值时,触发降级;时间窗口结束后,关闭降级 。
  • 异常数(分钟级)
  • 异常数(分钟统计)超过阈值时,触发降级;时间窗口结束后,关闭降级

Sentinel熔断降级会在调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高),对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联错误。
当资源被降级后,在接下来的降级时间窗口之内,对该资源的调用都自动熔断(默认行为是抛出 DegradeException)。
Sentinei的断路器是没有类似Hystrix半开状态的。(Sentinei 1.8.0 已有半开状态)
半开的状态系统自动去检测是否请求有异常,没有异常就关闭断路器恢复使用,有异常则继续打开断路器不可用。

2.Sentinel降级-RT

  1. 平均响应时间(DEGRADE_GRADE_RT):当1s内持续进入5个请求,对应时刻的平均响应时间(秒级)均超过阈值( count,以ms为单位),那么在接下的时间窗口(DegradeRule中的timeWindow,以s为单位)之内,对这个方法的调用都会自动地熔断(抛出DegradeException )。注意Sentinel 默认统计的RT上限是4900 ms,超出此阈值的都会算作4900ms,若需要变更此上限可以通过启动配置项-Dcsp.sentinel.statistic.max.rt=xxx来配置。

注意:Sentinel 1.7.0才有平均响应时间DEGRADE_GRADE_RT),Sentinel 1.8.0的没有这项,取而代之的是慢调用比例 (SLOW_REQUEST_RATIO)。

  1. 慢调用比例 (SLOW_REQUEST_RATIO):选择以慢调用比例作为阈值,需要设置允许的慢调用 RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用。当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断。

接下来讲解Sentinel 1.7.0的。

  1. @RestController
  2. @Slf4j
  3. public class FlowLimitController {
  4. ...
  5. @GetMapping("/testD")
  6. public String testD() {
  7. try {
  8. TimeUnit.SECONDS.sleep(1);
  9. } catch (InterruptedException e) {
  10. e.printStackTrace();
  11. }
  12. log.info("testD 测试RT");
  13. }
  14. }


jmeter压测

结论
按照上述配置,永远一秒钟打进来10个线程(大于5个了)调用testD,我们希望200毫秒处理完本次任务,如果超过200毫秒还没处理完,在未来1秒钟的时间窗口内,断路器打开(保险丝跳闸)微服务不可用,保险丝跳闸断电了后续我停止jmeter,没有这么大的访问量了,断路器关闭(保险丝恢复),微服务恢复OK。

3.Sentinel降级-异常比例

  1. 异常比例(DEGRADE_GRADE_EXCEPTION_RATIO):当资源的每秒请求量 >= 5,并且每秒异常总数占通过量的比值超过阈值( DegradeRule中的 count)之后,资源进入降级状态,即在接下的时间窗口( DegradeRule中的timeWindow,以s为单位)之内,对这个方法的调用都会自动地返回。异常比率的阈值范围是[0.0, 1.0],代表0% -100%。

注意,与Sentinel 1.8.0相比,有些不同(Sentinel 1.8.0才有的半开状态),Sentinel 1.8.0的如下:

  1. 异常比例 (ERROR_RATIO):当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%。

接下来讲解Sentinel 1.7.0的。

  1. @RestController
  2. @Slf4j
  3. public class FlowLimitController {
  4. ...
  5. @GetMapping("/testD")
  6. public String testD() {
  7. log.info("testD 异常比例");
  8. int age = 10/0;
  9. return "------testD";
  10. }
  11. }


img
结论
按照上述配置,单独访问一次,必然来一次报错一次(int age = 10/0),调一次错一次。
开启jmeter后,直接高并发发送请求,多次调用达到我们的配置条件了。断路器开启(保险丝跳闸),微服务不可用了,不再报错error而是服务降级了。

4.Sentinel降级-异常数

  1. 异常数( DEGRADE_GRADF_EXCEPTION_COUNT ):当资源近1分钟的异常数目超过阈值之后会进行熔断。注意由于统计时间窗口是分钟级别的,若timeWindow小于60s,则结束熔断状态后码可能再进入熔断状态。

注意,与Sentinel 1.8.0相比,有些不同(Sentinel 1.8.0才有的半开状态),Sentinel 1.8.0的如下:

  1. 异常数 (ERROR_COUNT):当单位统计时长内的异常数目超过阈值之后会自动进行熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。

接下来讲解Sentinel 1.7.0的。
异常数是按照分钟统计的,时间窗口一定要大于等于60秒

  1. @RestController
  2. @Slf4j
  3. public class FlowLimitController{
  4. ...
  5. @GetMapping("/testE")
  6. public String testE()
  7. {
  8. log.info("testE 测试异常数");
  9. int age = 10/0;
  10. return "------testE 测试异常数";
  11. }
  12. }


访问http://localhost:8401/testE,第一次访问绝对报错,因为除数不能为零,我们看到error窗口,但是达到5次报错后,进入熔断后降级。

5.Sentinel热点key(上)


何为热点?热点即经常访问的数据。很多时候我们希望统计某个热点数据中访问频次最高的 Top K 数据,并对其访问进行限制。比如:

  • 商品 ID 为参数,统计一段时间内最常购买的商品 ID 并进行限制
  • 用户 ID 为参数,针对一段时间内频繁访问的用户 ID 进行限制

热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效。

Sentinel 利用 LRU 策略统计最近最常访问的热点参数,结合令牌桶算法来进行参数级别的流控。热点参数限流支持集群模式。
承上启下复习start
兜底方法,分为系统默认和客户自定义,两种
之前的case,限流出问题后,都是用sentinel系统默认的提示: Blocked by Sentinel (flow limiting)
我们能不能自定?类似hystrix,某个方法出问题了,就找对应的兜底降级方法?
结论 - 从HystrixCommand到SentinelResource

  1. @RestController
  2. @Slf4j
  3. public class FlowLimitController
  4. {
  5. ...
  6. @GetMapping("/testHotKey")
  7. @SentinelResource(value = "testHotKey",blockHandler/*兜底方法*/ = "deal_testHotKey")
  8. public String testHotKey(@RequestParam(value = "p1",required = false) String p1,
  9. @RequestParam(value = "p2",required = false) String p2) {
  10. //int age = 10/0;
  11. return "------testHotKey";
  12. }
  13. /*兜底方法*/
  14. public String deal_testHotKey (String p1, String p2, BlockException exception) {
  15. return "------deal_testHotKey,o(╥﹏╥)o"; //sentinel系统默认的提示:Blocked by Sentinel (flow limiting)
  16. }
  17. }

配置

  • @SentinelResource(value = "testHotKey")
  • 异常打到了前台用户界面看到,不友好

  • @SentinelResource(value = "testHotKey", blockHandler = "dealHandler_testHotKey")
  • 方法testHotKey里面第一个参数只要QPS超过每秒1次,马上降级处理
  • 异常用了我们自己定义的兜底方法

测试

5.Sentinel热点key(下)

上述案例演示了第一个参数p1,当QPS超过1秒1次点击后马上被限流。
参数例外项

  • 普通 - 超过1秒钟一个后,达到阈值1后马上被限流
  • 我们期望p1参数当它是某个特殊值时,它的限流值和平时不一样
  • 特例 - 假如当p1的值等于5时,它的阈值可以达到200

配置

测试

前提条件 - 热点参数的注意点,参数必须是基本类型或者String
其它
在方法体抛异常

  1. @RestController
  2. @Slf4j
  3. public class FlowLimitController
  4. {
  5. ...
  6. @GetMapping("/testHotKey")
  7. @SentinelResource(value = "testHotKey",blockHandler/*兜底方法*/ = "deal_testHotKey")
  8. public String testHotKey(@RequestParam(value = "p1",required = false) String p1,
  9. @RequestParam(value = "p2",required = false) String p2) {
  10. int age = 10/0;//<----------------------------会抛异常的地方
  11. return "------testHotKey";
  12. }
  13. /*兜底方法*/
  14. public String deal_testHotKey (String p1, String p2, BlockException exception) {
  15. return "------deal_testHotKey,o(╥﹏╥)o"; //sentinel系统默认的提示:Blocked by Sentinel (flow limiting)
  16. }
  17. }

将会抛出Spring Boot 2的默认异常页面,而不是兜底方法。

  • SentinelResource - 处理的是sentinel控制台配置的违规情况,有blockHandler方法配置的兜底处理;
  • RuntimeException int age = 10/0,这个是java运行时报出的运行时异常RunTimeException,@SentinelResource不管

总结 - @SentinelResource主管配置出错,运行出错该走异常走异常

7.Sentinel系统规则

Sentinel 系统自适应限流从整体维度对应用入口流量进行控制,结合应用的 Load、CPU 使用率、总体平均 RT、入口 QPS 和并发线程数等几个维度的监控指标,通过自适应的流控策略,让系统的入口流量和系统的负载达到一个平衡,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。
系统规则
系统保护规则是从应用级别的入口流量进行控制,从单台机器的 load、CPU 使用率、平均 RT、入口 QPS 和并发线程数等几个维度监控应用指标,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。
系统保护规则是应用整体维度的,而不是资源维度的,并且仅对入口流量生效。入口流量指的是进入应用的流量(EntryType.IN),比如 Web 服务或 Dubbo 服务端接收的请求,都属于入口流量。
系统规则支持以下的模式:

  • Load 自适应(仅对 Linux/Unix-like 机器生效):系统的 load1 作为启发指标,进行自适应系统保护。当系统 load1 超过设定的启发值,且系统当前的并发线程数超过估算的系统容量时才会触发系统保护(BBR 阶段)。系统容量由系统的 maxQps minRt 估算得出。设定参考值一般是 CPU cores 2.5。
  • CPU usage(1.5.0+ 版本):当系统 CPU 使用率超过阈值即触发系统保护(取值范围 0.0-1.0),比较灵敏。
  • 平均 RT:当单台机器上所有入口流量的平均 RT 达到阈值即触发系统保护,单位是毫秒。
  • 平均 RT:当单台机器上所有入口流量的平均 RT 达到阈值即触发系统保护,单位是毫秒。
  • 入口 QPS:当单台机器上所有入口流量的 QPS 达到阈值即触发系统保护。

    8.SentinelResource配置(上)、

    按资源名称限流 + 后续处理
    启动Nacos成功
    启动Sentinel成功
    zds-egg-sentinel8401
    controller ``` @RestController public class RateLimitController {

    @GetMapping(“/byResource”) @SentinelResource(value = “byResource”,blockHandler = “handleException”) public CommonResult byResource() {

    1. return new CommonResult(200,"按资源名称限流测试OK",new Payment(2020L,"serial001"));

    }

    public CommonResult handleException(BlockException exception) {

    1. return new CommonResult(444,exception.getClass().getCanonicalName()+"\t 服务不可用");

    } }

  1. **配置流控规则**<br />配置步骤
  2. 图形配置和代码关系<br />表示1秒钟内查询次数大于1,就跑到我们自定义的处流,限流<br />**测试**<br />1秒钟点击1下,OK<br />超过上述,疯狂点击,返回了自己定义的限流处理信息,限流发生

{ “code”: 444, “msg”: “服务不可用”, “requestId”: “22222222”, “data”: null }

  1. **额外问题**<br />此时关闭问服务8401 -> Sentinel控制台,流控规则消失了
  2. ---
  3. _按照Url地址限流 + 后续处理_<br />**通过访问的URL来限流,会返回Sentinel自带默认的限流处理信息**<br />**业务类RateLimitController**

@RestController public class RateLimitController { …

  1. @GetMapping("/rateLimit/byUrl")
  2. @SentinelResource(value = "byUrl")
  3. public CommonResult byUrl()
  4. {
  5. return new CommonResult(200,"按url限流测试OK",new Payment(2020L,"serial002"));
  6. }

}

  1. **Sentinel控制台配置**
  2. **测试**
  3. - 快速点击http://localhost:8401/rateLimit/byUrl
  4. - 结果 - 会返回Sentinel自带的限流处理结果 Blocked by Sentinel (flow limiting)
  5. **上面兜底方案面临的问题**
  6. 1. 系统默认的,没有体现我们自己的业务要求。
  7. 1. 依照现有条件,我们自定义的处理方法又和业务代码耦合在一块,不直观。
  8. 1. 每个业务方法都添加—个兜底的,那代码膨胀加剧。
  9. 1. 全局统—的处理方法没有体现。
  10. ### 9.SentinelResource配置(中)
  11. 客户自定义限流处理逻辑<br />自定义限流处理类 - 创建CustomerBlockHandler类用于自定义限流处理逻辑

import com.alibaba.csp.sentinel.slots.block.BlockException; import com.atguigu.springcloud.entities.CommonResult; import com.atguigu.springcloud.entities.Payment;

public class CustomerBlockHandler { public static SingleResult handlerException(BlockException exception) { return new SingleResult().except(4444,”按客戶自定义,global handlerException——1”,””,””); }

  1. public static SingleResult handlerException2(BlockException exception) {
  2. return new SingleResult().except(4444,"按客戶自定义,global handlerException----2","","");
  3. }

}

  1. ```
  2. @RestController
  3. public class RateLimitController {
  4. ...
  5. @GetMapping("/rateLimit/customerBlockHandler")
  6. @SentinelResource(value = "customerBlockHandler", blockHandlerClass = CustomerBlockHandler.class,//<-------- 自定义限流处理类
  7. blockHandler = "handlerException2")
  8. public SingleResult customerBlockHandler(){
  9. return new SingleResult().success("", "serial003");
  10. }
  11. }

Sentinel控制台配置

启动微服务后先调用一次 - http://localhost:8401/rateLimit/customerBlockHandler。然后,多次快速刷新http://localhost:8401/rateLimit/customerBlockHandler。刷新后,我们自定义兜底方法的字符串信息就返回到前端。

10.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 逻辑中,而是会原样抛出。

Sentinel主要有三个核心Api:

  1. SphU定义资源
  2. Tracer定义统计
  3. ContextUtil定义了上下文