Redis与数据库的双写一致性是分布式系统中常见的挑战,核心目标是在缓存(Redis)和数据库之间维持数据的一致性。以下是常见的解决方案及其实现逻辑:
1. 缓存更新策略
1.1 Cache-Aside(旁路缓存)
核心逻辑:由应用层管理缓存和数据库的读写。
- 读操作:
- 先读缓存,命中则返回;
- 未命中则读数据库,回填缓存。
- 写操作:
- 先更新数据库;
- 再删除缓存(而非更新,避免并发写导致脏数据)。
- 读操作:
优点:简单、通用,适用于读多写少场景。
缺点:
- 短暂不一致:若数据库更新后、缓存删除前有读请求,会读到旧数据。
- 需处理缓存删除失败(需重试或异步补偿)。
1.2 Write-Through(直写)
核心逻辑:所有写操作同时更新数据库和缓存。
- 写操作:
- 先更新缓存;
- 同步更新数据库。
- 写操作:
优点:强一致性。
缺点:
- 写入性能低(需同时写缓存和数据库);
- 不适用于写多场景。
2. 双删策略
延迟双删
核心逻辑:
- 先删除缓存;
- 更新数据库;
- 延迟一段时间后再次删除缓存(确保主从同步完成后的旧数据清理)。
适用场景:数据库主从架构,存在同步延迟。
关键参数:延迟时间需大于主从同步时间(如500ms)。
缺点:依赖延迟时间的准确性。
3. 异步补偿
3.1 基于消息队列
核心逻辑:
- 更新数据库;
- 发送消息到MQ,异步删除缓存;
- 消费端重试失败任务。
优点:解耦、保证最终一致性。
缺点:系统复杂度增加,需维护MQ。
3.2 基于数据库Binlog(如Canal)
核心逻辑:
- 数据库更新后,通过Binlog(如MySQL)捕获变更;
- 通过中间件(如Canal)解析Binlog;
- 异步更新或删除缓存。
优点:无侵入性,保证最终一致性。
缺点:依赖数据库日志同步机制,延迟可能较高。
4. 强一致性方案
4.1 分布式锁
核心逻辑:
- 写操作前获取分布式锁;
- 更新数据库后删除缓存;
- 读操作若缓存未命中,需先获取锁再查数据库。
优点:强一致性。
缺点:性能低,复杂度高。
4.2 版本号/时间戳
核心逻辑:
- 数据写入时附带版本号;
- 更新时校验版本号,防止旧数据覆盖;
- 缓存中存储版本号,读请求需校验。
适用场景:对一致性要求极高的场景(如库存扣减)。
缺点:实现复杂,需维护版本号。
5. 方案对比与选择
| 方案 | 一致性级别 | 性能 | 复杂度 | 适用场景 |
|---|---|---|---|---|
| Cache-Aside | 最终一致 | 高 | 低 | 读多写少 |
| 延迟双删 | 最终一致 | 中 | 中 | 主从同步延迟场景 |
| 异步补偿(MQ/Binlog) | 最终一致 | 中 | 高 | 高并发、最终一致场景 |
| 分布式锁 | 强一致 | 低 | 高 | 对一致性要求极高的场景 |
6. 最佳实践
- 优先最终一致性:大多数场景不需要强一致,可通过异步补偿或延迟双删实现。
- 监控与告警:监控缓存与数据库不一致的时长,设置阈值告警。
- 兜底策略:
- 缓存设置合理的TTL,避免长期不一致;
- 关键数据(如库存)采用强一致性方案。
总结
选择方案需权衡业务场景(一致性要求、读写比例)、系统复杂度及性能。例如:
- 电商商品详情页:Cache-Aside + 异步补偿;
- 金融交易系统:分布式锁 + 版本号。