• Sentinel源码剖析之核心组件作用和介绍


    1、什么是Sentinel

    Sentinel 是分布式系统的防御系统。以流量为切入点,通过动态设置的流量控制、服务熔断降级、系统负载保护等多个维度保护服务的稳定性,通过服务降级增强服务被拒后用户的体验。

    官网:https://github.com/alibaba/Sentinel/wiki/

    2、Sentinel特性

    • 丰富的应用场景:Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用应用等。

    • 完备的实时监控:Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况。

    • 广泛的开源生态:Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud、Apache Dubbo、gRPC、Quarkus 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel。同时 Sentinel 提供 Java/Go/C++ 等多语言的原生实现。

    • 完善的 SPI 扩展机制:Sentinel 提供简单易用、完善的 SPI 扩展接口。您可以通过实现扩展接口来快速地定制逻辑。例如定制规则管理、适配动态数据源等。

    在这里插入图片描述

    3、Sentinel 模块

    • 核心库(Java 客户端)不依赖任何框架/库,能够运行于所有 Java 运行时环境,同时对 Dubbo/Spring Cloud 等框架也有较好的支持。

    • 控制台(Dashboard)基于 Spring Boot 开发,打包后可以直接运行,不需要额外的 Tomcat 等应用容器。

    在这里插入图片描述
    在这里插入图片描述

    4、Sentinel基本概念

    Sentinel 实现限流、隔离、降级、熔断功能,本质要做的就是两件事:

    • 统计数据:统计某个资源的访问数据,例如:QPS、RT 等信息;

    • 规则判断:判断限流规则、隔离规则、降级规则、熔断规则是否满足。

    资源

    • 资源是 Sentinel 的关键概念。它可以是 Java 应用程序中的任何内容,例如,由应用程序提供的服务,或由应用程序调用的其它应用提供的服务,甚至可以是一段代码。

    • 只要通过 Sentinel API 定义的代码,就是资源,能够被 Sentinel 保护起来。大部分情况下,可以使用方法签名,URL,甚至服务名称作为资源名来表示资源。

    • 资源就是希望被 Sentinel 保护的业务,例如项目中定义的 Controller 方法,就是默认被 Sentinel 保护的资源

    规则

    围绕资源的实时状态设定的规则,可以包括流量控制规则、熔断降级规则以及系统保护规则。所有规则可以动态实时调整。

    5、Sentinel大致工作原理/工作流程

    在这里插入图片描述

    6、Sentinel核心组件

    1、ProcessorSlotChain

    Sentinel 的核心骨架是 ProcessorSlotChain,这个类基于责任链模式来设计,将不同的功能(限流、降级、系统保护等)封装为一个一个的 Slot,请求进入后逐个执行即可。系统会为每个资源创建一套 SlotChain。SlotChain 其实可以分为两部分:统计数据构建部分(statistic)和判断部分(rule checking)

    2、责任链中的 SlotChain 分为两大类

    统计数据构建部分(statistic)

    • NodeSelectorSlot:负责构建簇点链路中的节点(DefaultNode),将这些节点形成链路树
    • ClusterBuilderSlot:负责构建某个资源的 ClusterNode,ClusterNode 可以保存资源的运行信息以及来源信息(origin 名称),例如响应时间、QPS、block 数量、线程数、异常数等
    • StatisticSlot:负责统计实时调用数据,包括运行信息、来源信息等

    规则判断部分(rule checking)

    • AuthoritySlot:负责授权规则(来源控制)(黑白名单)
    • LogSlot:负责日志打印
    • SystemSlot:负责系统保护规则(系统qps,thread等等防护)
    • ParamFlowSlot:负责热点参数限流规则
    • FlowSlot:负责普通限流规则
    • GatewayFlowSlot:负责网关限流规则
    • DegradeSlot:负责降级规则
    3、slotChain调用顺序

    在这里插入图片描述

    4、Context

    Context 是对资源操作的上下文,每个资源操作必须属于一个 Context。如果代码中没有指定 Context,则会创建一个 name 为 sentinel_default_context 的默认 Context。一个 Context 生命周期中可以包含多个资源操作。Context 生命周期中最后一个资源在 exit() 时会清理该 Context,也就意味着这个 Context 生命周期结束了

    说白了context指的是 调用方访问受保护的资源时,调用链路的上下文

    • Context 代表调用链路上下文,贯穿一次调用链路中的所有资源(Entry)。Context 维持着入口节点(entranceNode)、本次调用链路的 curNode、调用来源(origin)等信息;

    • Context 维持的方式:通过 ThreadLocal 传递,只有在入口 enter 的时候生效。由于 Context 是通过 ThreadLocal 传递的,因此对于异步调用链路,线程切换的时候会丢掉 Context,因此需要手动通过 ContextUtil.runOnContext(context, f) 来变换 context;
      后续的 Slot 都可以通过 Context 拿到 DefaultNode 或者 ClusterNode,从而获取统计数据,完成规则判断;

    • Context 初始化的过程中会创建 EntranceNode ,entranceNode Name往往指的是调用方,资源,default。entranceNode 作为流量入口,作为一个统计维度

    public void contextDemo() {
        // 创建一个来自于 appA 访问的 Context,”entranceOne“ 为 Context 的名称,”appA“ 为来源名称
        ContextUtil.enter("entranceOne", "appA");
        
        // Entry 就是一个资源操作对象
        Entry resource1 = null;
        Entry resource2 = null;
        
        try {
            // 获取资源 resource1 的 entry
            resource1 = SphU.entry("resource1");
            // 代码至此,说明当前对资源 resource1 的请求通过了流控
            // 对资源 resource1 的相关业务处理。。。
    
            // 获取资源 resource2 的 entry
            resource2 = SphU.entry("resource2");
            // 代码至此,说明当前对资源 resource2 的请求通过了流控
            // 对资源 resource2 的相关业务处理。。。
        } catch (BlockException e) {
            // 代码至此,说明请求被限流,这里执行降级处理
        } finally {
            if (resource1 != null) {
                resource1.exit();
            }
            if (resource2 != null) {
                resource2.exit();
            }
        }
        // 释放Context
        ContextUtil.exit();
    
        // --------------------------------------------------------
    
        // 创建另一个来自于 appA 访问的 Context,
        // entranceTwo 为 Context 的 name
        ContextUtil.enter("entranceTwo", "appA");
        
        // Entry 就是一个资源操作对象
        Entry resource3 = null;
        
        try {
            // 获取资源 resource2 的 entry
            resource2 = SphU.entry("resource2");
            // 代码至此,说明当前对资源 resource2 的请求通过了流控
            // 对资源 resource2 的相关业务处理。。。
    
    
            // 获取资源 resource3 的 entry
            resource3 = SphU.entry("resource3");
            // 代码至此,说明当前对资源 resource3 的请求通过了流控
            // 对资源 resource3 的相关业务处理。。。
    
        } catch (BlockException e) {
            // 代码至此,说明请求被限流,这里执行降级处理
        } finally {
            if (resource2 != null) {
                resource2.exit();
            }
            if (resource3 != null) {
                resource3.exit();
            }
        }
        // 释放Context
        ContextUtil.exit();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    5、Entry

    entry为令牌,当访问受限的资源被允许时,会获取到entry令牌

    • 每一次资源调用都会创建一个 Entry。Entry 包含了资源名、curNode(当前统计节点)、originNode(来源统计节点)等信息。

    • CtEntry 为普通的 Entry,在调用 SphU.entry(xxx) 的时候创建。特性:Linked entry within current context(内部维护着 parent 和 child)

    • 需要注意的一点:CtEntry 构造函数中会做调用链的变换,即将当前 Entry 接到传入 Context 的调用链路上(setUpEntryFor)。

    • 资源调用结束时需要 entry.exit()。exit 操作会过一遍 slot chain exit,恢复调用栈,exit 会清空 entry 中的 context 防止重复调用

    6、Node

    簇点链路:就是项目内的调用链路,链路中被监控的每个接口就是一个资源。默认情况下 sentinel 会监控 SpringMVC 的每一个端点(Endpoint),因为 SpringMVC 的每一个端点就是调用链路中的一个资源。流控、熔断等都是针对簇点链路中的资源来设置的。

    注意哦!

    簇点链路: 通俗的讲是上下文Context中的逻辑链路,context内部包含着从entranceNode出发的到受保护资源的至少一条链路,不同使用方式,链路树可能会不一样哦,entranceNode往往作为调用方!

    Sentinel 里面的各种种类的统计节点,即簇点链路是由一个个 Node 组成的:

    在这里插入图片描述

    所有的节点都可以记录对资源的访问统计数据,因为都是 StatisticNode 的子类,StatisticNode 是最为基础的统计节点,包含秒级和分钟级两个滑动窗口结构

    /**
     * 定义了一个使用数组保存数据的计量器(以"秒"为单位)
     * SAMPLE_COUNT:样本窗口数量,默认值为 2
     * INTERVAL:时间窗长度,默认值为 1000 毫秒
     */
    private transient volatile Metric rollingCounterInSecond = new ArrayMetric(SampleCountProperty.SAMPLE_COUNT,
        IntervalProperty.INTERVAL);
    
    /**
     * 定义了一个使用数组保存数据的计量器(以"分"为单位)
     */
    private transient Metric rollingCounterInMinute = new ArrayMetric(60, 60 * 1000, false);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    节点 Node 按照作用分为以下几类:

    • DefaultNode:链路节点,用于统计调用链路上某个资源的数据,维持树状结构,即代表链路树中的每一个资源。一个资源出现在不同链路中时,会创建不同的 DefaultNode 节点

    • ClusterNode:簇点,代表资源,一个资源不管出现在多少链路中,只会有一个 ClusterNode,用于记录当前资源被访问的所有统计数据之和。即用于统计每个资源全局的数据(不区分调用链路),以及存放该资源的按来源区分的调用数据(类型为 StatisticNode)。特别是 Constants.ENTRY_NODE 节点用于统计全局的入口资源数据

    • EntranceNode入口节点,特殊的链路节点(DefaultNode),对应某个 Context 入口的所有调用数据。Constants.ROOT 节点也是入口节点。

    例如:

    现在两个业务接口:

    业务 1:Controller 中的资源 /order/query 访问了 Service 中的资源 /cat
    业务 2:Controller 中的资源 /order/save 访问了 Service 中的资源 /cat

    创建的链路图如下:

    在这里插入图片描述
    Node 节点总结:

    • Node:用于完成数据统计的接口;
    • StatisticNode:统计节点,是 Node 接口的实现类,用于完成数据统计;
    • EntranceNode:入口节点,一个 Context 会有一个入口节点,用于统计当前 Context 的总体流量数据;
    • DefaultNode:默认节点,用于统计一个资源在当前 Context 中的流量数据;
    • ClusterNode:集群节点,用于统计一个资源在所有 Context 中的总体流量数据;
    7、Slot插槽

    1、NodeSelectorSlot 用户调用链路构建

    负责收集资源的路径,并将这些资源的调用路径,以树状结构存储起来,用于根据调用路径来限流降级。

    ContextUtil.enter("entrance1", "appA");
     Entry nodeA = SphU.entry("nodeA");
     if (nodeA != null) {
        nodeA.exit();
     }
     ContextUtil.exit();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    上述代码通过 ContextUtil.enter() 创建了一个名为 entrance1 的上下文,同时指定调用发起者为 appA;接着通过 SphU.entry()请求一个 token,如果该方法顺利执行没有抛 BlockException,表明 token 请求成功。

    根据调用者的不同(entranceNode)(context)不同,同一资源存在者,多种调用链路

    ContextUtil.enter("entrance1", "appA");
      Entry nodeA = SphU.entry("nodeA");
      if (nodeA != null) {
        nodeA.exit();
      }
      ContextUtil.exit();
    
    ContextUtil.enter("entrance2", "appA");
      nodeA = SphU.entry("nodeA");
      if (nodeA != null) {
        nodeA.exit();
      }
      ContextUtil.exit();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    在内存中生成以下结构

                       machine-root
                       /         \
                      /           \
              EntranceNode1   EntranceNode2
                    /               \
                   /                 \
           DefaultNode(nodeA)   DefaultNode(nodeA)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    2、ClusterBuilderSlot ,统计簇点构建(资源的所有链路数据统计)

    用于存储资源的统计信息以及调用者信息,例如该资源的 RT,QPS,thread count,Block count,Exception count 等等,这些信息将用作为多维度限流,降级的依据。简单来说,就是用于构建 ClusterNode。

    3、StatisticSlot 数据统计

    StatisticSlot 是 Sentinel 最为重要的类之一,用于根据规则判断结果进行相应的统计操作。用于记录、统计不同纬度的 runtime 指标监控信息

    • 执行 entry() 方法的时候:依次执行后面的判断 slot。每个 slot 触发流控的话会抛出异常(BlockException 的子类)。若有 BlockException 抛出,则记录 block 数据;若无异常抛出则算作可通过(pass),记录 pass 数据。

    • 执行 exit() 方法的时候:若无 error(无论是业务异常还是流控异常),记录 complete(success)以及 RT,线程数-1。

    • 记录数据的维度:线程数+1、记录当前 DefaultNode 数据、记录对应的 originNode 数据(若存在 origin)、累计 IN 统计数据(若流量类型为 IN)。

    StatisticSlot 是 Sentinel 的核心功能插槽之一,用于统计实时的调用数据。

    • clusterNode:资源唯一标识的 ClusterNode 的实时统计
    • origin:根据来自不同调用者的统计信息
    • defaultNode: 根据入口上下文区分的资源 ID 的 runtime 统计
    • 入口流量的统计

    4、ParamFlowSlot

    热点参数限流,对应热点流控。
    热点参数限流是分别统计参数值相同的请求,判断是否超过 QPS 阈值。

    5、FlowSlot

    用于根据预设的限流规则以及前面 slot 统计的状态,来进行流量控制。对应流控规则。

    这个 slot 主要根据预设的资源的统计信息,按照固定的次序,依次生效。如果一个资源对应两条或者多条流控规则,则会根据如下次序依次检验,直到全部通过或者有一个规则生效为止:

    三种流控模式:

    • 直接模式:统计当前资源的请求,触发阈值时对当前资源直接限流,也是默认的模式
    • 关联模式:统计与当前资源相关的另一个资源,触发阈值时,对当前资源限流
    • 链路模式:统计从指定链路访问到本资源的请求,触发阈值时,对指定链路限流

    三种流控模式,从底层数据统计角度来看,分为两类

    • 对进入资源的所有请求(ClusterNode)做限流统计:直接模式、关联模式
    • 对进入资源的部分链路(DefaultNode)做限流统计:链路模式

    三种流控效果:

    • 快速失败:达到阈值后,新的请求会被立即拒绝并抛出 FlowException 异常。是默认的处理方式,基于滑动时间窗口算法。
    • warm up:预热模式,对超出阈值的请求同样是拒绝并抛出异常。但这种模式阈值会动态变化,从一个较小值逐渐增加到最大阈值。基于滑动时间窗口算法,只不过阈值是动态的
    • 排队等待:让所有的请求按照先后次序排队执行,两个请求的间隔不能小于指定时长,基于漏桶算法。
      滑动时间窗的功能分两部分:

    6、GatewayFlowSlot 同上

    7、AuthoritySlot

    来源访问控制

    根据配置的黑白名单和调用来源信息,来做黑白名单控制。对应授权规则 。

    白名单:来源(origin)在白名单内的调用者允许访问
    黑名单:俩与(origin)在黑名单内的调用者不允许访问

    8、DegradeSlot

    熔断降级

    通过统计信息及预设的规则,来做熔断,对应降级规则。

    熔断降级是解决雪崩问题的重要手段,其思路是由断路器统计服务调用的异常比例、慢请求比例,如果超出阈值则会熔断该服务。即拦截访问该服务的一切请求,而当服务恢复时,断路器会放行访问该服务的请求

    在这里插入图片描述

    器熔断策略有三种:慢调用、异常比例、异常数

    • 慢调用:业务的响应时长(RT)大于指定时长的请求被认定为慢调用请求。在指定时间内,如果请求数量超过设定的最小数量,慢调用比例大于设定的阈值,则触发熔断。例如下图配置,RT 超过 500ms 的调用是慢调用,统计最近 10000ms 内的请求,如果请求量超过 10 次,并且慢调用比例不低于 0.5,则触发熔断,熔断时长为 5s,然后进入 half-open 状态,放行一次请求做测试
    • 异常比例或异常数:统计指定时间内的调用,如果调用次数超过指定请求数,并且出现异常的比例达到设定的比例阈值(或超过指定异常数),则触发熔断。例如下图配置,统计最近 1000ms 内的请求,如果请求超过 10 次,并且异常比例不低于 0.4,则触发熔断,熔断时长为 5s,然后进入 half-open 状态,放行一次请求做测试

    9、SystemSlot

    系统保护

    通过系统的状态,例如 load1 等,来控制总的入口流量。对应系统规则。

    这个 slot 会根据对于当前系统的整体情况,对入口资源的调用进行动态调配。其原理是让入口的流量和当前系统的预计容量达到一个动态平衡。

    注意系统规则只对入口流量起作用(调用类型为 EntryType.IN),对出口流量无效。可通过 SphU.entry(res, entryType) 指定调用类型,如果不指定,默认是 EntryType.OUT

  • 相关阅读:
    柬埔寨市场最全开发攻略
    尝试+坚持=成功
    ArcgisForJS如何实现添加含图片样式的点要素?
    C语言 | Leetcode C语言题解之第401题二进制手表
    PAT(Advanced Level) Practice(with python)——1118 Birds in Forest
    说说商标注册
    Flink学习笔记(九)状态编程
    Docker镜像安全深度扫描
    PHP+茶叶商城系统 毕业设计-附源码211121
    带你深入了解Fragment懒加载
  • 原文地址:https://blog.csdn.net/qq_44787816/article/details/127657659