第 13 章 缓存系统
学习目标
学完本章,你应该能:
- 理解为什么需要缓存,以及 L1/L2 两级缓存的区别
- 掌握编程式缓存和声明式缓存两种用法
- 能根据场景选择合适的缓存策略
一、为什么需要缓存
有些数据(如用户详情、字典配置、热门列表)查询频繁但很少变化。每次都查数据库,既慢又浪费资源。
缓存就是把这类数据暂存在更快的存储(内存/Redis)里,下次直接返回,避免重复查数据库。
fba 采用两级缓存:
| 级别 | 存储位置 | 特点 |
|---|---|---|
| L1(本地缓存) | 应用内存 | 最快,但每个进程独立 |
| L2(Redis) | Redis | 稍慢,但多进程/多实例共享 |
读数据先查 L1,没有就查 L2,再没有才查数据库,查完后回填缓存。这就是经典的"多级缓存"。
两级缓存读取流程
二、两种缓存方式
fba 提供两种缓存使用方式,适合不同场景。
方式一:编程式缓存
手动控制缓存的读写、失效、过期时间。通过全局 Redis 客户端完成:
python
from backend.database.redis import redis_client
# 手动存
await redis_client.set('key', 'value', ex=60)
# 手动取
value = await redis_client.get('key')
# 手动删
await redis_client.delete('key')特点:完全可控,适合复杂逻辑、批量操作,但代码多、易出错。
方式二:声明式缓存(推荐)
通过装饰器自动管理缓存,代码极简。
@cached —— 自动缓存函数结果
python
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)- 第一次调用 → 执行函数 → 缓存结果
- 后续调用 → 直接从缓存返回
key='user_id'表示取参数user_id作为缓存键,最终键名如user:detail:1
@cached 工作流程
@cache_invalidate —— 更新后自动失效缓存
python
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})- 执行更新后 → 自动删除相关缓存
- 通过 Pub/Sub 广播 → 通知其他节点清理本地缓存
Pub/Sub:Redis 的发布订阅机制,一个节点发布消息,其他订阅节点都收到,用于多节点缓存同步失效。
缓存失效流程
三、对比与选择
| 维度 | 编程式 | 声明式(装饰器) |
|---|---|---|
| 代码量 | 多 | 极少(一行装饰器) |
| 一致性保障 | 手动,易遗漏 | 自动 + Pub/Sub 广播 |
| 灵活性 | 最高 | 较高(支持 key_builder) |
| 适用场景 | 复杂缓存、批量、特殊数据 | 标准 CRUD、列表、详情、热点数据 |
四、使用策略(官方推荐)
大多数项目推荐这样用:
- 读操作(详情、列表、热点数据)→ 用
@cached - 删/改操作 → 用
@cache_invalidate - 特殊场景 → 用编程式手动操作
- 追求效率 → 优先声明式缓存
- 需要精细控制 → 用编程式缓存
缓存策略选择流程
五、key_builder(进阶)
如果缓存键无法直接从单个参数获取,可用自定义函数生成:
python
@cached(name='user:profile', key_builder=user_key_builder)
async def get_current_user_profile():
...key_builder 是一个自定义函数,根据你的逻辑生成缓存键,适合复杂场景。
六、常见疑问
Q1:为什么要两级缓存,一层不够吗? L1 内存缓存最快,但每个进程独立,多实例部署时数据不一致;L2 Redis 共享但稍慢。两级结合,既快又能跨实例共享。
Q2:缓存失效为什么重要? 数据更新后,如果缓存不失效,用户会读到旧数据(数据不一致)。所以"写操作必须失效缓存",这是缓存使用的核心纪律。
本章小结
关键要点回顾:
- 两级缓存:L1 本地内存 + L2 Redis
- 两种方式:编程式(手动)、声明式(装饰器,推荐)
@cached缓存结果,@cache_invalidate失效缓存- 读用 cached,写用 invalidate,复杂场景用编程式
下一步: 第 14 章讲异步任务 Celery,处理耗时的后台操作。
对应官方文档:
缓存(backend/reference/cache.html)