69 lines
2.1 KiB
Python
69 lines
2.1 KiB
Python
"""Redis 单例网关(B7 lifespan 管理连接):预警广播 publish + L3 缓存失效 DEL。
|
||
|
||
惰性连接(首次 publish/DEL 才建);连接/执行失败一律降级日志,不阻塞业务
|
||
(DB 为权威,PRD FR-4 通知语义)。测试注入 fake:monkeypatch 本模块
|
||
`_gateway`,实现 publish(channel, payload) / delete(*keys) 即可。
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import json
|
||
import logging
|
||
from typing import Any
|
||
|
||
from app.config.settings import settings
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
class RedisGateway:
|
||
def __init__(self, url: str | None = None) -> None:
|
||
self._url = url or settings.redis_url
|
||
self._client: Any = None
|
||
|
||
def _ensure(self) -> Any:
|
||
if self._client is None:
|
||
import redis
|
||
|
||
self._client = redis.Redis.from_url(self._url, decode_responses=True)
|
||
return self._client
|
||
|
||
def publish(self, channel: str, payload: dict[str, Any]) -> None:
|
||
self._ensure().publish(channel, json.dumps(payload, ensure_ascii=False))
|
||
|
||
def delete(self, *keys: str) -> None:
|
||
self._ensure().delete(*keys)
|
||
|
||
|
||
_gateway: RedisGateway | Any | None = None
|
||
|
||
|
||
def set_gateway(gateway: RedisGateway | Any | None) -> None:
|
||
"""单例注入点(B7 lifespan 注册;测试注入 fake)。"""
|
||
global _gateway
|
||
_gateway = gateway
|
||
|
||
|
||
def get_gateway() -> RedisGateway:
|
||
"""取网关单例;未注册时惰性创建(脚本直调场景)。"""
|
||
global _gateway
|
||
if _gateway is None:
|
||
_gateway = RedisGateway()
|
||
return _gateway
|
||
|
||
|
||
def publish(channel: str, payload: dict[str, Any]) -> None:
|
||
"""广播;失败降级(不阻塞预警落库)。"""
|
||
try:
|
||
get_gateway().publish(channel, payload)
|
||
except Exception:
|
||
logger.exception("publish %s failed", channel)
|
||
|
||
|
||
def cache_delete(*keys: str) -> None:
|
||
"""写侧缓存失效(PRD §5.1:MySQL 更新时 DEL);失败降级(TTL 兜底过期)。"""
|
||
try:
|
||
get_gateway().delete(*keys)
|
||
except Exception:
|
||
logger.warning("cache DEL failed (degrade to TTL): %s", keys, exc_info=True)
|