[db:标题]
摘要:前言 缓存(例如:Redis)和数据库的数据一致性问题,也是一个经典的问题,无论是在面试还是在日常工作当中,遇到的概率非常大。尤其是在高并发的场景下,这个问题会变得更加严重。 业内常见的解决方案 先更新数据库,再删缓存。 延迟双删:先删缓存
前言
缓存(例如:Redis)和数据库的数据一致性问题,也是一个经典的问题,无论是在面试还是在日常工作当中,遇到的概率非常大。尤其是在高并发的场景下,这个问题会变得更加严重。
业内常见的解决方案
先更新数据库,再删缓存。
延迟双删:先删缓存,再更新数据库,延时一段时间,再删一次缓存。
Canal/Maxwell订阅 binlog,更新数据库,基于中间件监听binlog后删除缓存。
这三种方案基本上就能解决市面上大部分的业务场景中的缓存不一致的问题了。
方案
优点
缺点
适用场景
先更新数据库,再删缓存
实现简单,对业务代码侵入小
删缓存失败,存在数据不一致的问题
95%一般场景都适合,尤其是并发量不大,或者对一致性要求不太高的。
延迟双删
数据一致性保证更好
第二次删失败要重试;延迟 时长要靠压测估
对数据一致性要求高,并发量大的场景。
更新数据库,基于中间件监听binlog后删除缓存
与业务代码解耦,一致性有保障
实现复杂,需要引入中间件
适合基础建设完善,且并发高对一致性要求高的场景
先写数据库,再删缓存
先来说一下,为什么是删除缓存,而不是更新缓存?
并发写冲突风险高
在高并发场景下,多个线程/服务可能同时更新同一份数据,如果采用更新缓存的策略,容易出现竞态条件,导致缓存中的数据是旧值或错误值。
假设有两个线程 A 和 B,同时更新数据库和缓存:
时间
线程 A
线程 B
T1
更新数据库为 100
T2
更新数据库为 200
T3
更新缓存为 100(A 的旧值)
T4
更新缓存为 200(B 的新值)
最终缓存中是 200,看起来没问题,但如果 T3 发生在 T4 之后(由于网络延迟、线程调度等),缓存就会被A的旧值覆盖,变成 100,导致缓存和数据库不一致。
时间
线程 A
线程 B
T1
更新数据库为 100
T2
更新数据库为 200
T3
更新缓存为 200(B 的新值)
T4
更新缓存为 100(A 的旧值)
但是删除缓存就不存在这个问题,删除缓存操作具有幂等性,多次执行不会产生副作用,相比更新缓存更适合在高并发场景下使用。
缓存更新逻辑复杂,容易出错
更新缓存时,通常需要:
从数据库读取最新值
构造缓存对象(可能涉及序列化、字段拼接等)
写入 Redis
这个过程中,任何一个环节出错,都可能导致缓存中的数据是不完整或错误的。
删除缓存逻辑简单,只要一个 DEL 操作,出错概率低,恢复快。
延迟加载(Lazy Loading)更自然
删除缓存后,下一次读取时会触发缓存未命中,然后从数据库加载最新数据并回填缓存。
这种延迟加载机制:
保证了缓存中的数据总是从数据库加载的最新值
避免了提前写入缓存时可能用到的旧数据
删除缓存配合“先更新数据库,再删除缓存”策略更可靠
先更新数据库,再删除缓存,这个策略在大多数情况下能保证最终一致性,即使出现极端情况(如删除缓存失败),也可以通过消息队列重试或定时任务补偿。
但是有时候删除缓存还要考虑一些极端场景,例如:删除缓存后,大量请求打到数据库,造成缓存击穿。通常的解决方案就是
使用互斥锁(如 Redis 分布式锁)保证只有一个线程去加载数据
在一致性优先的场景下,删除缓存比更新缓存更稳妥、更简单、更容易兜底的选择。
先写数据库还是先删缓存?
上面通过比较,我们知道了,删缓存比更新缓存更好,所以为了降低数据不一致的情况产生,选择删缓存。那么是先更新数据库再删缓存呢,还是说,先删缓存再更新数据库呢?
先删缓存,再更新数据库
举个例子来说明:
还是有两个线程 A 和 B,线程A是写操作,线程B是读操作。
时刻
线程 A(写)
线程 B(读)
T1
删除缓存成功
T2
缓存 miss,去库读旧值 100
T3
把 100 回填缓存
T4
数据库更新为 200(还没提交或同步完成)
也有可能T3发生在T4之后
时刻
线程 A(写)
线程 B(读)
T1
删除缓存成功
T2
缓存 miss,去库读旧值 100
T3
数据库更新为 200(还没提交或同步完成)
T4
把 100 回填缓存
如果先删缓存,就有可能导致缓存里在很长一段时间内都是 旧值 100,直到下一次失效或更新。
先更新数据库,再删缓存
如果我们先更新数据库,再删除缓存,有一个好处,那就是缓存删除失败的概率还是比较低的,除非是网络问题或者缓存服务器宕机的问题,否则大部分情况都是可以成功的。
