九、redis 入门 之 数据库和缓存一致性问题
一、问题描述
Redis 作为缓存,数据本质是数据库的 “副本”。当业务需要更新数据时(如修改商品库存、用户余额),若仅更新 Redis 或仅更新数据库,或两者更新顺序错误,会导致Redis 中的数据 ≠ 数据库中的数据,最终用户查询时获取到不一致的结果,影响业务正确性(如库存超卖、余额显示错误)。
二、解决方案
1、延时双删
删缓存 → 更 MySQL → 延迟(如 500ms)→ 再删缓存
- **核心操作**:先删除Redis缓存,再更新MySQL数据,之后延迟几百毫秒再次删除Redis缓存。
- **作用**:即使更新MySQL期间,其他线程读取旧数据并写入Redis,第二次删除操作也能清除旧数据,保证缓存与数据库一致性。
2、队列 + 重试机制
更 MySQL → 删缓存(失败则发 MQ)→ MQ 重试删缓存
- 更新数据:业务操作先修改 MySQL 数据库的数据,确保源数据准确。
- 删除缓存:尝试删除 Redis 中对应数据的缓存,让后续读请求重新从数据库加载最新数据。若此步骤失败(如网络波动、Redis 故障 ),进入重试流程。
- 消息队列(MQ)介入:当删除缓存失败,将需删除的缓存 Key 发送至 MQ。
- 推送与重试:MQ 接收 Key 后,推送回业务系统,触发重试删除缓存操作,通过多次重试(结合 MQ 的消息持久化、重试特性 ),最终保证 Redis 缓存被删除,后续读操作能获取数据库最新数据,实现缓存与数据库的最终一致性,避免因缓存未及时清理导致的数据不一致问题。
3、异步更新缓存(基于订阅 binlog 的同步机制)
更 MySQL(写 binlog)→ 订阅程序解析 binlog → 删缓存(失败则发 MQ 重试)
此机制利用 MySQL 的 binlog 日志,实现 Redis 缓存与数据库的异步同步,保障数据最终一致,核心流程如下:
- 数据库操作:业务更新 MySQL 数据,MySQL 会将操作记录写入 binlog 日志(这是 MySQL 用于主从同步、数据恢复的日志文件 )。
- 订阅 binlog:部署专门的 “订阅 binlog 程序”,实时监听 binlog 日志,解析出更新的数据内容及对应的缓存 Key(比如更新了某商品库存,解析出商品 ID 作为缓存 Key )。
- 缓存操作与重试:
- 程序拿到数据和 Key 后,尝试删除 Redis 中对应缓存(让后续读请求重新从数据库加载最新数据 )。
- 若删除缓存失败,会将 “操作数据 + Key” 发送到 MQ(消息队列 ),借助 MQ 的重试机制,再次触发缓存删除操作,直到删除成功,确保 Redis 缓存与数据库数据最终同步。
使用阿里的一款开源框架canal,通过该框架可以对MySQL的binlog进行订阅,而canal正是模仿Tmys
的slave数据库的备份请求,使得Redis的数据更新达到了相同的效果
MQ消息中间可以采用RocketMQ来实现推送。
三、方案优劣对比
四、方案选型建议
- 快速落地小项目:选延时双删,成本低、逻辑简单,适合对一致性要求不极致的场景(如普通商品详情缓存)。
- 中等复杂度业务:选队列 + 重试机制,通过 MQ 保障重试,平衡实现成本与一致性(如订单状态缓存同步)。
- 高并发 / 强解耦场景:选binlog 订阅,彻底解耦业务与缓存操作,适合核心链路(如电商库存、金融交易)。