Cloud Alibaba Sentinel——限流、降级

一、Sentinel服务降级简介

https://github.com/alibaba/Sentinel/wiki/熔断降级

除了流量控制以外,对调用链路中不稳定的资源进行熔断降级也是保障高可用的重要措施之一。由于调用关系的复杂性,如果调用链路中的某个资源不稳定,最终会导致请求发生堆积。Sentinel 熔断降级会在调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高),对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联错误。当资源被降级后,在接下来的降级时间窗口之内,对该资源的调用都自动熔断(默认行为是抛出 DegradeException)。

65.jpg

我们通常用以下三种方式来衡量资源是否处于稳定的状态:

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

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

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

注意:异常降级仅针对业务异常,对 Sentinel 限流降级本身的异常(BlockException)不生效。为了统计异常比例或异常数,需要通过 Tracer.trace(ex) 记录业务异常


二、Sentinel降级实战

1、降级——RT

66.jpg

描述:当1s内请求线程数大于N(N默认5)-QPS,如果请求的平均响应时间超过阈值200ms,则在未来的1s内,服务将不可用,保险丝跳闸断电!

2、降级——异常比例

67.jpg

描述:QPS >5时,且每秒异常总数占所有请求量的比重超过20%,则熔断降级;

如果,请求QPS不够,则及时异常比重超过20%,也不会熔断,而是直接报错!

3、降级——异常数

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

68.jpg

描述:一分钟内的异常数 > 5次,那么在接下来的61s内,系统就会被熔断;


三、Sentinel热点key —— 热点参数限流

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

  • 商品 ID 为参数,统计一段时间内最常购买的商品 ID 并进行限制

  • 用户 ID 为参数,针对一段时间内频繁访问的用户 ID 进行限制

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

1、这里需要引入一个新的注解标签 @SentinelResource(可与HystrixCommand对比学习)—— 兜底方法肯定需要用到此重要注解

在Controller编写如下的测试hotkey的方法+兜底方法:

@GetMapping("/testHotkey")
@SentinelResource(value = "testHotkey", blockHandler = "deal_testHotkey")  //配热点时候用这个,不带"/"
public String testHotkey(@RequestParam("p1") String p1,
                         @RequestParam("p2") String p2){
    return "-------testHotkey";
}

public String deal_testHotkey(String p1, String p2, BlockException exception){
    return "-------deal testHotkey o(╥﹏╥)o";  //Sentinel的默认提示“Blocked by Sentinel(flow limiting)”
}

2、在Sentinel中增加热点规则:

69.jpg

3、快速访问此链接 “http://localhost:8401/testHotkey?p1=1”

70.jpg

显然,当蚕食为p1时,被限流了,而当参数为p2时,无论QPS多大,访问都不会被限流;

4、如果我们不指定   blockHandler = "deal_testHotkey"   

那么是会调用Sentinel自带的限流处理还是报错呢?

71.jpg

结果是,直接“Error Page”,比较严重,所以我们一定要指定   blockHandler = "deal_testHotkey"  

5、热点Key规则的参数例外项

72.jpg

当p1值为abc时候,只有当QPS超过4之后,才会被限流;

6、补充:

对于普通的 0/1 等运行时异常,并被会被兜底方法兜底,因为:

SentinelResource只会负责Sentinel配置控制台配置了规则的限流规则不满足BlockException时候才兜底,并不管Runtime等Exception


四、Sentinel系统自适应限流 —— 粗粒度 ——入口级

1、Sentinel 系统自适应限流从整体维度对应用入口流量进行控制,结合应用的 Load、CPU 使用率、总体平均 RT、入口 QPS 和并发线程数等几个维度的监控指标,通过自适应的流控策略,让系统的入口流量和系统的负载达到一个平衡,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。

在开始之前,我们先了解一下系统保护的目的:

  • 保证系统不被拖垮

  • 在系统稳定的前提下,保持系统的吞吐量

长期以来,系统保护的思路是根据硬指标,即系统的负载 (load1) 来做系统过载保护。当系统负载高于某个阈值,就禁止或者减少流量的进入;当 load 开始好转,则恢复流量的进入。这个思路给我们带来了不可避免的两个问题:

  • load 是一个“结果”,如果根据 load 的情况来调节流量的通过率,那么就始终有延迟性。也就意味着通过率的任何调整,都会过一段时间才能看到效果。当前通过率是使 load 恶化的一个动作,那么也至少要过 1 秒之后才能观测到;同理,如果当前通过率调整是让 load 好转的一个动作,也需要 1 秒之后才能继续调整,这样就浪费了系统的处理能力。所以我们看到的曲线,总是会有抖动。

  • 恢复慢。想象一下这样的一个场景(真实),出现了这样一个问题,下游应用不可靠,导致应用 RT 很高,从而 load 到了一个很高的点。过了一段时间之后下游应用恢复了,应用 RT 也相应减少。这个时候,其实应该大幅度增大流量的通过率;但是由于这个时候 load 仍然很高,通过率的恢复仍然不高。

