mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 17:30:23 +08:00
- 删除全部 Python 源码 (src/) 及 Alembic 迁移脚本,归档至 _deprecated_py_src/ - 重构 Rust gateway ai_pipeline: 拆分 planner/finalize 模块,新增 contracts/adaptation 层 - 重组 handlers 模块为 admin/public/proxy/internal/shared 子模块结构 - 新增 executor 模块,引入 Rust 原生数据库迁移 (aether-data/migrations) - 简化 CI/Docker 构建流程,移除 base image 二级构建,统一为单一 app image - 移除 Python 相关基础设施文件 (entrypoint.sh, gunicorn_conf.py, Dockerfile.base)
223 lines
7.5 KiB
Python
223 lines
7.5 KiB
Python
"""
|
||
缓存同步服务(Redis Pub/Sub)
|
||
|
||
提供分布式缓存失效同步功能,用于多实例部署场景。
|
||
当一个实例修改数据并失效本地缓存时,通过 Redis pub/sub 通知其他实例同步失效。
|
||
|
||
使用场景:
|
||
1. 多实例部署时,确保所有实例的缓存一致性
|
||
2. GlobalModel/Model 变更时,同步失效所有实例的缓存
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import asyncio
|
||
import json
|
||
from collections.abc import Callable
|
||
from typing import Any
|
||
|
||
import redis.asyncio as aioredis
|
||
|
||
from src.clients.redis_client import get_redis_client_sync
|
||
from src.core.logger import logger
|
||
|
||
|
||
class CacheSyncService:
|
||
"""
|
||
缓存同步服务
|
||
|
||
通过 Redis pub/sub 实现分布式缓存失效同步
|
||
"""
|
||
|
||
# Redis 频道名称
|
||
CHANNEL_GLOBAL_MODEL = "cache:invalidate:global_model"
|
||
CHANNEL_MODEL = "cache:invalidate:model"
|
||
CHANNEL_CLEAR_ALL = "cache:invalidate:clear_all"
|
||
|
||
def __init__(self, redis_client: aioredis.Redis):
|
||
"""
|
||
初始化缓存同步服务
|
||
|
||
Args:
|
||
redis_client: Redis 客户端实例
|
||
"""
|
||
self._redis = redis_client
|
||
self._pubsub: aioredis.client.PubSub | None = None
|
||
self._listener_task: asyncio.Task | None = None
|
||
self._handlers: dict[str, Callable] = {}
|
||
self._running = False
|
||
|
||
async def start(self) -> Any:
|
||
"""启动缓存同步服务(订阅 Redis 频道)"""
|
||
if self._running:
|
||
logger.warning("[CacheSync] 服务已在运行")
|
||
return
|
||
|
||
try:
|
||
self._pubsub = self._redis.pubsub()
|
||
|
||
# 订阅所有缓存失效频道
|
||
await self._pubsub.subscribe(
|
||
self.CHANNEL_GLOBAL_MODEL,
|
||
self.CHANNEL_MODEL,
|
||
self.CHANNEL_CLEAR_ALL,
|
||
)
|
||
|
||
# 启动监听任务
|
||
self._listener_task = asyncio.create_task(self._listen())
|
||
self._running = True
|
||
|
||
logger.info(
|
||
"[CacheSync] 缓存同步服务已启动,订阅频道: "
|
||
f"{self.CHANNEL_GLOBAL_MODEL}, "
|
||
f"{self.CHANNEL_MODEL}, {self.CHANNEL_CLEAR_ALL}"
|
||
)
|
||
except Exception as e:
|
||
logger.error(f"[CacheSync] 启动失败: {e}")
|
||
raise
|
||
|
||
async def stop(self) -> Any:
|
||
"""停止缓存同步服务"""
|
||
if not self._running:
|
||
return
|
||
|
||
self._running = False
|
||
|
||
# 取消监听任务
|
||
if self._listener_task:
|
||
self._listener_task.cancel()
|
||
try:
|
||
await self._listener_task
|
||
except asyncio.CancelledError:
|
||
pass
|
||
|
||
# 取消订阅
|
||
if self._pubsub:
|
||
await self._pubsub.unsubscribe()
|
||
await self._pubsub.close()
|
||
|
||
logger.info("[CacheSync] 缓存同步服务已停止")
|
||
|
||
def register_handler(self, channel: str, handler: Callable) -> None:
|
||
"""
|
||
注册缓存失效处理器
|
||
|
||
Args:
|
||
channel: Redis 频道名称
|
||
handler: 处理函数(接收消息数据作为参数)
|
||
"""
|
||
self._handlers[channel] = handler
|
||
logger.debug(f"[CacheSync] 注册处理器: {channel}")
|
||
|
||
async def _listen(self) -> None:
|
||
"""监听 Redis pub/sub 消息(含断线重连)"""
|
||
logger.info("[CacheSync] 开始监听缓存失效消息")
|
||
consecutive_failures = 0
|
||
max_consecutive_failures = 10
|
||
reconnect_interval = 5.0
|
||
|
||
while self._running:
|
||
try:
|
||
async for message in self._pubsub.listen():
|
||
consecutive_failures = 0 # 收到消息即重置
|
||
if message["type"] == "message":
|
||
channel = message["channel"]
|
||
data = message["data"]
|
||
|
||
try:
|
||
payload = json.loads(data)
|
||
logger.debug(f"[CacheSync] 收到消息: {channel} -> {payload}")
|
||
|
||
if channel in self._handlers:
|
||
handler = self._handlers[channel]
|
||
await handler(payload)
|
||
else:
|
||
logger.warning(f"[CacheSync] 未找到处理器: {channel}")
|
||
except json.JSONDecodeError as e:
|
||
logger.error(f"[CacheSync] 消息解析失败: {data}, 错误: {e}")
|
||
except Exception as e:
|
||
logger.error(f"[CacheSync] 处理消息失败: {channel}, 错误: {e}")
|
||
except asyncio.CancelledError:
|
||
logger.info("[CacheSync] 监听任务已取消")
|
||
return
|
||
except Exception as e:
|
||
consecutive_failures += 1
|
||
logger.error(
|
||
f"[CacheSync] 监听失败 ({consecutive_failures}/{max_consecutive_failures}): {e}"
|
||
)
|
||
if consecutive_failures >= max_consecutive_failures:
|
||
logger.error("[CacheSync] 连续失败次数过多,停止重连")
|
||
return
|
||
await asyncio.sleep(reconnect_interval)
|
||
|
||
async def publish_global_model_changed(self, model_name: str) -> Any:
|
||
"""发布 GlobalModel 变更通知"""
|
||
await self._publish(self.CHANNEL_GLOBAL_MODEL, {"model_name": model_name})
|
||
|
||
async def publish_model_changed(self, provider_id: str, global_model_id: str) -> Any:
|
||
"""发布 Model 变更通知"""
|
||
await self._publish(
|
||
self.CHANNEL_MODEL, {"provider_id": provider_id, "global_model_id": global_model_id}
|
||
)
|
||
|
||
async def publish_clear_all(self) -> Any:
|
||
"""发布清空所有缓存通知"""
|
||
await self._publish(self.CHANNEL_CLEAR_ALL, {})
|
||
|
||
async def _publish(self, channel: str, data: dict) -> None:
|
||
"""发布消息到 Redis 频道(含简单重试)"""
|
||
message = json.dumps(data)
|
||
last_error: Exception | None = None
|
||
for attempt in range(2):
|
||
try:
|
||
await self._redis.publish(channel, message)
|
||
logger.debug(f"[CacheSync] 发布消息: {channel} -> {data}")
|
||
return
|
||
except Exception as e:
|
||
last_error = e
|
||
if attempt == 0:
|
||
await asyncio.sleep(0.5)
|
||
logger.error(f"[CacheSync] 发布消息失败(已重试): {channel}, 错误: {last_error}")
|
||
|
||
|
||
# 全局单例
|
||
_cache_sync_service: CacheSyncService | None = None
|
||
|
||
|
||
async def get_cache_sync_service(
|
||
redis_client: aioredis.Redis | None = None,
|
||
) -> CacheSyncService | None:
|
||
"""
|
||
获取缓存同步服务实例
|
||
|
||
Args:
|
||
redis_client: Redis 客户端实例(首次调用时需要提供)
|
||
|
||
Returns:
|
||
CacheSyncService 实例,如果 Redis 不可用返回 None
|
||
"""
|
||
global _cache_sync_service
|
||
|
||
if _cache_sync_service is None:
|
||
if redis_client is None:
|
||
# 尝试获取全局 Redis 客户端
|
||
redis_client = get_redis_client_sync()
|
||
|
||
if redis_client is None:
|
||
logger.warning("[CacheSync] Redis 不可用,分布式缓存同步已禁用")
|
||
return None
|
||
|
||
_cache_sync_service = CacheSyncService(redis_client)
|
||
logger.info("[CacheSync] 缓存同步服务已初始化")
|
||
|
||
return _cache_sync_service
|
||
|
||
|
||
async def close_cache_sync_service() -> None:
|
||
"""关闭缓存同步服务"""
|
||
global _cache_sync_service
|
||
|
||
if _cache_sync_service:
|
||
await _cache_sync_service.stop()
|
||
_cache_sync_service = None
|