当前位置: 首页 > news >正文

Sentinel原理之责任链详解

文章目录

  • 1. 基础知识
  • 2. 责任链统计类Slot
    • 2.1 NodeSelectorSlot
    • 2.2 ClusterBuilderSlot
    • 2.3 StatisticSlot
    • 2.4 LeapArray滑动窗口
    • 2.5 资源节点树
  • 3. 责任链规则类Slot
    • 3.1 SystemSlot
    • 3.2 FlowSlot
    • 3.3 DegradeSlot

1. 基础知识

Sentinel的核心在于其轻量级责任链插槽,主要实现类如下:

  1. NodeSelectorSlot:构建资源调用树,关联上下文和资源节点
  2. ClusterBuilderSlot:创建全局资源节点,聚合跨链路统计信息
  3. StatisticSlot:核心统计引擎,实时记录通过/阻塞/异常等指标,滑动窗口即在此实现
  4. SystemSlot:系统保护相关判断,如CPU和线程数等
  5. FlowSlot:流量控制,通常是配置QPS,可指定流量控制行为,如预热、快速失败和排队等策略
  6. DegradeSlot:熔断降级,配置基于慢调用比例、异常数量或比例配置降级条件

责任链的调用顺序是固定的,不能随意修改

调用链主要分为两种:

  1. 统计类Slot:如NodeSelectorSlot、ClusterBuilderSlot和StatisticSlot,用于收集资源指标,为规则判断提供数据
  2. 规则类Slot:基于统计数据执行控制逻辑

责任链可通过SPI机制自定义Slot,自定义的Slot需要实现ProcessorSlot接口,可使用@SpiOrder注解指定执行顺序,若未指定执行顺序在最后面

若未指定Context的名称,其默认名称为sentinel_default_context

同一个线程的Context是一样的,同一个资源名称使用的一直是同一个责任链

在资源树中,节点一共有四种类型:

  1. EntranceNode:继承DefaultNode,可以看成是一个资源树的根节点
  2. DefaultNode:继承StatisticNode,记录了资源信息、子节点信息和ClusterNode节点信息
  3. ClusterNode:继承StatisticNode,记录了资源名称和资源类型
  4. StatisticNode:继承自接口Node,内部有滑动窗口实现类ArrayMetric,分别支持秒和分钟的固定统计窗口,还有线程数量

在调用SphU.entry()方法时,若entry()直接抛出异常,Entry会自动记录BlockException,若要追踪系统普通异常,则需要依赖于Tracer.trace()方法来记录异常到Entry中以方便后续判断

调用Entry.exit()方法时会自动调用ContextUtil.exit(),前提是未显式创建Context,若显式创建了Context,则需要手动调用Context的exit()方法

调用Entry.exit()方法时,其执行步骤和entry()相反,主要是根据实际执行结果更新通过数、RT等

每次调用完entry()方法后需要调用exit()方法,否则会导致线程计数持续占用,导致误识别熔断限流,以及Entry未被释放引起内存泄漏

使用SphU.asyncEntry()时,需要在异步线程中同步调用exit()方法

2. 责任链统计类Slot

2.1 NodeSelectorSlot

资源树构建Slot,新建节点的条件:

  1. 线程不是同一个:核心为上下文名称不是同一个
  2. 资源名称不是同一个:每个资源都有独属于自己对应的Slot链

只要满足上面的任一条件就会新建一个DefaultNode节点,一个上下文对应一个EntranceNode节点

在新建DefaultNode节点时会构建各个节点的父子关系,在调用exit时从子节点回溯到父节点

2.2 ClusterBuilderSlot

同一个Slot链下该Slot中固定只有一个,和资源名称一一对应的

在该类中保存了不同资源对应的ClusterNode节点

其新增条件只有一个:资源名称不同

在该类中如果上下文的origin属性不为空,ClusterNode节点会创建origin属性对应的StatisticNode,并保存不同origin属性对应的节点

2.3 StatisticSlot

该Slot只做统计相关的事情,在entry()方法时优先执行完后面的Slot,再统计数据

entry()方法只记录DefaultNode和ClusterNode的线程数量和请求数量,如果origin属性不为空则记录StatisticNode的线程数量和请求数量

当Slot抛出异常时记录该异常,抛出异常是BlockException记录到blockError中,其它异常记录在error中,以方便exit()方法记录判断

