缓存
约 658 字大约 2 分钟
编程式缓存
- 完全手动控制缓存的读写、失效、TTL 等
- 适合复杂逻辑、批量操作、自定义序列化、事务等高级场景
- 代码较为显式,易于调试,但重复代码多,容易出错
依赖
所有操作都通过 backend/database/redis.py 中的全局 Redis 客户端完成:
# 创建 redis 客户端单例
redis_client: RedisCli = RedisCli()声明式缓存
- 通过装饰器自动管理缓存生命周期
- 代码极简,关注点分离,适合标准 CRUD
@cached
自动缓存函数结果
from sqlalchemy.ext.asyncio import AsyncSession
from backend.app.admin.crud.crud_user import user_dao
from backend.common.cache.decorator import cached
@cached(name='user:detail', key='user_id')
async def get_user_detail(db: AsyncSession, user_id: int):
return await user_dao.get(db, user_id)- 第一次调用 → 执行函数 → 缓存结果
- 后续调用 → 直接从缓存(L1 或 Redis)返回
key='user_id'表示从函数关键字参数中取user_id作为缓存键,最终键名类似user:detail:1
@cache_invalidate
方法执行后自动失效指定缓存
from sqlalchemy.ext.asyncio import AsyncSession
from backend.app.admin.crud.crud_user import user_dao
from backend.common.cache.decorator import cache_invalidate
@cache_invalidate(name='user:detail', key='user_id')
async def update_user(db: AsyncSession, user_id: int, name: str) -> None:
await user_dao.update(db, user_id, {'nickname': name})- 执行更新后 → 自动删除 L1 和 Redis 缓存
- 通过 Pub/Sub 广播 → 通知其他节点清理本地 L1 缓存
- 默认
atomic=True,缓存失效失败时会抛出异常,避免数据已经变更但缓存仍旧存在
key_builder
如果缓存键无法直接从单个参数中获取,可使用自定义函数生成缓存键
from backend.common.cache.decorator import cached, user_key_builder
@cached(name='user:profile', key_builder=user_key_builder)
async def get_current_user_profile():
...对比
| 维度 | 编程式缓存 | 声明式缓存(装饰器) |
|---|---|---|
| 代码量 | 多(每个地方都要写 get/set/delete) | 极少(一行装饰器即可) |
| 一致性保障 | 需手动保证(容易遗漏失效) | 自动处理 + Pub/Sub 广播 |
| 灵活性 | 最高 | 较高(支持 key_builder) |
| 开发效率 | 较低 | 高 |
| 维护成本 | 高(修改逻辑需改多处) | 低(改装饰器参数即可) |
| 适用场景 | 复杂缓存策略、批量、预热、特殊数据 | 标准 CRUD、列表、详情、热点数据 |
| 调试难度 | 低(逻辑显式) | 中等(需理解装饰器内部流程) |
使用策略
大多数项目推荐以下模式:
- 特殊场景 → 使用编程式手动操作
- 读操作(详情、列表、热点数据) → 使用
@cached声明式缓存 - 删/改操作 → 使用
@cache_invalidate声明式失效 - 开启本地缓存 → L1(内存)缓存将进一步提速,L2(Redis)作为持久层
- 追求开发效率和代码简洁 → 优先使用声明式缓存
- 需要精细控制、复杂逻辑 → 使用编程式缓存

