在Spring Boot中解决Redis和MySQL的数据一致性问题,业界最核心的思路是追求"最终一致性"而非"强一致性"。因为强一致性会带来巨大的性能开销,在高并发场景下不切实际。
下面是从简单到复杂的几种主流解决方案,你可以根据业务对一致性的要求来选择。
💡 方案一:Cache-Aside Pattern(旁路缓存模式)- 最常用方案
这是最经典、应用最广泛的模式,核心原则是读请求由缓存主导,写请求由数据库主导。
- 读流程:查询Redis → 命中则返回 → 未命中则查MySQL,并写入Redis。
- 写流程 :先更新MySQL,成功后立即删除Redis缓存。下次读取时,会重新从MySQL加载最新数据。
为什么是删除而不是更新缓存?
直接更新缓存存在并发"写覆盖"风险(如两个线程先后更新数据库,但缓存更新顺序错乱导致脏数据)。而删除是幂等操作,更安全。
- 优点:实现简单,性能好,能满足大部分业务场景。
- 缺点:存在极小概率的不一致窗口期。
- Spring Boot 实现 :在Service层写方法上使用
@Transactional注解,保证MySQL更新成功后,再执行删除缓存操作。
🚀 方案二:延迟双删 - 针对极端情况的优化
此方案是对方案一的改进,旨在解决"先更新数据库,再删除缓存"策略在极端并发下可能出现的脏数据问题。
- 核心流程 :
- 先删除Redis缓存。
- 更新MySQL数据库。
- 休眠一段时间(毫秒级),再次删除Redis缓存。
这第二次删除是为了清除在休眠期间,可能由其他线程从MySQL读入Redis的旧数据,从而最大限度地保证最终一致性。
- 优点:极大降低了并发脏数据的可能性。
- 缺点:增加了写请求的延迟,且休眠时间难以精确控制。
📨 方案三:消息队列(MQ)异步确保 - 解决缓存删除失败
此方案主要解决"先更新MySQL,再删除Redis"时,删除缓存操作可能失败的问题。
-
核心流程:
- 更新MySQL数据库。
- 发送一条"删除缓存"的消息到MQ。
- MQ消费者收到消息后,执行Redis删除操作。若失败,进行重试,直至成功。
-
优点:解耦了数据库更新和缓存删除,并通过MQ的重试机制保证了缓存最终会被删除。
-
缺点:引入MQ增加了系统复杂度和维护成本。
-
Spring Boot 实现:可使用Spring Cloud Stream或Spring AMQP(RabbitMQ)等框架。
🎯 方案四:Canal监听Binlog - 终极方案,业务无侵入
此方案通过监听MySQL的Binlog(二进制日志)来实现缓存更新,对业务代码零侵入,被认为是实现最终一致性的终极方案。
-
核心流程:
- 应用程序只更新MySQL数据库。
- Canal伪装成MySQL的从库,监听并解析Binlog变更。
- Canal将变更事件发送给消费者(如MQ或直接处理)。
- 消费者收到事件后,更新或删除Redis缓存。
-
优点:业务代码无需关心缓存,一致性高,彻底解耦。
-
缺点:架构复杂,需要独立部署和维护Canal服务,运维成本高。
-
Spring Boot 实现 :在项目中引入
canal-spring-boot-starter依赖,并配置Canal服务地址,然后编写监听器处理Binlog事件。
📊 方案对比总结
| 方案 | 实现复杂度 | 一致性保证 | 性能影响 | 业务侵入性 | 适用场景 |
|---|---|---|---|---|---|
| Cache-Aside | 低 | 最终一致性(弱) | 低 | 有 | 90%以上业务场景 |
| 延迟双删 | 中 | 最终一致性(较强) | 中(有延迟) | 有 | 对一致性要求稍高的场景 |
| 消息队列(MQ) | 中高 | 最终一致性(可靠) | 低 | 有 | 需确保缓存删除成功的场景 |
| Canal + Binlog | 高 | 最终一致性(最强) | 极低 | 无 | 核心业务、高并发场景 |
💎 最佳实践与建议
- 首选Cache-Aside:对于绝大多数业务,**"先更新MySQL,再删除Redis"**的Cache-Aside模式是最佳起点。它足够简单,能满足大部分需求。
- 设置缓存过期时间:无论采用何种策略,都应为Redis缓存设置一个合理的过期时间(TTL)作为兜底方案,强制数据最终一致。
- 处理缓存击穿/穿透:高并发下,需考虑使用互斥锁、布隆过滤器等手段,防止热点数据失效或查询不存在的数据时,大量请求直接击穿至数据库。
- 权衡业务:从业务角度出发,判断可以接受多长时间的数据不一致,再选择对应的技术方案。
- 拥抱最终一致性:在分布式系统中,要接受"最终一致性"的理念,而不是强求"强一致性"。
如果你的业务对一致性要求极高且并发量大,可以考虑Canal方案;如果追求简单快速,Cache-Aside是首选。