Skip to content

第 13 章 缓存系统 ​

学习目标 ​

学完本章,你应该能:

  1. 理解为什么需要缓存,以及 L1/L2 两级缓存的区别
  2. 掌握编程式缓存和声明式缓存两种用法
  3. 能根据场景选择合适的缓存策略

一、为什么需要缓存 ​

有些数据(如用户详情、字典配置、热门列表)查询频繁但很少变化。每次都查数据库,既慢又浪费资源。

缓存就是把这类数据暂存在更快的存储(内存/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)