4. 熔断器状态机实现:基于有限状态机(FSM)的熔断器核心逻辑代码实现(Python伪代码)
熔断器的核心是一个有限状态机(FSM),它维护三个明确的状态:CLOSED(关闭)、OPEN(打开) 和 HALF_OPEN(半开)。状态之间的转换由预定义的阈值、时间窗口和探测结果驱动。本节将使用 Python 伪代码实现这个状态机的核心逻辑,并展示状态转换的完整流程。
4.1 状态定义与转换矩阵
在实现之前,先明确状态机的转换规则。下表定义了每个状态下的允许事件及目标状态:
| 当前状态 | 触发事件 | 条件 | 目标状态 | 动作 |
|---|---|---|---|---|
| CLOSED | 请求成功 | — | CLOSED | 重置失败计数 |
| CLOSED | 请求失败 | 失败率 < 阈值 | CLOSED | 递增失败计数 |
| CLOSED | 请求失败 | 失败率 ≥ 阈值 | OPEN | 启动超时计时器 |
| OPEN | 超时到期 | — | HALF_OPEN | 允许探测请求 |
| OPEN | 请求到达 | — | OPEN | 快速失败(拒绝) |
| HALF_OPEN | 探测成功 | — | CLOSED | 重置所有计数器 |
| HALF_OPEN | 探测失败 | — | OPEN | 重启超时计时器 |
设计要点: 状态转换必须原子化。在并发环境下,状态变更和计数器更新应使用锁或 CAS(Compare-And-Swap)操作,防止竞态条件导致状态不一致。
4.2 核心状态机实现(Python 伪代码)
以下代码实现了熔断器的 FSM 核心逻辑。为保持清晰,省略了线程安全细节,聚焦于状态转换和计数逻辑。
import time
from enum import Enum
class CircuitState(Enum):
CLOSED = 1
OPEN = 2
HALF_OPEN = 3
class CircuitBreaker:
def __init__(self, failure_threshold=5, recovery_timeout=30, half_open_max_requests=3):
self.state = CircuitState.CLOSED
self.failure_count = 0
self.success_count = 0
self.failure_threshold = failure_threshold
self.recovery_timeout = recovery_timeout # 秒
self.half_open_max_requests = half_open_max_requests
self.last_failure_time = 0
self.half_open_requests = 0
def call(self, func, fallback=None):
"""执行受熔断器保护的调用"""
if self.state == CircuitState.OPEN:
if time.time() - self.last_failure_time >= self.recovery_timeout:
self._transition_to_half_open()
else:
return self._fast_fail(fallback)
if self.state == CircuitState.HALF_OPEN:
if self.half_open_requests >= self.half_open_max_requests:
return self._fast_fail(fallback)
self.half_open_requests += 1
try:
result = func()
self._on_success()
return result
except Exception as e:
self._on_failure()
if fallback is not None:
return fallback(e)
raise
def _on_success(self):
if self.state == CircuitState.HALF_OPEN:
self._transition_to_closed()
elif self.state == CircuitState.CLOSED:
self.failure_count = 0 # 连续成功重置失败计数
def _on_failure(self):
self.last_failure_time = time.time()
if self.state == CircuitState.HALF_OPEN:
self._transition_to_open()
elif self.state == CircuitState.CLOSED:
self.failure_count += 1
if self.failure_count >= self.failure_threshold:
self._transition_to_open()
def _transition_to_open(self):
self.state = CircuitState.OPEN
self.last_failure_time = time.time()
self.half_open_requests = 0
print("[熔断器] 状态转换: CLOSED -> OPEN")
def _transition_to_half_open(self):
self.state = CircuitState.HALF_OPEN
self.half_open_requests = 0
print("[熔断器] 状态转换: OPEN -> HALF_OPEN")
def _transition_to_closed(self):
self.state = CircuitState.CLOSED
self.failure_count = 0
self.success_count = 0
self.half_open_requests = 0
print("[熔断器] 状态转换: HALF_OPEN -> CLOSED")
def _fast_fail(self, fallback):
"""快速失败:直接返回降级结果或抛出异常"""
if fallback is not None:
return fallback(Exception("CircuitBreaker OPEN"))
raise Exception("CircuitBreaker OPEN: request rejected")
实现提示: 实际生产环境中,
failure_count 应替换为滑动窗口统计(如基于时间窗口的失败率),避免突发流量导致过早熔断。上述伪代码使用计数阈值是为了简化状态机演示。
4.3 状态机转换流程(SVG 图)
下图展示了熔断器 FSM 的完整状态转换路径,包含三个状态和六条转换边。每个转换边上标注了触发事件和条件。
4.4 关键设计决策
在实现 FSM 时,以下设计决策直接影响熔断器的鲁棒性和性能:
- 计数 vs. 滑动窗口: 简单计数实现容易受突发流量影响。生产环境建议使用 滑动时间窗口(如 10 秒窗口内失败率)替代固定计数。
- 半开状态探测数:
half_open_max_requests控制半开状态下允许的探测请求数量。通常设置为 1~3,避免过多流量压垮正在恢复的服务。 - 超时恢复策略:
recovery_timeout是熔断器从 OPEN 到 HALF_OPEN 的等待时间。建议使用 指数退避(如 30s → 60s → 120s)而非固定值,防止服务抖动。 - 降级与快速失败: 在 OPEN 状态下,所有请求应快速失败并返回降级结果(fallback),避免线程阻塞等待超时。
避坑指南: 不要在
_on_success 和 _on_failure 中执行耗时操作(如日志写入、远程调用)。状态转换应保持轻量,否则会阻塞调用线程,降低熔断器本身的性能。
4.5 状态机扩展:引入半开探测计数器
在某些场景下,半开状态需要更精细的控制。下图展示了引入 探测计数器 后的状态机内部逻辑,区分“允许探测”和“探测进行中”两个子阶段:
扩展建议: 将半开状态拆分为“允许探测”和“探测进行中”两个子阶段,可以更精确地控制并发探测数,防止半开状态下突发大量请求压垮后端。实际实现时,可以使用
asyncio.Semaphore 或 threading.BoundedSemaphore 控制并发度。
4.6 总结
本节实现的 FSM 熔断器核心逻辑具备以下特征:
- 状态清晰: CLOSED、OPEN、HALF_OPEN 三态转换,符合标准熔断器模式。
- 可配置: 失败阈值、恢复超时、半开探测数均可外部注入。
- 可扩展: 计数逻辑可替换为滑动窗口;半开状态可引入子阶段控制并发。
- 生产就绪: 伪代码可直接映射到 Python、Java、Go 等语言的实现,只需补充线程安全和指标上报。