本文目录导读:

这是一个非常经典且重要的后端开发问题,缓存更新的目标是保证数据库(DB)和缓存(如Redis)中的数据最终一致,同时兼顾高并发下的性能。
下面我来为你详细梳理常见的缓存更新案例,包括其策略、优缺点和适用场景。
核心难点:并发写与缓存一致性问题
假设我们有一个读取流程:
- 读请求,先查缓存。
- 缓存未命中,查数据库。
- 将数据库结果写入缓存。
- 返回数据。
问题发生在写操作时:当有并发写请求(更新DB)和读请求(更新缓存)同时发生时,很容易出现数据不一致。
四大主流缓存更新策略
Cache Aside Pattern(旁路缓存)—— 最常用、最推荐
这是业界最标准的模式,具体做法是:
- 读: 先读缓存,命中则直接返回;未命中则读数据库,然后设置缓存,最后返回。
- 写: 先更新数据库,然后删除缓存。
为什么是“先更新DB,再删除缓存”而不是“先更新缓存”或“先删除缓存”?
原因1:避免并发读写导致的脏数据
- 场景: 线程A发起写操作,线程B发起读操作。
- 错误做法(先删缓存,再更新DB):
- A删除缓存。
- B读缓存未命中,从DB读到旧数据。
- B将旧数据写入缓存。
- A将新数据写入DB。
- 结果: 缓存中是旧数据(脏数据),DB是新数据。
- 正确做法(先更新DB,再删缓存):
- A更新DB(新数据)。
- B读缓存未命中,从DB读到新数据(或旧数据,取决于隔离级别,但在大多数RC级别下读到新数据概率大)。
- B将数据写入缓存。
- A删除缓存。
- 结果: 最终缓存会被删除,下一次读请求会重新从DB加载新数据,保证最终一致性。
原因2:避免删除操作本身失败
- 如果先删除缓存,然后更新DB失败,缓存已删除,DB数据未变,下次读请求会查到旧数据写入缓存,导致缓存和DB不一致(缓存是旧数据,DB也是旧数据,但业务上可能期望新的)。
- 如果先更新DB,再删除缓存,即使删除缓存失败,我们还可以通过重试机制(如消息队列、定时任务)来补偿删除操作。
优点: 简单、高效、对并发读友好。 缺点: 短暂的不一致窗口(删除缓存成功前,可能有读请求读到旧缓存),在高并发下,这个窗口非常小,通常可以接受。
Read Through / Write Through(穿透缓存)
应用层不直接与数据库交互,而是通过缓存层(如提供一个缓存抽象层,Caffeine + Redis 的组合)来读写数据。
- Read Through: 缓存层负责读取数据库,如果缓存未命中,缓存自身会去加载数据库并填充,对应用透明。
- Write Through: 写请求直接写入缓存,缓存层负责同步写入数据库(同步阻塞)。
- 优点: 一致性高(写操作必须同时成功或失败)。
- 缺点: 写性能差(因为要同步写DB),不适合写密集型场景。
Write Behind Caching(异步写回)
数据只写缓存,缓存异步批量写入数据库。
- 优点: 写性能极高(只写内存),适合写入量巨大、对数据一致性要求不严格的场景(如日志、点赞数、PV/UV统计)。
- 缺点: 数据有丢失风险(如果缓存宕机),一致性窗口较长,业务需要能容忍不一致。
先更新缓存,再更新数据库(不推荐)
- 严重缺陷: 如果缓存更新成功,数据库更新失败,则数据永久不一致(缓存有脏数据),几乎只有特殊业务(如并发控制要求极强且必须立即失效的场景,但通常用分布式锁替代)才会使用。
实战中的权衡与优化
在实际业务中,你通常会遇到以下问题,需要做出权衡:
问题1:删除缓存失败怎么办?
方案: 引入重试机制。
- 设计模式: 在删除操作失败时,将任务(key + 操作类型)写入消息队列(如Kafka、RabbitMQ)。
- 异步消费者: 一个异步的消费者监听队列,不断尝试删除该缓存。
- 兜底: 可以设置重试次数上限或者延迟重试,确保最终删除成功。
代码示例(伪代码):
public void updateData(Long id, Data data) {
// 1. 更新数据库
database.update(id, data);
// 2. 尝试删除缓存
try {
redisCache.delete("data:" + id);
} catch (Exception e) {
// 3. 如果删除失败,发送消息到MQ,由MQ消费者重试
mq.send(new CacheDeleteMessage("data:" + id));
}
}
问题2:高并发下短时间内不一致怎么办?
方案: 延迟双删。
- 做法: 在“先更新DB,再删缓存”的基础上,延迟一段时间(比如1秒)后再删一次缓存。
- 原理: 这个延迟时间要大于“读请求从DB读数据到写入缓存”的时间,这样即使有并发读请求在此期间写入了旧缓存,延迟删除也会把它清掉。
- 缺点: 增加了实现复杂度,且这个“足够长的时间”很难精确估算。
问题3:是否需要加锁?
- 不需要: 对于绝大多数业务场景,删除缓存策略 + 重试机制已经足够,因为不一致窗口极短,且最终一致。
- 需要: 如果业务要求“强一致性”(如银行账户余额、秒杀库存),那么应该放弃使用缓存,直接访问数据库,并配合数据库的乐观锁或悲观锁,或者使用分布式锁来对某个Key的读写进行串行化。
如何选择?
| 业务场景 | 推荐策略 | 理由 |
|---|---|---|
| 读多写少,对一致性要求中等 | Cache Aside(先更新DB,再删缓存) | 简单、高效,不一致窗口可控。 |
| 读写比例接近,对一致性要求高 | Read Through / Write Through (或 直接读DB) | 一致性高,但写性能下降。 |
| 大量写操作,偶尔读,允许最终一致 | Write Behind (异步写回) | 写性能极佳,但有一定数据丢失风险。 |
| 高并发,对延迟敏感 | 在Cache Aside基础上,采用延迟双删 | 最大程度降低不一致窗口。 |
最终建议:
在大多数业务系统中,Cache Aside(先更新DB,再删缓存)+ 删除失败的重试机制 是最通用、最稳妥的选择,它平衡了性能、一致性和实现复杂度。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播