"""熔断器 — 按 app_id 独立的断路保护。 状态机:CLOSED → OPEN(连续失败达阈值)→ HALF_OPEN(超时后探测)→ CLOSED/OPEN。 内存实现,单实例部署,不依赖外部存储。 """ from __future__ import annotations import enum import time from dataclasses import dataclass, field class CircuitState(enum.Enum): """熔断器状态。""" CLOSED = "closed" # 正常放行 OPEN = "open" # 熔断中,拒绝请求 HALF_OPEN = "half_open" # 探测中,放行单个请求 @dataclass class _BreakerState: """单个 app_id 的熔断内部状态。""" state: CircuitState = CircuitState.CLOSED failure_count: int = 0 last_failure_time: float = 0.0 last_state_change: float = field(default_factory=time.monotonic) class CircuitBreaker: """按 app_id 独立的熔断器。 - check():检查当前状态,OPEN 且超时自动转 HALF_OPEN - record_success():HALF_OPEN→CLOSED;CLOSED 重置失败计数 - record_failure():连续达阈值→OPEN;HALF_OPEN 失败→重新 OPEN """ def __init__( self, failure_threshold: int = 5, recovery_timeout: int = 60, ) -> None: self._failure_threshold = failure_threshold self._recovery_timeout = recovery_timeout self._breakers: dict[str, _BreakerState] = {} def _get_state(self, app_id: str) -> _BreakerState: """获取或初始化指定 app_id 的状态。""" if app_id not in self._breakers: self._breakers[app_id] = _BreakerState() return self._breakers[app_id] def check(self, app_id: str) -> CircuitState: """检查当前熔断状态。 - CLOSED / HALF_OPEN:允许通过,返回对应状态 - OPEN 且未超时:返回 OPEN(拒绝) - OPEN 且已超时:自动转 HALF_OPEN,返回 HALF_OPEN(允许探测) """ breaker = self._get_state(app_id) if breaker.state == CircuitState.CLOSED: return CircuitState.CLOSED if breaker.state == CircuitState.HALF_OPEN: return CircuitState.HALF_OPEN # OPEN 状态:检查是否超过恢复超时 elapsed = time.monotonic() - breaker.last_failure_time if elapsed >= self._recovery_timeout: # 超时,转为 HALF_OPEN 探测 breaker.state = CircuitState.HALF_OPEN breaker.last_state_change = time.monotonic() return CircuitState.HALF_OPEN return CircuitState.OPEN def record_success(self, app_id: str) -> None: """记录调用成功。 - HALF_OPEN→CLOSED(探测成功,恢复正常) - CLOSED 下重置失败计数 """ breaker = self._get_state(app_id) if breaker.state == CircuitState.HALF_OPEN: breaker.state = CircuitState.CLOSED breaker.failure_count = 0 breaker.last_state_change = time.monotonic() elif breaker.state == CircuitState.CLOSED: # CLOSED 状态下成功重置失败计数 breaker.failure_count = 0 def record_failure(self, app_id: str) -> None: """记录调用失败。 - CLOSED:累加失败计数,达阈值→OPEN - HALF_OPEN:探测失败→重新 OPEN """ breaker = self._get_state(app_id) now = time.monotonic() if breaker.state == CircuitState.HALF_OPEN: # 探测失败,重新熔断 breaker.state = CircuitState.OPEN breaker.failure_count = self._failure_threshold breaker.last_failure_time = now breaker.last_state_change = now elif breaker.state == CircuitState.CLOSED: breaker.failure_count += 1 breaker.last_failure_time = now if breaker.failure_count >= self._failure_threshold: breaker.state = CircuitState.OPEN breaker.last_state_change = now