ai-robot-core/ai-service/app/services/metadata_cache_service.py

203 lines
5.7 KiB
Python
Raw Normal View History

"""
元数据字段定义缓存服务
使用 Redis 缓存 metadata_field_definitions减少数据库查询
"""
import json
import logging
from typing import Any
from app.core.config import get_settings
logger = logging.getLogger(__name__)
class MetadataCacheService:
"""
元数据字段定义缓存服务
缓存策略
- Key: metadata:fields:{tenant_id}
- Value: JSON 序列化的字段定义列表
- TTL: 1小时3600
- 更新策略写时更新 + 定时刷新
"""
CACHE_KEY_PREFIX = "metadata:fields"
DEFAULT_TTL = 3600 # 1小时
def __init__(self):
self._settings = get_settings()
self._redis_client = None
self._enabled = self._settings.redis_enabled
async def _get_redis(self):
"""获取 Redis 连接(延迟初始化)"""
if not self._enabled:
return None
if self._redis_client is None:
try:
import redis.asyncio as redis
self._redis_client = redis.from_url(
self._settings.redis_url,
decode_responses=True
)
except Exception as e:
logger.error(f"[MetadataCache] Failed to connect Redis: {e}")
self._enabled = False
return None
return self._redis_client
def _make_key(self, tenant_id: str) -> str:
"""生成缓存 key"""
return f"{self.CACHE_KEY_PREFIX}:{tenant_id}"
async def get_fields(self, tenant_id: str) -> list[dict[str, Any]] | None:
"""
获取缓存的字段定义
Args:
tenant_id: 租户 ID
Returns:
字段定义列表未缓存返回 None
"""
if not self._enabled:
return None
try:
redis = await self._get_redis()
if not redis:
return None
key = self._make_key(tenant_id)
cached_data = await redis.get(key)
if cached_data:
logger.info(f"[MetadataCache] Cache hit for tenant={tenant_id}")
return json.loads(cached_data)
logger.info(f"[MetadataCache] Cache miss for tenant={tenant_id}")
return None
except Exception as e:
logger.error(f"[MetadataCache] Get cache error: {e}")
return None
async def set_fields(
self,
tenant_id: str,
fields: list[dict[str, Any]],
ttl: int | None = None
) -> bool:
"""
缓存字段定义
Args:
tenant_id: 租户 ID
fields: 字段定义列表
ttl: 过期时间默认 1小时
Returns:
是否成功
"""
if not self._enabled:
return False
try:
redis = await self._get_redis()
if not redis:
return False
key = self._make_key(tenant_id)
ttl = ttl or self.DEFAULT_TTL
await redis.setex(
key,
ttl,
json.dumps(fields, ensure_ascii=False, default=str)
)
logger.info(
f"[MetadataCache] Cached {len(fields)} fields for tenant={tenant_id}, "
f"ttl={ttl}s"
)
return True
except Exception as e:
logger.error(f"[MetadataCache] Set cache error: {e}")
return False
async def invalidate(self, tenant_id: str) -> bool:
"""
使缓存失效字段定义更新时调用
Args:
tenant_id: 租户 ID
Returns:
是否成功
"""
if not self._enabled:
return False
try:
redis = await self._get_redis()
if not redis:
return False
key = self._make_key(tenant_id)
result = await redis.delete(key)
if result:
logger.info(f"[MetadataCache] Invalidated cache for tenant={tenant_id}")
return bool(result)
except Exception as e:
logger.error(f"[MetadataCache] Invalidate error: {e}")
return False
async def invalidate_all(self) -> bool:
"""
使所有元数据缓存失效
Returns:
是否成功
"""
if not self._enabled:
return False
try:
redis = await self._get_redis()
if not redis:
return False
# 查找所有元数据缓存 key
pattern = f"{self.CACHE_KEY_PREFIX}:*"
keys = []
async for key in redis.scan_iter(match=pattern):
keys.append(key)
if keys:
await redis.delete(*keys)
logger.info(f"[MetadataCache] Invalidated {len(keys)} cache entries")
return True
except Exception as e:
logger.error(f"[MetadataCache] Invalidate all error: {e}")
return False
# 全局缓存服务实例
_metadata_cache_service: MetadataCacheService | None = None
async def get_metadata_cache_service() -> MetadataCacheService:
"""获取元数据缓存服务实例(单例)"""
global _metadata_cache_service
if _metadata_cache_service is None:
_metadata_cache_service = MetadataCacheService()
return _metadata_cache_service