"""通用熔断器:保护下游资源免受连续失败冲击。 状态机: CLOSED → 连续失败达 failure_threshold → OPEN OPEN → 经过 recovery_timeout → HALF_OPEN HALF_OPEN → 一次成功 → CLOSED / 一次失败 → OPEN 供 FlowOrchestrator 对 DB 校验失败、WeChat 启动失败等做熔断保护。 """ from __future__ import annotations import enum import logging import time import threading logger = logging.getLogger("woc-bridge") class CircuitState(enum.Enum): """熔断器状态。""" CLOSED = "closed" # 正常工作,请求通过 OPEN = "open" # 熔断中,请求拒绝 HALF_OPEN = "half_open" # 半开,允许一次试探请求 class CircuitBreaker: """熔断器。 Attributes: name: 熔断器名称(如 "db_verify") failure_threshold: 连续失败次数阈值,默认 5 recovery_timeout: 熔断恢复秒数,默认 30 """ def __init__( self, name: str, failure_threshold: int = 5, recovery_timeout: float = 30.0, ) -> None: """初始化熔断器。 Args: name: 熔断器名称 failure_threshold: 连续失败次数阈值 recovery_timeout: 熔断恢复秒数 """ self.name = name self.failure_threshold = failure_threshold self.recovery_timeout = recovery_timeout self._state = CircuitState.CLOSED self._fail_count = 0 self._last_failure_time: float = 0.0 self._lock = threading.Lock() @property def state(self) -> CircuitState: """当前状态。 OPEN 状态超过 recovery_timeout 自动转 HALF_OPEN。 """ with self._lock: if ( self._state == CircuitState.OPEN and time.monotonic() - self._last_failure_time >= self.recovery_timeout ): self._state = CircuitState.HALF_OPEN logger.info( "[breaker:%s] OPEN -> HALF_OPEN (after %.0fs)", self.name, self.recovery_timeout, ) return self._state def allow(self) -> bool: """是否允许请求通过。 CLOSED / HALF_OPEN 允许;OPEN 拒绝。 HALF_OPEN 时只允许一次试探(调用方需配合 record_success/failure)。 """ return self.state in (CircuitState.CLOSED, CircuitState.HALF_OPEN) def record_success(self) -> None: """记录一次成功。 CLOSED: 重置失败计数。 HALF_OPEN: 转 CLOSED,恢复服务。 """ with self._lock: if self._state == CircuitState.HALF_OPEN: self._state = CircuitState.CLOSED logger.info("[breaker:%s] HALF_OPEN -> CLOSED", self.name) self._fail_count = 0 def record_failure(self) -> None: """记录一次失败。 CLOSED: 失败计数 +1,达阈值转 OPEN。 HALF_OPEN: 立即转 OPEN。 OPEN: 更新 last_failure_time(延长熔断)。 """ with self._lock: self._last_failure_time = time.monotonic() if self._state == CircuitState.HALF_OPEN: self._state = CircuitState.OPEN logger.warning( "[breaker:%s] HALF_OPEN -> OPEN (probe failed)", self.name ) return self._fail_count += 1 if ( self._state == CircuitState.CLOSED and self._fail_count >= self.failure_threshold ): self._state = CircuitState.OPEN logger.warning( "[breaker:%s] CLOSED -> OPEN (fail_count=%d)", self.name, self._fail_count, ) def reset(self) -> None: """手动重置熔断器到 CLOSED 状态。""" with self._lock: self._state = CircuitState.CLOSED self._fail_count = 0 self._last_failure_time = 0.0 logger.info("[breaker:%s] reset to CLOSED", self.name)