• Sentinel 系统规则


    https://github.com/alibaba/Sentinel/wiki/%E7%B3%BB%E7%BB%9F%E8%87%AA%E9%80%82%E5%BA%94%E9%99%90%E6%B5%81

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

    背景

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

    • 保证系统不被拖垮
    • 在系统稳定的前提下,保持系统的吞吐量

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

    • load 是一个“结果”,如果根据 load 的情况来调节流量的通过率,那么就始终有延迟性。也就意味着通过率的任何调整,都会过一段时间才能看到效果。当前通过率是使 load 恶化的一个动作,那么也至少要过 1 秒之后才能观测到;同理,如果当前通过率调整是让 load 好转的一个动作,也需要 1 秒之后才能继续调整,这样就浪费了系统的处理能力。所以我们看到的曲线,总是会有抖动。
    • 恢复慢。想象一下这样的一个场景(真实),出现了这样一个问题,下游应用不可靠,导致应用 RT 很高,从而 load 到了一个很高的点。过了一段时间之后下游应用恢复了,应用 RT 也相应减少。这个时候,其实应该大幅度增大流量的通过率;但是由于这个时候 load 仍然很高,通过率的恢复仍然不高。

    TCP BBR 的思想给了我们一个很大的启发。我们应该根据系统能够处理的请求,和允许进来的请求,来做平衡,而不是根据一个间接的指标(系统 load)来做限流。最终我们追求的目标是 在系统不被拖垮的情况下,提高系统的吞吐率,而不是 load 一定要到低于某个阈值。如果我们还是按照固有的思维,超过特定的 load 就禁止流量进入,系统 load 恢复就放开流量,这样做的结果是无论我们怎么调参数,调比例,都是按照果来调节因,都无法取得良好的效果。

    Sentinel 在系统自适应保护的做法是,用 load1 作为启动自适应保护的因子,而允许通过的流量由处理请求的能力,即请求的响应时间以及当前系统正在处理的请求速率来决定。

    系统规则

    系统保护规则是从应用级别的入口流量进行控制,从单台机器的 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 达到阈值即触发系统保护,单位是毫秒。
    • 并发线程数:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护。
    • 入口 QPS:当单台机器上所有入口流量的 QPS 达到阈值即触发系统保护。

    原理

    先用经典图来镇楼:

    TCP-BBR-pipe

    我们把系统处理请求的过程想象为一个水管,到来的请求是往这个水管灌水,当系统处理顺畅的时候,请求不需要排队,直接从水管中穿过,这个请求的RT是最短的;反之,当请求堆积的时候,那么处理请求的时间则会变为:排队时间 + 最短处理时间。

    • 推论一: 如果我们能够保证水管里的水量,能够让水顺畅的流动,则不会增加排队的请求;也就是说,这个时候的系统负载不会进一步恶化。

    我们用 T 来表示(水管内部的水量),用RT来表示请求的处理时间,用P来表示进来的请求数,那么一个请求从进入水管道到从水管出来,这个水管会存在 P * RT 个请求。换一句话来说,当 T ≈ QPS * Avg(RT) 的时候,我们可以认为系统的处理能力和允许进入的请求个数达到了平衡,系统的负载不会进一步恶化。

    接下来的问题是,水管的水位是可以达到了一个平衡点,但是这个平衡点只能保证水管的水位不再继续增高,但是还面临一个问题,就是在达到平衡点之前,这个水管里已经堆积了多少水。如果之前水管的水已经在一个量级了,那么这个时候系统允许通过的水量可能只能缓慢通过,RT会大,之前堆积在水管里的水会滞留;反之,如果之前的水管水位偏低,那么又会浪费了系统的处理能力。

    • 推论二: 当保持入口的流量是水管出来的流量的最大的值的时候,可以最大利用水管的处理能力。

    然而,和 TCP BBR 的不一样的地方在于,还需要用一个系统负载的值(load1)来激发这套机制启动。

    注:这种系统自适应算法对于低 load 的请求,它的效果是一个“兜底”的角色。对于不是应用本身造成的 load 高的情况(如其它进程导致的不稳定的情况),效果不明显。

    示例

    我们提供了系统自适应限流的示例:SystemGuardDemo

    package com.wsm.order.controller;
    
    import com.alibaba.csp.sentinel.annotation.SentinelResource;
    import com.alibaba.csp.sentinel.slots.block.BlockException;
    import com.wsm.order.service.OrderService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import java.util.concurrent.TimeUnit;
    
    @RestController
    @RequestMapping("/order")
    public class OrderController {
    
        @Autowired
        OrderService orderService;
    
        @RequestMapping("/add")
        public String add(){
            System.out.println("下单成功!");
            return "生成订单";
        }
    
        @RequestMapping("/get")
        public String get(){
            System.out.println("查询订单!");
            return "查询订单";
        }
    
        @RequestMapping("/test1")
        public String test1(){
            return orderService.getUser();
        }
    
        @RequestMapping("/test2")
        public String test2(){
            return orderService.getUser();
        }
    
        @RequestMapping("/flow")
    //    @SentinelResource(value = "flow",blockHandler = "flowBlockHandler")
        public String flow(){
            System.out.println("========flow====");
            return "正常访问";
        }
    
        public String flowBlockHandler(BlockException e){
            return "流控了";
        }
    
        @RequestMapping("/flowThread")
    //    @SentinelResource(value = "flowThread",blockHandler = "flowThreadBlockHandler")
        public String flowThread() throws InterruptedException {
            TimeUnit.SECONDS.sleep(5);
            System.out.println("flowThread正常访问");
            return "正常访问";
        }
    
        public String flowThreadBlockHandler(BlockException e){
            return "flowThread流控了";
        }
    
        @RequestMapping("/err")
        public String err() {
            int a=1/0;
            return "err";
        }
    
        @RequestMapping("/get/{id}")
        @SentinelResource(value = "getById", blockHandler = "HotBlockHandler")
        public String getById(@PathVariable("id") Integer id) {
            System.out.println("正常访问:"+id);
            return "正常访问:"+id;
        }
    
        public String HotBlockHandler(@PathVariable("id") Integer id,BlockException e) {
            return "热点异常处理"+id;
        }
    
    }

  • 相关阅读:
    重新了解Java基础(六)-Java关键字
    重新了解Java基础(五)-Java标识符
    重新了解Java基础(四)-常用开发工具&注释简介
    CentOS 7 安装JDK
    一步一步教你自定义博客园(cnblog)界面
    如何设置文字的阴影
    如何实现2D动画
    跳转九宫格的制作
    风车的动图制作
    心跳的动图制作
  • 原文地址:https://www.cnblogs.com/mingforyou/p/15676476.html
Copyright © 2020-2023  润新知