Sentinel 核心实现剖析:SlotChain、SPI、限流算法与熔断降级

发布时间:2026/6/22 11:15:00
Sentinel 核心实现剖析:SlotChain、SPI、限流算法与熔断降级 Sentinel 核心实现剖析SlotChain、SPI、限流算法与熔断降级一、SPI 扩展点体系Sentinel 大量使用 SPIService Provider Interface机制实现组件的动态加载与替换。与 Java 原生ServiceLoader不同Sentinel 自定义了一套更灵活的 SPI 框架支持按别名加载、优先级排序以及默认实现回退。核心类为SpiLoader入口通过SpiLoader.of(ClassT service)获取指定接口的加载器然后通过loadDefault()、loadInstance(String alias)等方法实例化实现。实现类通过Spi注解标记并可以指定value作为别名。若存在多个实现可以通过Spi(order -100)控制优先级。Sentinel 中通过 SPI 加载的关键扩展点包括SlotChainBuilder负责构建 ProcessorSlotChain。ProcessorSlot构成 SlotChain 的单个节点如FlowSlot、DegradeSlot等。InitFunc初始化回调用于启动时执行一些初始化逻辑如加载规则数据源。MetricCallback监控指标输出回调。CommandCenter与 Dashboard 通信的命令中心实现。TransportClientFactoryDashboard 通信的客户端工厂。示例通过 SPI 获取默认SlotChainBuilder的代码路径SlotChainBuilderbuilderSpiLoader.of(SlotChainBuilder.class).loadDefaultInstance();其中DefaultSlotChainBuilder标注了Spi(isDefault true)因此被当作默认实现加载。二、ProcessorSlotChain 的构建过程Sentinel 的拦截逻辑由一条责任链SlotChain完成。链的构建入口在CtSph.lookProcessChain()方法中。对于每个资源首次被访问时会触发链的创建。创建流程调用SlotChainProvider.newSlotChain()。通过SpiLoader.of(SlotChainBuilder.class).loadFirstInstanceOrDefault()加载SlotChainBuilder默认实现为DefaultSlotChainBuilder。DefaultSlotChainBuilder.build()方法中遍历所有通过 SPI 加载的ProcessorSlot实现类并按照Spi注解的order升序排列依次调用addLast()构建单向链表。最终形成的链结构如下按 order 排序NodeSelectorSlot - ClusterBuilderSlot - LogSlot - StatisticSlot - AuthoritySlot - SystemSlot - FlowSlot - DegradeSlot每个 Slot 的实现类通过Spi标注并携带order值。默认实现分别位于对应的 Slot 类中如DefaultNodeSelectorSlot。每个 Slot 持有下一个 Slot 的引用entry()方法内部调用fireEntry(context, resourceWrapper, node, count, prioritized, args)传递到下游。自定义 Slot 可以通过 SPI 方式插入链中只需实现ProcessorSlot接口使用Spi(order ...)注解指定位置并添加到 SPI 配置文件中。三、StatisticSlot 与滑动窗口统计StatisticSlot是负责调用统计的核心 Slot。在entry()方法中会调用后续 Slot 链执行业务逻辑并在调用前后记录指标。其统计基于Metric接口默认实现为ArrayMetric内部使用滑动窗口LeapArrayMetricBucket。滑动窗口结构LeapArray总长度intervalInMs默认为 1000ms划分为sampleCount个桶默认 2 个每个 500ms。每个桶是WindowWrapMetricBucket包装了一个MetricBucket对象。MetricBucket内部使用LongAdder累加 PASS、BLOCK、SUCCESS、EXCEPTION 计数以及 RT 总和、最小 RT 等。当前时间所在的桶通过currentWindow()获取。如果当前桶过期时间戳 窗口长度 当前时间则重置桶数据并移动到新位置。滑动窗口的“滑动”体现在计算总量时会累加当前窗口内的所有桶当前桶和上一个不活动的桶取决于时间范围例如要计算过去 1 秒的 QPS就会累加所有未被淘汰的桶。MetricBucket累加通过addPass(int n)等方法完成内部LongAdder.add(n)。RT 的统计通过addRt(long rt)累加计算平均 RT 时用rtTotal除以successCount。核心代码片段简化publiclongpass(){data.currentWindow();// 滚动到当前窗口longsum0;for(WindowWrapMetricBucketwrap:data.list()){sumwrap.value().pass();}returnsum;}这种设计的优势是完全无锁LongAdder统计开销极低且支持秒级精度。四、FlowSlot 与限流算法实现FlowSlot负责基于配置的流控规则进行限流。规则通过FlowRuleManager加载支持按资源、流控模式直接、关联、链路以及流控效果快速失败、Warm Up、排队等待进行判断。流控核心类为FlowRuleChecker它持有多个TrafficShapingController的实现类根据controlBehavior选择不同的限流算法快速失败DefaultController—— 基于滑动窗口的 QPS 或线程数比较。Warm UpWarmUpController—— 令牌桶预热算法。排队等待RateLimiterController—— 漏桶算法精确限速。4.1 DefaultController快速失败实现最简单从StatisticNode获取当前 QPS 或线程数与规则中的count阈值比较若超过则抛出FlowException。对于 QPS 模式直接读取node.passQps()线程数模式读取node.curThreadNum()。4.2 WarmUpController预热基于 Guava 的SmoothWarmingUp思想实现了一个令牌桶。核心参数count稳定后的 QPS 阈值。warmUpPeriodSec预热时间秒。coldFactor冷启动因子默认为 3表示初始阈值只有稳定阈值的 1/3。内部维护storedTokens当前令牌数、lastFilledTime上次填充时间。每次请求到来时先根据时间差补充令牌速率逐步增加然后尝试扣除一个令牌。若令牌不足则直接阻塞实际上 Sentinel 的实现中预热模式并不支持排队令牌不足时抛出FlowException。关键代码逻辑简化publicbooleancanPass(Nodenode,intacquireCount,booleanprioritized){longcurrentTimeTimeUtil.currentTimeMillis();syncToken(currentTime);// 根据时间填充令牌if(storedTokenswarningToken){// 预热期令牌消耗速率较慢longoldStoredTokensstoredTokens;storedTokens-acquireCount;if(storedTokens0){storedTokensoldStoredTokens;returnfalse;}}else{// 稳定期storedTokens-acquireCount;if(storedTokens0){returnfalse;}}returntrue;}其中syncToken会根据当前时间和预热斜率计算应该补充的令牌数令牌生成速率从低到高逐渐增加。4.3 RateLimiterController排队等待使用漏桶思想通过latestPassedTime记录最近一次请求通过的时间。计算下一次请求允许通过的时间latestPassedTime costTime其中costTime 1.0 / count * 1000 ms。如果该时间大于当前时间说明请求需要等待若等待时间超过maxQueueingTimeMs用户配置的最大排队超时则拒绝否则休眠等待到允许时间后放行并更新latestPassedTime。publicbooleancanPass(Nodenode,intacquireCount,booleanprioritized){longcurrentTimeTimeUtil.currentTimeMillis();longcostTimeMath.round(1.0/count*1000);// 每个请求的间隔longexpectedTimecostTimelatestPassedTime.get();if(expectedTimecurrentTime){// 无需等待直接通过latestPassedTime.set(currentTime);returntrue;}else{longwaitTimecostTimelatestPassedTime.get()-currentTime;if(waitTimemaxQueueingTimeMs){returnfalse;}// 等待try{Thread.sleep(waitTime);}catch(InterruptedExceptione){returnfalse;}latestPassedTime.addAndGet(costTime);returntrue;}}注意这里使用了AtomicLong的latestPassedTime保证线程安全但实际可能存在并发竞争导致实际通过速率略高于配置值。不过 Sentinel 默认容忍这种轻微的超量认为其在实际场景中可接受。五、DegradeSlot 与熔断降级实现DegradeSlot依赖DegradeRuleManager加载的熔断规则。熔断器状态由CircuitBreaker接口管理针对不同的熔断策略有不同的实现SlowRequestCircuitBreaker慢调用比例。ExceptionCircuitBreaker异常比例和异常数内部通过LeapArray统计异常和慢请求。每种CircuitBreaker都维护了一个状态机CLOSED、OPEN、HALF-OPEN。状态转换的逻辑在tryPass()方法中从StatisticNode获取当前滑动窗口内的统计数据。根据策略判断是否达到熔断阈值例如异常比例超过errorRatio。如果是 CLOSED 状态且达到阈值则转为 OPEN记录熔断开始时间。如果是 OPEN 状态检查是否超过timeWindow若超过则转为 HALF-OPEN允许试探请求。HALF-OPEN 状态下若试探请求失败重新 OPEN成功则 CLOSED。SlowRequestCircuitBreaker的特殊之处在于它检查慢请求的比例而慢请求的判断是在StatisticSlot中比较 RT 是否超过maxAllowedRt完成的。代码结构简化publicbooleantryPass(Contextcontext){if(stateState.CLOSED){// 检查是否触发熔断if(isOverThreshold()){stateState.OPEN;nextRetryTimestampTimeUtil.currentTimeMillis()rule.getTimeWindow()*1000;returnfalse;}returntrue;}elseif(stateState.OPEN){if(TimeUtil.currentTimeMillis()nextRetryTimestamp){stateState.HALF_OPEN;returntrue;// 允许一个试探请求}returnfalse;}else{// HALF_OPEN// 后续通过探活回调改变状态returntrue;}}探活成功/失败由onSuccess/onFailure方法回调在StatisticSlot中调用。六、系统自适应保护 SystemSlotSystemSlot检查系统级规则规则由SystemRuleManager加载。它基于操作系统的指标Load、CPU 使用率、JVM 线程数、平均 RT、入口 QPS 等做出判断。指标获取方式Load通过OperatingSystemMXBean.getSystemLoadAverage()仅 Linux 有效。CPU 使用率Sentinel 通过SentinelConfig自己采集利用ManagementFactory.getOperatingSystemMXBean()和线程睡眠计算相对 CPU 时间。平均 RT、入口 QPS取自全局入口ClusterNode的统计数据。线程数Thread.activeCount()。当任一指标超过规则设定的阈值时SystemSlot抛出SystemBlockException。需要注意的是系统规则是全局性的不区分资源。七、热点参数限流 ParamFlowSlotParamFlowSlot依靠ParamFlowRuleManager加载的规则对带有参数的资源调用进行细粒度控制。其核心挑战在于统计维度爆炸每个资源 参数索引 参数值都需要独立的计数器。Sentinel 采用 LRU 淘汰的ConcurrentLinkedHashMap基于com.googlecode.concurrentlinkedhashmap存储参数统计信息key 为参数值value 为ParameterMetric内部同样使用滑动窗口统计 QPS。当请求进入时获取指定索引的参数值在对应资源的ParameterMetric中查找该参数的CacheMap累加计数并根据规则中的特定值阈值或全局阈值决定是否限流。如果没有命中特定值则使用通用阈值count。LRU 策略防止内存无限增长当参数值基数极大时不活跃的参数会被自动淘汰但这也意味着对于长尾参数流量可能不受限。八、初始化与资源调用入口Sentinel 的初始化通过InitExecutor.doInit()完成它会利用 SPI 加载所有InitFunc实现并执行。常见的InitFunc实现包括CommandCenterInitFunc启动 Dashboard 通信的 HTTP Server默认 Netty。MetricCallbackInitFunc注册 Metric 回调。DefaultClusterClientInitFunc集群流控客户端初始化可选。资源调用入口是SphU.entry()内部逻辑获取或创建Context通过ContextUtil。通过CtSph.lookProcessChain(resourceWrapper)获取或构建SlotChain。调用chain.entry(context, resourceWrapper, count, prioritized, args)触发整条链。返回Entry业务代码执行后需调用entry.exit()触发StatisticSlot中的 exit 逻辑记录 RT 等。九、与 Spring Cloud 及微服务生态的集成细节Sentinel 为 Spring Cloud 提供了自动配置模块其中SentinelWebAutoConfiguration会注册一个SentinelWebInterceptor或SentinelWebFluxFilter拦截所有 Web 请求自动创建资源和上下文。对于 FeignSentinelFeignAutoConfiguration会通过Feign.builder()的contract构建代理时插入SentinelInvocationHandler实现降级回退。对于 GatewaySentinelGatewayAutoConfiguration注册SentinelGatewayFilter并将路由 ID 作为资源名。这些自动配置大部分利用了 Spring 的BeanPostProcessor和 AOP 机制将 Sentinel 的防护能力透明化。十、总结Sentinel 的内部实现围绕 SlotChain 责任链展开通过 SPI 保证组件可插拔滑动窗口统计使用无锁LongAdder提供高性能指标采集限流算法覆盖快速失败、令牌桶预热和漏桶排队满足不同场景熔断降级通过状态机维护探活逻辑系统自适应保护基于 OS 指标兜底。掌握这些底层机制有助于在极端流量场景下准确理解 Sentinel 的行为以及进行合理的规则配置和扩展。