在并发环境下,“双写不一致”指的是多个线程同时更新数据库和缓存时,因操作顺序或执行时机差异导致缓存与数据库数据不匹配的问题。解决这一问题的核心是保证数据库与缓存操作的原子性、顺序性或最终一致性,具体方案需结合业务对一致性的要求(强一致性/最终一致性)和性能需求选择。
一、核心解决方案
1. 延迟双删策略(最终一致性,适合高并发场景)
在“先删除缓存,再更新数据库”的基础上,增加第二次删除缓存的延迟操作,解决并发读写导致的不一致。
- 步骤:
- 线程A删除缓存;
- 线程A更新数据库;
- 延迟一段时间(如几百毫秒)后,线程A再次删除缓存。
- 原理:
延迟删除的目的是覆盖“数据库更新期间,其他线程读取旧数据并写入缓存”的场景。例如:- 线程A删除缓存→更新数据库(耗时100ms);
- 线程B在这100ms内读取数据,发现缓存为空,从数据库读取旧值并写入缓存;
- 线程A更新数据库后,延迟100ms再次删除缓存,此时线程B写入的旧值会被删除,后续请求会从数据库加载新值到缓存,保证最终一致。
- 注意:
- 延迟时间需大于业务中“读取+写入缓存”的最大耗时(可通过压测确定);
- 可通过消息队列或定时任务执行延迟删除,避免阻塞主线程。
2. 加锁机制(强一致性,适合低并发、高一致性场景)
通过分布式锁或本地锁,保证同一时间只有一个线程操作数据,避免并发冲突。
- 分布式锁方案(适合分布式系统):
- 线程获取分布式锁(如基于Redis的
SET NX命令); - 持有锁的线程执行“更新数据库→删除缓存”(或“删除缓存→更新数据库”);
- 操作完成后释放锁,其他线程需等待锁释放后再执行。
- 线程获取分布式锁(如基于Redis的
- 本地锁方案(适合单节点服务):
用synchronized或ReentrantLock锁住数据更新逻辑,确保单JVM内操作串行化。 - 优点:严格保证一致性,避免并发冲突;
- 缺点:降低并发性能(锁竞争会阻塞线程),需注意锁超时和死锁问题。
3. 数据库binlog同步(最终一致性,适合高可用场景)
通过监听数据库的binlog日志,异步同步数据到缓存,彻底解耦数据库与缓存的更新逻辑。
- 步骤:
- 线程仅更新数据库(不操作缓存);
- 数据库更新后,binlog日志记录变更(如MySQL的binlog);
- 通过中间件(如Canal)监听binlog,解析出数据变更后,异步更新或删除缓存。
- 原理:以数据库为“唯一数据源”,缓存的更新完全依赖数据库的变更日志,避免双写操作的时序问题。
- 优点:
- 无锁竞争,不影响业务接口性能;
- 即使缓存更新失败,可通过重试机制(如消息队列重试)保证最终一致;
- 缺点:
- 存在一定延迟(binlog同步→缓存更新的耗时),不适合强实时性场景;
- 需部署binlog监听组件,增加系统复杂度。
4. 版本号机制(最终一致性,适合有序更新场景)
为数据增加版本号字段,通过版本号判断缓存是否需要更新,避免旧数据覆盖新数据。
- 步骤:
- 数据库表中新增
version字段(初始为0,每次更新+1); - 线程更新数据库时,同时更新版本号(如
UPDATE ... SET version = version + 1 WHERE id = ? AND version = ?); - 缓存中存储数据时,同时保存版本号;
- 读取缓存时,若缓存版本号低于数据库最新版本号,则忽略缓存,从数据库加载新数据并更新缓存。
- 数据库表中新增
- 原理:通过版本号确保“只有最新版本的数据能写入缓存”,避免并发更新时旧数据覆盖新数据。
- 适用场景:数据更新有明确顺序(如商品库存、用户积分),且允许短暂的版本差。
5. 读写锁分离(降低冲突,适合读多写少场景)
通过读写锁控制“读缓存”和“写缓存+数据库”的并发关系,减少冲突概率。
- 规则:
- 写操作(更新数据库+缓存):获取写锁(排他锁),此时其他读写操作需等待;
- 读操作(读取缓存/数据库):获取读锁(共享锁),多个读操作可并行,但需等待写锁释放。
- 原理:保证写操作的原子性,避免读操作在写操作执行过程中加载旧数据到缓存。
- 注意:需使用分布式读写锁(如Redisson的
RReadWriteLock),适合分布式系统。
二、方案对比与选择建议
| 方案 | 一致性级别 | 性能 | 复杂度 | 适用场景 |
|---|---|---|---|---|
| 延迟双删 | 最终一致性 | 高 | 低 | 高并发、允许短暂不一致(如商品详情) |
| 分布式锁 | 强一致性 | 中(有锁竞争) | 中 | 低并发、强一致性(如订单状态) |
| binlog同步 | 最终一致性 | 高 | 高 | 高可用、读写分离架构(如用户信息) |
| 版本号机制 | 最终一致性 | 高 | 中 | 有序更新场景(如库存、积分) |
| 读写锁分离 | 最终一致性 | 中 | 中 | 读多写少、需减少冲突(如新闻资讯) |
三、总结
解决并发双写不一致的核心思路是:
- 强一致性需求:优先选择分布式锁或读写锁,通过串行化操作避免冲突;
- 高并发+最终一致性:优先选择延迟双删或binlog同步,在性能与一致性间平衡;
- 复杂业务场景:可组合多种方案(如“版本号+延迟双删”),进一步降低不一致概率。
需注意,没有“万能方案”,需根据业务对一致性的敏感度、并发量、系统复杂度等因素综合选择,避免过度设计。