exit()方法记录DefaultNode和ClusterNode的响应时间和异常数量(如果有的话),如果origin属性不为空则记录StatisticNode的响应时间和异常数量(如果有的话)

StatisticSlot主要操作的是StatisticNode中的秒级和分级滑动窗口,以及线程数量对象

2.4 LeapArray滑动窗口

在Sentinel的StatisticNode中,滑动窗口的实现类是ArrayMetric,该类中有LeapArray实现类,支持设置分片数量和每片时间间隔

在StatisticNode中,秒级窗口有2个分片,间隔为1000ms,每片时长500ms;分级窗口有60个分片,间隔为60000ms,每片时长1000ms

以秒级滑动窗口为例:

  • 数据结构
    • LeapArray:长度=2的AtomicReferenceArray<WindowWrap> 数组
    • WindowWrap:内有窗口时长windowLengthInMs、窗口开始时间windowStart和MetricBucket对象
    • MetricBucket:最小响应时间minRt和LongAdder[]类型的counters对象,counters长度为6,索引对应了MetricEvent枚举对象
  • 索引计算
    • sampleCount=2;windowLengthInMs=500
    • 时间窗口索引公式:idx = ( timestamp / windowLengthInMs ) % sampleCount
    • 窗口起始时间公式:windowStart = timestamp - timestamp % windowLengthInMs

此时假设需要均匀处理分布在100ms的100个请求,核心计算流程如下:

阶段 1:0ms → 500ms(填充第一个窗口)

  • 请求分布:时间点:0ms、100ms、200ms、300ms、400ms(共 5 个请求)。
  • 窗口操作
    • 所有请求的时间戳均落在 [0ms, 500ms) 区间。
    • 计算索引:idx = (timestamp / 500) % 2 = 0(所有请求均指向索引 0)。
  • 窗口创建与更新
    • 首个请求(0ms)
      • 索引 0 为空 → 创建新窗口 WindowWrap0,windowStart=0ms。
      • MetricBucket 初始化,LongAdder 数组清零。
      • 请求计数:PASS 指标 +1。
    • 后续请求(100ms、200ms、300ms、400ms)
      • 复用 WindowWrap0,更新 MetricBucket:每次请求使 PASS 计数 +1。
  • 当前窗口状态
    • 索引 0:PASS=5,其他指标(如 BLOCK)为 0。
    • 索引 1:仍为空。

阶段 2:500ms → 1000ms(切换至第二个窗口)

  • 请求分布:时间点:500ms、600ms、700ms、800ms、900ms(共 5 个请求)。
  • 窗口操作
    • 500ms 请求
      • 计算索引:idx = (500/500)%2 = 1,windowStart=500ms。
      • 索引 1 为空 → 创建 WindowWrap1,windowStart=500ms。
      • PASS 计数 +1。
    • 600ms–900ms 请求
      • 均落在 [500ms, 1000ms),索引 idx=1 → 复用 WindowWrap1,PASS 计数累加至 5。
  • 当前窗口状态
    • 索引 0:PASS=5(数据保留,但窗口已过期)。
    • 索引 1:PASS=5。

阶段 3:1000ms → 1500ms(复用并重置第一个窗口)

  • 请求分布:时间点:1000ms、1100ms、1200ms、1300ms、1400ms(共 5 个请求)。
  • 窗口操作
    • 1000ms 请求
      • 索引计算:idx = (1000/500)%2 = 0,windowStart=1000ms。
      • 检查索引 0:原窗口 windowStart=0ms(已过期)→ 重置窗口
        • 清空 MetricBucket 计数器(PASS 归零)。
        • 更新 windowStart=1000ms。
      • 新请求计数:PASS=1。
    • 1100ms–1400ms 请求
      • 复用索引 0 窗口,PASS 计数累加至 5。
  • 当前窗口状态
    • 索引 0:PASS=5(新周期数据)。
    • 索引 1:PASS=5(保留,但将在 1500ms 后过期)。

阶段 4:后续请求(循环复用窗口)

  • 模式重复
    • 每 500ms 切换一次窗口索引(0 → 1 → 0 → 1)。
    • 到达窗口边界时(如 1500ms、2000ms),过期窗口被重置并复用。
  • 计数分布
    • 每个 500ms 窗口均匀接收 5 个请求(共 20 个窗口覆盖 10000ms)。
    • 每个窗口最终 PASS 计数均为 5。