2、系统规则支持以下的模式:

73.jpg

  • 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 达到阈值即触发系统保护,单位是毫秒。

  • 并发线程数:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护。

  • 入口 QPS:当单台机器上所有入口流量的 QPS 达到阈值即触发系统保护。


五、@SentinelResource注解的配置使用(参考HystrixCommand学习)

1、新增一个RateLimitController:

@RestController
public class RateLimitController {
    @GetMapping("/byResource")
    @SentinelResource(value = "byResource", blockHandler = "handleException")
    public CommonResult byResource(){
        return new CommonResult(200,"按资源名称限流测试OK", new Payment(2020L,"serial001"));
    }
    public CommonResult handleException(BlockException exception){
        return new CommonResult(444,exception.getClass().getCanonicalName()+"\t 服务不可用");
    }
}

2、刚开始访问  http://localhost:8401/byResource   时,一切正常:

74.jpg

但是当我们以资源名称“byResource ”配置一个简单的限流规则后:

76.jpg

再次访问,使QPS > 1后,查看结果:

75.jpg

显然我们自定义的兜底方法限流信息生效了,还记得之前默认的“Blocked by Sentinel(flow limiting)”吗?

3、上面我们测试的是以资源名称新增限流规则的,现在我们再以url作为限流规则看看:

@GetMapping("/rateLimit/byUrl")
@SentinelResource(value = "byUrl")  //注意这里并没有指定 blockHandler方法
public CommonResult byUrl(){
    return new CommonResult(200,"按url限流测试OK",new Payment(2020L,"serial002"));
}

4、为此Url配置一套限流规则:

77.jpg

访问测试:当流控规则被打破后,使用了系统自带的限流信息“Blocked by Sentinel(flow limiting)

78.jpg

上面的兜底方案面临着一些严重的问题:

  • 每一个业务方法都得有一个兜底的方法,那么必然代码膨胀;

  • 如果我们实在不想配,就会使用系统默认的兜底信息,没有体现我们自己的业务要求;

  • 兜底处理方法和我们的业务代码耦合在一起,不直观;

  • 全局统一的处理方法没有体现;

5、因为存在上面的问题,就需要引入我们“自定义限流处理类”这样的需求:

创建一个自定义的限流处理类 CustomerBlockHandler,用于自定义的限流处理逻辑:

public class CustomerBlockHandler {
    //必须是static方法
    public static CommonResult handlerException(BlockException exception){
        return new CommonResult(4444,"按用户自定义, Global HandlerException-----1");
    }
    //做2个异常处理方法,是为了在业务中根据不同场景,选择使用不同的全局异常处理方法
    public static CommonResult handlerException2(BlockException exception){
        return new CommonResult(4444,"按用户自定义, Global HandlerException-----2");
    }
}

6、在Controller类中的业务代码上作如下配置:

@GetMapping("/rateLimit/customerBlockHandler")
@SentinelResource(value = "customerBlockHandler",
        blockHandlerClass = CustomerBlockHandler.class,
        blockHandler = "handlerException2")
public CommonResult customerBlockHandler(){
    return new CommonResult(200, "客户自定义限流处理", new Payment(2020L, "serial003"));
}

当为 customerBlockHandler 这个资源名配置了限流规则后,当QPS > 1后:

79.jpg

显然,使用了我们指定的兜底处理方法;

7、补充:Sentinel的流控还支持代码级别的配置,但是强烈不推荐使用:

private static void initFlowRules(){    
    List<FlowRule> rules = new ArrayList<>();    
    FlowRule rule = new FlowRule();
    rule.setResource("HelloWorld");
    rule.setGrade(RuleConstant.FLOW_GRADE_QPS);    
    // Set limit QPS to 20.
    rule.setCount(20);
    rules.add(rule);    
    FlowRuleManager.loadRules(rules);
}

8、Sentinel有三个核心的API:

  • SphU:定义资源;

  • Tracer:定义统计; 线程数/QPS

  • ContextUtil:定义了上下文


六、Sentinel服务熔断

Sentinel服务熔断,整合Ribbon、Feign,及fallback的使用,另起一篇笔记;

http://www.jiguiquan.com/archives/994

个人此项目代码地址(持续更新):

https://github.com/jiguiquan/cloud2020

jiguiquan@163.com

文章作者信息...

留下你的评论

*评论支持代码高亮<pre class="prettyprint linenums">代码</pre>

相关推荐