All checks were successful
continuous-integration/drone/push Build is passing
- 新增告警模块 (alerts): 告警规则配置与触发 - 新增成本管理模块 (cost): 成本统计与分析 - 新增配额模块 (quota): 配额管理与限制 - 新增微信模块 (wechat): 微信相关功能接口 - 新增缓存服务 (cache): Redis 缓存封装 - 新增请求日志中间件 (request_logger) - 新增异常处理和链路追踪中间件 - 更新 dashboard 前端展示 - 更新 SDK stats_client 功能
310 lines
8.7 KiB
Python
310 lines
8.7 KiB
Python
"""Redis缓存服务"""
|
||
import json
|
||
import logging
|
||
from typing import Optional, Any, Union
|
||
from functools import lru_cache
|
||
|
||
try:
|
||
import redis
|
||
from redis import Redis
|
||
REDIS_AVAILABLE = True
|
||
except ImportError:
|
||
REDIS_AVAILABLE = False
|
||
Redis = None
|
||
|
||
from ..config import get_settings
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
# 全局Redis连接池
|
||
_redis_pool: Optional[Any] = None
|
||
_redis_client: Optional[Any] = None
|
||
|
||
|
||
def get_redis_client() -> Optional[Any]:
|
||
"""获取Redis客户端单例"""
|
||
global _redis_pool, _redis_client
|
||
|
||
if not REDIS_AVAILABLE:
|
||
logger.warning("Redis module not installed, cache disabled")
|
||
return None
|
||
|
||
if _redis_client is not None:
|
||
return _redis_client
|
||
|
||
settings = get_settings()
|
||
|
||
try:
|
||
_redis_pool = redis.ConnectionPool.from_url(
|
||
settings.REDIS_URL,
|
||
max_connections=20,
|
||
decode_responses=True
|
||
)
|
||
_redis_client = Redis(connection_pool=_redis_pool)
|
||
# 测试连接
|
||
_redis_client.ping()
|
||
logger.info(f"Redis connected: {settings.REDIS_URL}")
|
||
return _redis_client
|
||
except Exception as e:
|
||
logger.warning(f"Redis connection failed: {e}, cache disabled")
|
||
_redis_client = None
|
||
return None
|
||
|
||
|
||
class CacheService:
|
||
"""缓存服务
|
||
|
||
提供统一的缓存接口,支持Redis和内存回退
|
||
|
||
使用示例:
|
||
cache = CacheService()
|
||
|
||
# 设置缓存
|
||
cache.set("user:123", {"name": "test"}, ttl=3600)
|
||
|
||
# 获取缓存
|
||
user = cache.get("user:123")
|
||
|
||
# 删除缓存
|
||
cache.delete("user:123")
|
||
"""
|
||
|
||
def __init__(self, prefix: Optional[str] = None):
|
||
"""初始化缓存服务
|
||
|
||
Args:
|
||
prefix: 键前缀,默认使用配置中的REDIS_PREFIX
|
||
"""
|
||
settings = get_settings()
|
||
self.prefix = prefix or settings.REDIS_PREFIX
|
||
self._client = get_redis_client()
|
||
|
||
# 内存回退缓存(当Redis不可用时使用)
|
||
self._memory_cache: dict = {}
|
||
|
||
@property
|
||
def is_available(self) -> bool:
|
||
"""Redis是否可用"""
|
||
return self._client is not None
|
||
|
||
def _make_key(self, key: str) -> str:
|
||
"""生成完整的缓存键"""
|
||
return f"{self.prefix}{key}"
|
||
|
||
def get(self, key: str, default: Any = None) -> Any:
|
||
"""获取缓存值
|
||
|
||
Args:
|
||
key: 缓存键
|
||
default: 默认值
|
||
|
||
Returns:
|
||
缓存值或默认值
|
||
"""
|
||
full_key = self._make_key(key)
|
||
|
||
if self._client:
|
||
try:
|
||
value = self._client.get(full_key)
|
||
if value is None:
|
||
return default
|
||
# 尝试解析JSON
|
||
try:
|
||
return json.loads(value)
|
||
except (json.JSONDecodeError, TypeError):
|
||
return value
|
||
except Exception as e:
|
||
logger.error(f"Cache get error: {e}")
|
||
return default
|
||
else:
|
||
# 内存回退
|
||
return self._memory_cache.get(full_key, default)
|
||
|
||
def set(
|
||
self,
|
||
key: str,
|
||
value: Any,
|
||
ttl: Optional[int] = None,
|
||
nx: bool = False
|
||
) -> bool:
|
||
"""设置缓存值
|
||
|
||
Args:
|
||
key: 缓存键
|
||
value: 缓存值
|
||
ttl: 过期时间(秒)
|
||
nx: 只在键不存在时设置
|
||
|
||
Returns:
|
||
是否设置成功
|
||
"""
|
||
full_key = self._make_key(key)
|
||
|
||
# 序列化值
|
||
if isinstance(value, (dict, list)):
|
||
serialized = json.dumps(value, ensure_ascii=False)
|
||
else:
|
||
serialized = str(value) if value is not None else ""
|
||
|
||
if self._client:
|
||
try:
|
||
if nx:
|
||
result = self._client.set(full_key, serialized, ex=ttl, nx=True)
|
||
else:
|
||
result = self._client.set(full_key, serialized, ex=ttl)
|
||
return bool(result)
|
||
except Exception as e:
|
||
logger.error(f"Cache set error: {e}")
|
||
return False
|
||
else:
|
||
# 内存回退(不支持TTL和NX)
|
||
if nx and full_key in self._memory_cache:
|
||
return False
|
||
self._memory_cache[full_key] = value
|
||
return True
|
||
|
||
def delete(self, key: str) -> bool:
|
||
"""删除缓存
|
||
|
||
Args:
|
||
key: 缓存键
|
||
|
||
Returns:
|
||
是否删除成功
|
||
"""
|
||
full_key = self._make_key(key)
|
||
|
||
if self._client:
|
||
try:
|
||
return bool(self._client.delete(full_key))
|
||
except Exception as e:
|
||
logger.error(f"Cache delete error: {e}")
|
||
return False
|
||
else:
|
||
return self._memory_cache.pop(full_key, None) is not None
|
||
|
||
def exists(self, key: str) -> bool:
|
||
"""检查键是否存在
|
||
|
||
Args:
|
||
key: 缓存键
|
||
|
||
Returns:
|
||
是否存在
|
||
"""
|
||
full_key = self._make_key(key)
|
||
|
||
if self._client:
|
||
try:
|
||
return bool(self._client.exists(full_key))
|
||
except Exception as e:
|
||
logger.error(f"Cache exists error: {e}")
|
||
return False
|
||
else:
|
||
return full_key in self._memory_cache
|
||
|
||
def ttl(self, key: str) -> int:
|
||
"""获取键的剩余过期时间
|
||
|
||
Args:
|
||
key: 缓存键
|
||
|
||
Returns:
|
||
剩余秒数,-1表示永不过期,-2表示键不存在
|
||
"""
|
||
full_key = self._make_key(key)
|
||
|
||
if self._client:
|
||
try:
|
||
return self._client.ttl(full_key)
|
||
except Exception as e:
|
||
logger.error(f"Cache ttl error: {e}")
|
||
return -2
|
||
else:
|
||
return -1 if full_key in self._memory_cache else -2
|
||
|
||
def incr(self, key: str, amount: int = 1) -> int:
|
||
"""递增计数器
|
||
|
||
Args:
|
||
key: 缓存键
|
||
amount: 递增量
|
||
|
||
Returns:
|
||
递增后的值
|
||
"""
|
||
full_key = self._make_key(key)
|
||
|
||
if self._client:
|
||
try:
|
||
return self._client.incrby(full_key, amount)
|
||
except Exception as e:
|
||
logger.error(f"Cache incr error: {e}")
|
||
return 0
|
||
else:
|
||
current = self._memory_cache.get(full_key, 0)
|
||
new_value = int(current) + amount
|
||
self._memory_cache[full_key] = new_value
|
||
return new_value
|
||
|
||
def expire(self, key: str, ttl: int) -> bool:
|
||
"""设置键的过期时间
|
||
|
||
Args:
|
||
key: 缓存键
|
||
ttl: 过期时间(秒)
|
||
|
||
Returns:
|
||
是否设置成功
|
||
"""
|
||
full_key = self._make_key(key)
|
||
|
||
if self._client:
|
||
try:
|
||
return bool(self._client.expire(full_key, ttl))
|
||
except Exception as e:
|
||
logger.error(f"Cache expire error: {e}")
|
||
return False
|
||
else:
|
||
return full_key in self._memory_cache
|
||
|
||
def clear_prefix(self, prefix: str) -> int:
|
||
"""删除指定前缀的所有键
|
||
|
||
Args:
|
||
prefix: 键前缀
|
||
|
||
Returns:
|
||
删除的键数量
|
||
"""
|
||
full_prefix = self._make_key(prefix)
|
||
|
||
if self._client:
|
||
try:
|
||
keys = self._client.keys(f"{full_prefix}*")
|
||
if keys:
|
||
return self._client.delete(*keys)
|
||
return 0
|
||
except Exception as e:
|
||
logger.error(f"Cache clear_prefix error: {e}")
|
||
return 0
|
||
else:
|
||
count = 0
|
||
keys_to_delete = [k for k in self._memory_cache if k.startswith(full_prefix)]
|
||
for k in keys_to_delete:
|
||
del self._memory_cache[k]
|
||
count += 1
|
||
return count
|
||
|
||
|
||
# 全局缓存实例
|
||
_cache_instance: Optional[CacheService] = None
|
||
|
||
|
||
def get_cache() -> CacheService:
|
||
"""获取全局缓存实例"""
|
||
global _cache_instance
|
||
if _cache_instance is None:
|
||
_cache_instance = CacheService()
|
||
return _cache_instance
|