上述流程关键机制说明:

  1. 窗口复用和重置:当该窗口的windowStart比计算出来的windowStart小时触发重置:
    • 更新分片的windowStart
    • 清空MetricBucket中的LongAdder数组数据
  2. 环形数组管理:通过数组长度和索引进行取模,保证索引一直在合法范围
  3. 技术性能优化:使用LongAdder存储指标,高并发使用时间分片减少竞争

2.5 资源节点树

资源节点树对于Sentinel而言就像JVM中的栈帧,其主要作用是记录资源入口调用链路

以下面的调用链路为例:

// 步骤1:调用资源A
try (Entry entryA = SphU.entry("ResourceA")) {// 步骤2:在资源A中调用资源Btry (Entry entryB = SphU.entry("ResourceB")) {// ... 业务逻辑}// 步骤3:在资源A中调用资源Ctry (Entry entryC = SphU.entry("ResourceC")) {// ... 业务逻辑}
}

其中每次调用SphU.entry()方法后,都会生成一个对应的资源节点,实际生产环境,调用链路具有很多分支,因此需要构建节点树来记录各个调用链路的关系。上述例子生成的资源节点树如下:

EntranceNode
resourceA=defaultNodeA
resourceB=defaultNodeB
resourceC=defaultNodeC

其资源对应派生节点关系:

defaultNodeA
clusterNodeA
originA1
originA2
originAN
statisticNodeA1
statisticNodeA2
statisticNodeAN
defaultNodeB
clusterNodeB
originB1
originB2
originBN
statisticNodeB1
statisticNodeB2
statisticNodeBN
defaultNodeC
clusterNodeC
originC1
originC2
originCN
statisticNodeC1
statisticNodeC2
statisticNodeCN

其对应的责任链一共有三条,分别对应resourceAresourceBresourceC

3. 责任链规则类Slot

规则类的Slot调用顺序从前到后分别是SystemSlot、FlowSlot和DegradeSlot,其调用顺序如下图:

系统过载
正常
触发限流
正常
熔断中
正常
请求进入
SystemSlot
抛出SystemBlockException
FlowSlot
抛出FlowException
DegradeSlot
抛出DegradeException
执行业务逻辑

3.1 SystemSlot

SystemSlot的统计数据来源必须要求入口类型EntryType是IN,常用的SphU.entry(String)其默认的Entrype是OUT

当EntryType是IN时,在StatisticSlot中会把线程数量、请求数、响应时长和异常数量都添加到全局静态对象ENTRY_NODE中,其对象类型是ClusterNode

系统规则使用SystemRuleManager维护,QPS、maxThread这些规则配置都直接维护在该管理类中。若配置的系统规则有多个,只会比较后取最合适的

在SystemSlot规则判断时,便会使用全局静态对象ENTRY_NODE统计的数据和SystemRuleManager维护的系统规则阈值进行判断,若超过了配置阈值则抛出SystemBlockException异常,后续的Slot将会跳过

3.2 FlowSlot

FlowSlot的统计数据根据规则配置来源节点不同:

  • StatisticNode
    • 入口配置了origin属性,FlowRule的limitApp和origin相同,strategy=DIRECT(默认)
    • FlowRule的limitApp=default,strategy=DIRECT(默认)
    • 入口配置了origin属性,FlowRule的limitApp=other,resource对应的limitApp!=origin
  • ClusterNode
    • FlowRule的limitApp=default(默认),strategy=DIRECT(默认)
    • FlowRule的strategy=RELATE,获取refResource对应的ClusterNode
  • DefaultNode
    • FlowRule的strategy=CHAIN,refResource=Context名称

FlowRule使用FlowRuleManager维护规则,在FlowRuleManager加载规则时,会根据controlBehavior属性来创建对应的流量控制器:

  • QPS控流
    • 慢启动:WarmUpController
    • 匀速队列:RateLimiterController
    • 慢启动匀速队列:WarmUpRateLimiterController
    • 默认:DefaultController
  • 线程数量控流:默认的DefaultController

