本文介绍Alibaba Sentinel 中实现 熔断器(Circuit Breaker)模式 的核心部分,基于 Martin Fowler 提出的经典 Circuit Breaker 模式。下面我将从 设计思想、状态流转、关键逻辑 和 潜在问题(如 #1638) 四个维度为你系统解析。
一、整体架构概览
Sentinel 的熔断器分为三层:
- 接口层 :
CircuitBreaker- 定义了熔断器的基本行为:获取规则、尝试放行、记录完成、查询状态。
- 抽象基类 :
AbstractCircuitBreaker- 实现通用状态管理(OPEN / HALF_OPEN / CLOSED)、超时重试、状态通知等。
- 具体策略实现 :
ResponseTimeCircuitBreaker- 基于 响应时间(RT) 的熔断策略:当慢请求比例超过阈值,触发熔断。
这是典型的 策略模式 + 模板方法模式 的结合。
二、熔断器三种状态详解(来自注释)
| 状态 | 行为 |
|---|---|
CLOSED |
正常放行所有请求。当指标(如慢请求率)超标 → 切换到 OPEN |
OPEN |
拒绝所有请求 ,直到 recoveryTimeoutMs 超时 → 尝试切换到 HALF_OPEN |
HALF_OPEN |
只放行一个探测请求(probe) : - 成功 → 回到 CLOSED - 失败(如超时/异常)→ 回到 OPEN |
这和 Martin Fowler 文章中的描述完全一致。
三、关键方法解析
1. tryPass(Context context)
-
作用:判断当前请求是否可以放行。
-
逻辑 :
javaif (CLOSED) → true if (OPEN) → 检查是否到了重试时间?如果是,尝试转为 HALF_OPEN 并放行一次 if (HALF_OPEN) → false(因为已经在放行那个"探测请求"了,不能再放) -
注意:
fromOpenToHalfOpen()会注册一个 回调钩子(whenTerminate),用于在请求结束后判断结果。
2. onRequestComplete(Context context)
- 作用 :当一个被放行的请求完成后,更新统计并决定是否要改变状态。
- 在
ResponseTimeCircuitBreaker中:- 计算本次请求 RT(响应时间)
- 如果 RT > 阈值 → 记为"慢请求"
- 更新滑动窗口统计(
slidingCounter) - 调用
handleStateChangeWhenThresholdExceeded(rt)
特别处理 HALF_OPEN 状态:
java
if (currentState == HALF_OPEN) {
if (rt > maxAllowedRt) {
fromHalfOpenToOpen(1.0d); // 探测失败,重回 OPEN
} else {
fromHalfOpenToClose(); // 探测成功,恢复 CLOSED
}
}
✅ 这是熔断器自愈的核心逻辑。
3. fromOpenToHalfOpen() 中的 #1638 临时修复
这是你提供的知识库中提到的关键问题:
问题 :如果一个请求在
HALF_OPEN状态下被放行,但被其他规则(如流控、授权)拦截了 (即entry.getBlockError() != null),那么它永远不会调用onRequestComplete(),导致熔断器卡在HALF_OPEN永远不恢复。
修复方案(临时 workaround):
java
entry.whenTerminate((context, entry) -> {
if (entry.getBlockError() != null) {
// 被其他规则 block 了 → 视为探测失败
currentState.compareAndSet(HALF_OPEN, OPEN);
notifyObservers(...);
}
});
whenTerminate是 Sentinel 的钩子,在 Entry 结束时(无论成功/失败/block)都会触发。- 如果发现是 被 block (而非正常完成或异常),就主动回退到 OPEN 状态,避免死锁。
✅ 这就是对 GitHub Issue #1638 的应对措施。
四、ResponseTimeCircuitBreaker 的熔断条件
基于三个配置参数(来自 DegradeRule):
| 参数 | 含义 |
|---|---|
count |
最大允许 RT(毫秒),例如 200ms |
slowRatioThreshold |
慢请求比例阈值,例如 0.5(50%) |
minRequestAmount |
最小请求数(低于此值不触发熔断) |
timeWindow |
熔断持续时间(秒) |
statIntervalMs |
统计窗口长度(毫秒) |
触发熔断条件:
java
if (totalCount >= minRequestAmount) {
double ratio = slowCount / totalCount;
if (ratio > maxSlowRequestRatio ||
(ratio == maxSlowRequestRatio && maxSlowRequestRatio == 1.0)) {
transformToOpen(ratio);
}
}
注意:当阈值是 1.0(100%)时,即使相等也要熔断(防止边界漏判)。
五、总结:Sentinel 熔断器工作流程
CLOSED OPEN & 超时 OPEN & 未超时 HALF_OPEN 正常完成 被 block 异常 是 是 否 是 请求到来 tryPass? 放行 转 HALF_OPEN, 放行一次 拒绝 拒绝(已放行探测) 执行业务 完成 or 异常 or 被 block? onRequestComplete: 更新统计 whenTerminate: 回退到 OPEN 是否 HALF_OPEN? RT > 阈值? OPEN CLOSED 是否 CLOSED 且 比例超标? OPEN
六、延伸思考
-
为什么用
AtomicReference<State>?→ 保证状态变更的原子性,避免并发竞争。
-
滑动窗口(LeapArray)的作用?
→ 实现时间窗口内的请求统计(类似限流中的滑动窗口),支持高并发下的高效计数。
-
能否支持更多熔断策略?
→ 当然!Sentinel 还有
ExceptionCircuitBreaker(基于异常比例),只需继承AbstractCircuitBreaker即可。 -
#1638 的根本解法?
→ 当前是"探测请求被 block 就视为失败"的 workaround。理想方案可能是:区分"业务失败"和"规则拦截",或引入更精细的状态机。
如果你正在使用 Sentinel 或参与其开发,理解这套机制对排查熔断问题(如"为什么一直熔断?"、"为什么不恢复?")非常关键。需要我进一步解释某一部分(比如 LeapArray 或 whenTerminate 机制),欢迎继续提问!