mirror of
https://github.com/fastapi-practices/fastapi_best_architecture.git
synced 2026-03-13 09:31:31 +08:00
Fix multi level cache key build and usage (#1066)
* Fix multi level cache key build and usage * Update build cache key
This commit is contained in:
56
backend/common/cache/decorator.py
vendored
56
backend/common/cache/decorator.py
vendored
@@ -3,7 +3,6 @@ import functools
|
||||
from collections.abc import Callable, Sequence
|
||||
from typing import Any, ParamSpec, TypeVar
|
||||
|
||||
from cachebox import make_hash_key
|
||||
from msgspec import json
|
||||
|
||||
from backend.common.cache.local import local_cache_manager
|
||||
@@ -18,11 +17,8 @@ from backend.utils.serializers import select_columns_serialize, select_list_seri
|
||||
P = ParamSpec('P')
|
||||
T = TypeVar('T')
|
||||
|
||||
# 哈希缓存键排除参数
|
||||
_EXCLUDE_PARAMS = frozenset({'db', 'session', 'self', 'cls', 'request', 'response'})
|
||||
|
||||
|
||||
def build_cache_key(
|
||||
def _build_cache_key(
|
||||
name: str,
|
||||
key: str | None,
|
||||
key_builder: Callable[..., str] | None,
|
||||
@@ -30,32 +26,18 @@ def build_cache_key(
|
||||
**kwargs: Any,
|
||||
) -> str:
|
||||
"""构建缓存 Key"""
|
||||
if key_builder:
|
||||
return f'{name}:{key_builder(*args, **kwargs)}'
|
||||
|
||||
if key:
|
||||
value = kwargs.get(key)
|
||||
if value is None:
|
||||
raise errors.ServerError(msg=f'缓存键构建失败,参数 "{key}" 不存在或值为空')
|
||||
return f'{name}:{value}'
|
||||
|
||||
filtered = {k: v for k, v in kwargs.items() if k not in _EXCLUDE_PARAMS and v is not None}
|
||||
|
||||
if filtered:
|
||||
hash_suffix = make_hash_key(*args, **kwargs)
|
||||
return f'{name}:{hash_suffix}'
|
||||
if key_builder:
|
||||
return f'{name}:{key_builder(*args, **kwargs)}'
|
||||
|
||||
return name
|
||||
|
||||
|
||||
def user_key_builder() -> str:
|
||||
"""基于当前用户 ID 生成缓存 Key"""
|
||||
user_id = ctx.user_id
|
||||
if user_id is None:
|
||||
raise errors.ServerError(msg='用户缓存键构建失败')
|
||||
return str(user_id)
|
||||
|
||||
|
||||
def _serialize_result(result: Any) -> bytes:
|
||||
"""
|
||||
序列化缓存结果
|
||||
@@ -93,6 +75,14 @@ def _deserialize_result(value: bytes) -> Any:
|
||||
return value
|
||||
|
||||
|
||||
def user_key_builder() -> str:
|
||||
"""基于当前用户 ID 生成缓存 Key"""
|
||||
user_id = ctx.user_id
|
||||
if user_id is None:
|
||||
raise errors.ServerError(msg='用户缓存键构建失败')
|
||||
return str(user_id)
|
||||
|
||||
|
||||
def cached( # noqa: C901
|
||||
name: str,
|
||||
*,
|
||||
@@ -113,7 +103,7 @@ def cached( # noqa: C901
|
||||
def decorator(func: Callable[P, T]) -> Callable[P, T]: # noqa: C901
|
||||
@functools.wraps(func)
|
||||
async def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
|
||||
cache_key = build_cache_key(name, key, key_builder, *args, **kwargs)
|
||||
cache_key = _build_cache_key(name, key, key_builder, *args, **kwargs)
|
||||
|
||||
# L1: 本地缓存
|
||||
if settings.CACHE_LOCAL_ENABLED:
|
||||
@@ -187,27 +177,27 @@ def cache_invalidate( # noqa: C901
|
||||
invalidate_error = None
|
||||
|
||||
try:
|
||||
invalidate_key = build_cache_key(name, key, key_builder, *args, **kwargs)
|
||||
|
||||
# L2 缓存失效
|
||||
if invalidate_key == name:
|
||||
await redis_client.delete(invalidate_key)
|
||||
else:
|
||||
await redis_client.delete_prefix(invalidate_key)
|
||||
invalidate_key = _build_cache_key(name, key, key_builder, *args, **kwargs)
|
||||
|
||||
# L1 缓存失效
|
||||
if settings.CACHE_LOCAL_ENABLED:
|
||||
if invalidate_key == name:
|
||||
local_cache_manager.delete(invalidate_key)
|
||||
else:
|
||||
local_cache_manager.delete_prefix(invalidate_key)
|
||||
else:
|
||||
local_cache_manager.delete(invalidate_key)
|
||||
|
||||
# 广播失效消息(通知其他节点清除本地缓存)
|
||||
if settings.CACHE_LOCAL_ENABLED:
|
||||
if invalidate_key == name:
|
||||
await cache_pubsub_manager.publish_invalidation(invalidate_key)
|
||||
else:
|
||||
await cache_pubsub_manager.publish_invalidation(invalidate_key, is_delete_prefix=True)
|
||||
else:
|
||||
await cache_pubsub_manager.publish_invalidation(invalidate_key)
|
||||
|
||||
# L2 缓存失效
|
||||
if invalidate_key == name:
|
||||
await redis_client.delete_prefix(invalidate_key)
|
||||
else:
|
||||
await redis_client.delete(invalidate_key)
|
||||
|
||||
except Exception as e:
|
||||
log.error(f'[Cache] INVALIDATE error: {e}')
|
||||
|
||||
36
backend/common/cache/warmup.py
vendored
36
backend/common/cache/warmup.py
vendored
@@ -1,36 +0,0 @@
|
||||
from backend.common.log import log
|
||||
from backend.database.db import async_db_session
|
||||
from backend.plugin.config.enums import ConfigType
|
||||
|
||||
|
||||
async def cache_warmup() -> None:
|
||||
"""缓存预热"""
|
||||
await _warmup_config()
|
||||
await _warmup_dict()
|
||||
|
||||
|
||||
async def _warmup_config() -> None:
|
||||
"""预热参数配置缓存"""
|
||||
try:
|
||||
from backend.plugin.config.service.config_service import config_service
|
||||
|
||||
async with async_db_session() as db:
|
||||
for type in ConfigType.get_member_values():
|
||||
await config_service.get_all(db=db, type=type)
|
||||
except ImportError:
|
||||
pass
|
||||
except Exception as e:
|
||||
log.warning(f'[Warmup] 参数配置缓存预热失败: {e}')
|
||||
|
||||
|
||||
async def _warmup_dict() -> None:
|
||||
"""预热数据字典缓存"""
|
||||
try:
|
||||
from backend.plugin.dict.service.dict_data_service import dict_data_service
|
||||
|
||||
async with async_db_session() as db:
|
||||
await dict_data_service.get_all(db=db)
|
||||
except ImportError:
|
||||
pass
|
||||
except Exception as e:
|
||||
log.warning(f'[Warmup] 数据字典缓存预热失败: {e}')
|
||||
@@ -18,7 +18,6 @@ from starlette_context.plugins import RequestIdPlugin
|
||||
|
||||
from backend import __version__
|
||||
from backend.common.cache.pubsub import cache_pubsub_manager
|
||||
from backend.common.cache.warmup import cache_warmup
|
||||
from backend.common.exception.exception_handler import register_exception
|
||||
from backend.common.log import set_custom_logfile, setup_logging
|
||||
from backend.common.response.response_code import StandardResponseCode
|
||||
@@ -68,9 +67,6 @@ async def register_init(app: FastAPI) -> AsyncGenerator[None, None]:
|
||||
# 创建操作日志任务
|
||||
create_task(OperaLogMiddleware.consumer())
|
||||
|
||||
# 缓存预热
|
||||
await cache_warmup()
|
||||
|
||||
# 启动缓存 Pub/Sub 监听器
|
||||
cache_pubsub_manager.start_listener()
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ class ConfigService:
|
||||
"""参数配置服务类"""
|
||||
|
||||
@staticmethod
|
||||
@cached(settings.CACHE_CONFIG_REDIS_PREFIX, key='pk')
|
||||
async def get(*, db: AsyncSession, pk: int) -> Config:
|
||||
"""
|
||||
获取参数配置详情
|
||||
@@ -34,10 +35,7 @@ class ConfigService:
|
||||
return config
|
||||
|
||||
@staticmethod
|
||||
@cached(
|
||||
settings.CACHE_CONFIG_REDIS_PREFIX,
|
||||
key_builder=lambda *, db, type: f'type:{type}',
|
||||
)
|
||||
@cached(settings.CACHE_CONFIG_REDIS_PREFIX, key='type')
|
||||
async def get_all(*, db: AsyncSession, type: str | None) -> Sequence[Config | None]:
|
||||
"""
|
||||
获取所有参数配置
|
||||
@@ -62,7 +60,6 @@ class ConfigService:
|
||||
return await paging_data(db, config_select)
|
||||
|
||||
@staticmethod
|
||||
@cache_invalidate(settings.CACHE_CONFIG_REDIS_PREFIX)
|
||||
async def create(*, db: AsyncSession, obj: CreateConfigParam) -> None:
|
||||
"""
|
||||
创建参数配置
|
||||
|
||||
@@ -17,6 +17,7 @@ class DictDataService:
|
||||
"""字典数据服务类"""
|
||||
|
||||
@staticmethod
|
||||
@cached(settings.CACHE_DICT_REDIS_PREFIX, key='pk')
|
||||
async def get(*, db: AsyncSession, pk: int) -> DictData:
|
||||
"""
|
||||
获取字典数据详情
|
||||
@@ -49,10 +50,6 @@ class DictDataService:
|
||||
return dict_datas
|
||||
|
||||
@staticmethod
|
||||
@cached(
|
||||
settings.CACHE_DICT_REDIS_PREFIX,
|
||||
key_builder=lambda *, db: 'all',
|
||||
)
|
||||
async def get_all(*, db: AsyncSession) -> Sequence[DictData]:
|
||||
"""
|
||||
获取所有字典数据
|
||||
@@ -94,7 +91,6 @@ class DictDataService:
|
||||
return await paging_data(db, dict_data_select)
|
||||
|
||||
@staticmethod
|
||||
@cache_invalidate(settings.CACHE_DICT_REDIS_PREFIX)
|
||||
async def create(*, db: AsyncSession, obj: CreateDictDataParam) -> None:
|
||||
"""
|
||||
创建字典数据
|
||||
|
||||
Reference in New Issue
Block a user