流控器执行逻辑:

  • DefaultController:直接比较统计指标和阈值,超出阈值直接抛出FlowException
  • WarmUpController:基于令牌桶算法和冷启动曲线,动态调整阈值,超出阈值抛出FlowException
  • RateLimiterController:基于漏桶算法控制请求频率,若频率大于配置的阈值则让其等待,等待时间大于最大等待时间则抛出FlowExceptiona,小于则线程等待时间差值
  • WarmUpRateLimiterController:结合了慢启动和匀速排队的方式,使用慢启动逐步增大流量,并在增大途中或稳定后使用匀速队列控制请求速率

慢启动算法:

  • 假设FlowRule配置count=100QPS,即每秒生成100令牌,warmUpPeriodInSec=10s,默认coldFactor=3
    • warningToken=( warmUpPeriodInSec * count ) / (coldFactor - 1)=500
    • maxToken=warningToken + (2 * warmUpPeriodInSec * count / (1 + coldFactor))=1000
  • 请求到达时令牌操作
    • 桶中有令牌:消耗令牌并立即处理请求
    • 桶中无令牌:拒绝请求
  • 结合冷启动曲线
    • 剩余令牌数大于警戒线warningToken:冷启动阶段,根据斜率判断请求数量是否超过冷启动曲线阈值
    • 剩余令牌数小于警戒线warningToken:进入常态化请求阶段

漏桶算法:

  • 控制逻辑
    • count=100,即100QPS,固定间隔 = 1000 / 100 = 10ms
    • 请求到达时计算预期通过时间:期望时间 = 上次通过时间 + 固定间隔
    • 若当前时间 < 期望时间:
      • 计算等待时间 = 期望时间 - 当前时间
      • 若等待时间 > maxQueueingTimeMs拒绝请求,否则线程休眠等待时间
    • 实现效果
      • 突发100个请求到达,系统按10ms/请求匀速处理(每秒100个)
      • 若QPS超过阈值则根据配置时间等待或拒绝
  • 实际案例
    • 参数配置
      • count=100:固定间隔 = 10ms
      • maxQueueingTimeMs:最大等待时间 = 5ms
      • 上次通过时间 = -1ms(默认)
    • 第一次调用为0ms:上次通过时间 = 0ms
    • 第二次调用为6ms
      • 当前时间 = 6ms
      • 期望时间 = 上次通过时间 + 固定间隔 = 0 + 10 = 10ms
      • 等待时间= 期望时间 - 当前时间 = 4ms,
      • 小于maxQueueingTimeMs = 5ms,等待4ms后放行
      • 上次通过时间 = 上次通过时间 + 10ms = 0 + 10 = 10ms
    • 第三次调用时间为14ms
      • 当前时间 = 14ms
      • 期望时间 = 10 + 10 = 20ms
      • 等待时间 = 20 - 14 = 6ms
      • 大于maxQueueingTimeMs,拒绝
      • 上次通过时间 = 10ms
    • 第四次调用时间为17ms
      • 当前时间 = 17ms
      • 期望时间 = 10 + 10 = 20ms
      • 等待时间 = 20 - 17 = 3ms,小于maxQueueingTimeMs,等待3ms
      • 上次通过时间 = 10 + 10 = 20ms
    • 第五次调用时间为31ms
      • 当前时间 = 31ms
      • 期望时间 = 20 + 10 = 30ms
      • 由于当前时间 >= 期望时间,直接放行
      • 上次通过时间 = 当前时间 = 31ms

3.3 DegradeSlot

负责判断熔断限流,根据预设规则控制资源访问状态,防止系统因依赖服务不稳定而崩溃

支持三种熔断策略:

  1. 慢调用比例:统计周期statIntervalMs内,慢调用比例 > slowRatioThreshold、总请求数 > minRequestAmount时触发,响应时间 > count配置即算作慢调用
  2. 异常比例:统计周期statIntervalMs内,异常比例 > count,总请求数 > minRequestAmount时触发
  3. 异常数量:统计周期statIntervalMs内,异常总数 > count,总请求数 > minRequestAmount时触发

熔断状态管理:

  • CLOSED(关闭):熔断关闭,正常放行请求
  • OPEN(打开):熔断打开,拒绝所有请求,持续timeWindow配置的时间
  • HALF_OPEN(半开):熔断持续时间结束后进入该状态,试探性放1个请求,若请求正常切换为关闭,否则切换为打开

