本文目录导读:

选择开源项目的缓存策略,核心原则是:没有银弹,一切取决于你的业务场景和数据访问模式。
下面从几个关键维度帮你拆解如何选择和设计缓存策略。
第一步:明确你的核心需求(灵魂拷问)
在选择具体策略前,先回答这三个问题:
- 数据容忍丢失吗? 如果缓存宕机,数据能否从后端重建?如果能,考虑纯内存缓存(如Redis);如果不能,需持久化缓存(如Redis RDB/AOF)或本地缓存+深度备份。
- 实时性要求多高? 数据变化后,多久需要同步到缓存?秒级、毫秒级还是最终一致?
- 读写比例是多少? 读多写少(典型缓存场景),还是读写均衡或写多读少?
主流策略对比与选择路径
| 策略 | 核心思想 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|---|
| 旁路缓存 | 应用先查缓存,命中返回;未命中查DB,回写缓存。 | 绝大多数读多写少场景 (如用户信息、配置) | 实现简单,数据一致性强 (以DB为准) | 会有缓存穿透、击穿、雪崩问题 |
| 读写穿透 | 应用只与缓存交互,缓存负责读写后端DB。 | 期望应用抽象存储层,简化访问逻辑 | 应用无感,封装性好 | 复杂度高,需要可靠的缓存组件 |
| 异步回写 | 应用只更新缓存,异步批量写回DB。 | 写操作频繁,可容忍短暂数据丢失 (如点赞数、PV) | 极高写入性能,DB压力小 | 数据丢失风险,实现复杂,有可靠性问题 |
| 本地缓存 | 在应用进程内缓存数据 (如Guava Cache, Caffeine)。 | 数据量小,变化极慢 (如系统配置、字典) | 极快,无网络开销 | 容量受限,更新需广播,重启丢失 |
面对三个经典问题的应对策略
无论选哪种,以下问题都可能出现,需要结合策略解决:
-
缓存穿透(查不存在的数据)
- 策略:使用布隆过滤器拦截根本不存在的数据请求;或者对空结果也进行短期缓存。
- 选择:适合旁路缓存。
-
缓存击穿(热点Key过期)
- 策略:互斥锁(只有一个线程去加载数据);或者热点数据永不过期(物理永不过期,逻辑过期时异步更新)。
- 选择:适合旁路缓存。
-
缓存雪崩(大量Key同时过期或缓存宕机)
- 策略:过期时间加随机值;Redis集群/哨兵确保高可用;限流降级保护DB;多级缓存(本地+远程)。
- 选择:所有策略都需要考虑基础设施的健壮性。
开源项目中的典型选择(技术栈参考)
-
Web应用:
- Spring Cache:默认支持旁路缓存,通过
@Cacheable注解实现,搭配Caffeine(本地)或Redis(分布式)。 - MyBatis:二级缓存本质是读写穿透,由ORM框架管理缓存与数据库的同步。
- Spring Cache:默认支持旁路缓存,通过
-
微服务/API网关:
- Nginx + Lua + Redis:旁路缓存,对API响应进行缓存,极大减少上游服务压力。
-
数据库缓存:
- MySQL Query Cache:已被废弃,因为粒度太粗,失效频繁,现在推荐应用层旁路缓存。
-
框架级别:
- Redis:本身作为旁路缓存的存储层,也支持读写穿透(通过Redisson的分布式锁,但实现复杂)。
- Memcached:纯粹的旁路缓存,不支持持久化。
实操建议(何时选择什么策略)
- 新手或快速迭代项目:无脑选旁路缓存(Cache-Aside) + Redis,实现最简单,最不容易犯错,数据一致性最好,把精力放在解决穿透、击穿、雪崩上。
- 读多写少,且对一致性容忍度高(如文章阅读数、商品浏览量):考虑异步回写,先用消息队列或定时任务异步写入DB。
- 写非常频繁,且必须保证数据最终一致(如分布式系统状态同步):考虑读写穿透 + 分布式锁,但实现成本高,建议先用成熟方案(如Paxos/Raft)。
- 对性能要求极度苛刻,且数据量小(如系统启动配置、静态字典):本地缓存(Caffeine) + 广播/定时刷新,注意更新通知机制。
一个实用的决策树
-
数据是否极其敏感,不能丢?
- 是 -> 使用旁路缓存 + 持久化存储(DB)。
- 否 -> 进入下一步。
-
写操作是否远多于读操作?
- 是 -> 优先考虑异步回写(MQ+定时任务)。
- 否 (典型读多写少) -> 进入下一步。
-
数据量是否小且变化极慢?
- 是 -> 使用本地缓存(Caffeine/Guava)。
- 否 -> 使用旁路缓存 + 分布式缓存(Redis),并设计好防击穿、穿透、雪崩机制。
最后的建议:在项目初期,优先采用旁路缓存(Cache-Aside) 搭配Redis,它清晰、可预测、易于调试,几乎能解决80%的缓存问题,等到你真的遇到性能瓶颈或特定业务需求时,再逐步引入更复杂的策略,不要为了炫技而过度设计。