本次提交新增了渠道消息处理的完整核心模块,包含以下核心功能: 1. 新增会话围栏类,实现会话并发控制与过期清理 2. 新增媒体清理器,实现过期媒体文件自动清理 3. 新增熔断器组件,实现服务降级与故障隔离 4. 新增消息处理器,完成渠道消息的完整流转处理 5. 新增限流器组件,实现渠道级和账户级流量控制 6. 新增链路追踪模块,集成Langfuse实现调用链路监控 7. 新增指标统计模块,实现消息处理全链路指标采集 8. 新增统一消息模型,封装全渠道消息格式 9. 新增块回复流水线,实现流式回复的合并与去重 10. 新增本地媒体存储模块,实现媒体文件的本地管理 11. 新增回复分发器,实现回复内容的有序发送与延迟处理 12. 完善__init__.py导出所有核心模块与工具类
112 lines
3.6 KiB
Python
112 lines
3.6 KiB
Python
import asyncio
|
||
import logging
|
||
import time
|
||
from dataclasses import dataclass, field
|
||
from enum import StrEnum
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
class CircuitState(StrEnum):
|
||
CLOSED = "closed"
|
||
OPEN = "open"
|
||
HALF_OPEN = "half_open"
|
||
|
||
|
||
@dataclass(slots=True)
|
||
class CircuitBreakerConfig:
|
||
failure_threshold: int = 5
|
||
recovery_timeout_sec: float = 30.0
|
||
half_open_max_requests: int = 1
|
||
consecutive_successes_to_close: int = 2
|
||
|
||
|
||
@dataclass
|
||
class CircuitBreaker:
|
||
"""简单熔断器。
|
||
|
||
状态机:
|
||
- CLOSED: 正常通行,累计失败计数。
|
||
- OPEN: 拒绝请求,持续 recovery_timeout_sec 后转 HALF_OPEN。
|
||
- HALF_OPEN: 允许少量探测请求。连续成功则转 CLOSED,任何失败则回 OPEN。
|
||
"""
|
||
|
||
name: str
|
||
config: CircuitBreakerConfig = field(default_factory=CircuitBreakerConfig)
|
||
|
||
_state: CircuitState = CircuitState.CLOSED
|
||
_failure_count: int = 0
|
||
_success_count: int = 0
|
||
_last_failure_time: float = 0.0
|
||
_lock: asyncio.Lock = field(default_factory=asyncio.Lock)
|
||
|
||
async def call(self, coro) -> object:
|
||
if not await self._allow_request():
|
||
raise CircuitBreakerOpenError(
|
||
f"Circuit breaker '{self.name}' is OPEN"
|
||
)
|
||
try:
|
||
result = await coro
|
||
except Exception:
|
||
await self._record_failure()
|
||
raise
|
||
else:
|
||
await self._record_success()
|
||
return result
|
||
|
||
async def _allow_request(self) -> bool:
|
||
async with self._lock:
|
||
if self._state == CircuitState.CLOSED:
|
||
return True
|
||
if self._state == CircuitState.OPEN:
|
||
if time.monotonic() - self._last_failure_time >= self.config.recovery_timeout_sec:
|
||
self._state = CircuitState.HALF_OPEN
|
||
self._success_count = 0
|
||
logger.info("Circuit '%s' transitioning OPEN -> HALF_OPEN", self.name)
|
||
return True
|
||
return False
|
||
if self._state == CircuitState.HALF_OPEN:
|
||
return True
|
||
return True
|
||
|
||
async def _record_success(self) -> None:
|
||
async with self._lock:
|
||
if self._state == CircuitState.HALF_OPEN:
|
||
self._success_count += 1
|
||
if self._success_count >= self.config.consecutive_successes_to_close:
|
||
self._state = CircuitState.CLOSED
|
||
self._failure_count = 0
|
||
self._success_count = 0
|
||
logger.info("Circuit '%s' transitioning HALF_OPEN -> CLOSED", self.name)
|
||
|
||
async def _record_failure(self) -> None:
|
||
async with self._lock:
|
||
self._failure_count += 1
|
||
self._last_failure_time = time.monotonic()
|
||
if self._state == CircuitState.HALF_OPEN:
|
||
self._state = CircuitState.OPEN
|
||
logger.warning(
|
||
"Circuit '%s' transitioning HALF_OPEN -> OPEN (probe failed)",
|
||
self.name,
|
||
)
|
||
elif self._state == CircuitState.CLOSED and self._failure_count >= self.config.failure_threshold:
|
||
self._state = CircuitState.OPEN
|
||
logger.warning(
|
||
"Circuit '%s' transitioning CLOSED -> OPEN after %d failures",
|
||
self.name,
|
||
self._failure_count,
|
||
)
|
||
|
||
@property
|
||
def state(self) -> CircuitState:
|
||
return self._state
|
||
|
||
async def reset(self) -> None:
|
||
async with self._lock:
|
||
self._state = CircuitState.CLOSED
|
||
self._failure_count = 0
|
||
self._success_count = 0
|
||
|
||
|
||
class CircuitBreakerOpenError(Exception):
|
||
pass |