DegradeSlot原理

  • 调用entry():
    • 熔断状态=CLOSE:放行请求
    • 熔断状态=OPEN
      • 请求时间在熔断窗口timeWindow内:拒绝请求,抛出DegradeException
      • 请求时间在熔断窗口timeWindow后:仅单线程竞争,修改熔断状态为HALF_OPEN,竞争成功线程被放行,竞争失败线程被拒绝,抛出DegradeException
  • 调用exit():
    • 请求正常:正常退出
    • 抛出BlockException:正常退出
    • 抛出其它异常:使用资源对应的断路器进行后置判断
      • 熔断状态=CLOSE:记录对应失败指标
      • 熔断状态=HALF_OPEN:切换为OPEN状态

当熔断状态进行切换时会同步发送切换事件,可实现CircuitBreakerStateChangeObserver接口完成监听逻辑,并使用EventObserverRegistry.getInstance().addStateChangeObserver()方法完成添加监听器

熔断规则DegradeRule被DegradeRuleManager进行管理,在新增加载规则时,会为对应的熔断规则创建对应的CircuitBreaker(断路器),在DegradeSlot中便是使用断路器来判断请求是否该熔断

断路器原理:内部都是使用滑动窗口完成时间段内的统计判断,窗口仅一个,时间窗口大小=statIntervalMs配置

断路器类型和判断逻辑:

  • 慢调用断路器
    • 创建条件:grade=0
    • 统计窗口指标:记录总调用次数,若响应时间大于count,慢调用次数+1
    • 修改为OPEN状态
      • 窗口内总请求数 > minRequestAmount
      • 慢调用次数 / 总调用次数 > slowRatioThreshold
    • 修改为CLOSED状态
      • 当前状态 = HALF_OPEN
      • 响应时间 > count
  • 异常断路器:异常比例和异常数量使用同一个断路器进行判断
    • 创建条件:grade=1或2
    • 统计窗口指标:记录总调用次数,记录普通异常次数
    • 状态修改为OPEN条件
      • 窗口内总请求数 > minRequestAmount
      • 策略为异常比例时,errCount / totalCount > count
      • 策略为异常数量时,curCount > count
    • 修改为CLOSED状态
      • 当前状态 = HALF_OPEN
      • 当前响应无普通异常抛出
http://www.dtcms.com/a/319879.html

相关文章:

  • imx6ull-驱动开发篇12——GPIO子系统驱动LED
  • C++高频知识点(十五)
  • Qwen-Image开源模型实战
  • 【Floyd】Shortest Routes II
  • 显卡服务器的作用主要是什么?-哈尔滨云前沿
  • 使用内网穿透工具1分钟上线本地网站至公网可访问,局域网电脑变为服务器
  • Mysql数据仓库备份脚本
  • 2.7 (拓展)非父子通信(事件总线和provide-inject)详解
  • 2025 年华数杯全国大学生数学建模竞赛B题 网络切片无线资源管理方案设计--完整成品、思路、代码、模型结果分享,仅供学习~
  • java 生成pdf导出
  • 【tip】font-family的设置可能导致的文字奇怪展示
  • 《P3275 [SCOI2011] 糖果》
  • 运营商面向政企客户推出的DICT项目
  • 【ee类保研面试】数学类---概率论
  • 5G专网提高产业生产力
  • 别墅泳池设计综述:从理念创新到技术实现的系统性研究
  • 基于 PyTorch 从零实现 Transformer 模型:从核心组件到训练推理全流程
  • Java 大视界 -- Java 大数据在智能安防门禁系统中的人员行为分析与异常事件预警(385)
  • nvm安装,nvm管理node版本
  • Java设计模式总结
  • 【设计模式精解】什么是代理模式?彻底理解静态代理和动态代理
  • Vue自定义流程图式菜单解决方案
  • [激光原理与应用-171]:测量仪器 - 能量型 - 激光能量计(单脉冲能量测量)
  • DicomObjects COM 8.XX
  • VUE+SPRINGBOOT从0-1打造前后端-前后台系统-文章列表
  • [TIP 2025] 轻量级光谱注意力LSA,极致优化,减少99.8%参数,提升性能!
  • kafka安装与参数配置
  • MPC-in-the-Head 转换入门指南
  • 抖音、快手、视频号等多平台视频解析下载 + 磁力嗅探下载、视频加工(提取音频 / 压缩等)
  • 【性能测试】---测试工具篇(jmeter)