目录
问题:
一台MySQL,一台Redis,两台应用服务器,用户的数据存储持久化在MySQL中,缓存在Redis,有请求的时候从Redis中获取缓存的用户数据,有修改则同时修改MySQL和Redis中的数据。现在问题是:
1. 先保存到MySQL和先保存到Redis都面临着一个保存成功而另外一个保存失败的情况,这样,如何保证MySQL与Redis中的数据同步?
2. 两台应用服务器的并发访问,如何保证数据的安全性?
解决方法:
数据库和缓存之间一般不需要强一致性。
一般缓存是这样的:
- 读的顺序是先读缓存,后读数据库
- 写的顺序是先写数据库,然后写缓存
- 每次更新了相关的数据,都要把该缓存清理掉
- 为了避免极端条件下造成的缓存与数据库之间的数据不一致,缓存需要设置一个失效时间。时间到了,缓存自动被清理,达到缓存和数据库数据的“最终一致性”
作者:Doing
链接:https://www.zhihu.com/question/36413559/answer/310691736
一文讲透数据库缓存一致性问题
主要围绕数据库缓存一致性问题展开,详细阐述了缓存的意义、引入缓存后的一致性挑战、更新缓存的手段、最终一致性的保证方法、减少缓存删除 / 更新失败的措施、复杂多缓存场景的处理方式以及通过订阅 MySQL binlog 处理缓存的方式。
- 缓存的意义:缓存通过使用高速存储介质(如 Redis)或就近使用本地内存,以空间换时间的方式提升读性能,帮助业务应对高流量读请求。
- 引入缓存后的一致性挑战:数据同时存在于数据库和缓存中,由于两者之间无事务保证,无法做到强一致,但可通过优化使不一致时间窗口尽可能短,达到最终一致 。
- 更新缓存的手段:常见策略有更新数据库后更新缓存、更新数据库前更新缓存、更新数据库后删除缓存、更新数据库前删除缓存。分析各策略在并发场景下的潜在问题,综合来看,更新数据库后删除缓存更适合读多写少场景,更新数据库后更新缓存适合读写相当或写多读少场景。
- 最终一致性如何保证:给缓存设置过期时间,当出现更新 Redis 失败等极端场景时,缓存失效后重新查询数据库并回写,可保证最终一致性。
- 如何减少缓存删除 / 更新的失败:借助消息中间件(如 RocketMQ)的重试机制减少不一致情况。若消息中间件无事务消息特性,可采用消息表方式确保更新数据库和发送消息一起成功。
- 如何处理复杂的多缓存场景:一个数据库记录更新可能涉及多个 Key 的更新,可将更新缓存的操作以 MQ 消息方式发送,由不同系统或专门系统订阅并聚合操作 。
- 通过订阅 MySQL binlog 的方式处理缓存:利用类似 Canal 的产品订阅 MySQL 的 binlog,监听数据变化,集中处理相关缓存,实现缓存与数据库的一致性。
在分布式环境下,要保证 MySQL 和 Redis 数据的一致性,可采用以下几种方法:
缓存失效策略
- 读操作:先从 Redis 里读取数据,若未命中,再从 MySQL 中读取,然后把数据存入 Redis。
- 写操作:先更新 MySQL 中的数据,接着使 Redis 里的缓存失效。下次读取时,会重新从 MySQL 加载最新数据到 Redis。
消息队列异步处理
借助消息队列(如 Kafka、RabbitMQ),异步地更新 Redis 缓存。
- 写操作:先更新 MySQL 数据,然后把更新消息发送到消息队列。
- 消息消费:消费者从消息队列接收消息,更新 Redis 中的缓存。这种方式能确保即使在更新 Redis 时出现失败,也可通过重试机制保证最终一致性。
分布式事务
利用分布式事务框架(如 Seata)保证 MySQL 和 Redis 的操作在同一个事务里。
- 两阶段提交(2PC):在事务开始时,协调者向所有参与者(MySQL 和 Redis)发送准备请求,参与者执行操作并反馈结果。若所有参与者都准备好,协调者发送提交请求;若有参与者失败,协调者发送回滚请求。
- 补偿事务:当某个操作失败时,执行补偿操作来恢复到之前的状态。
定期数据同步
定期把 MySQL 中的数据同步到 Redis。可使用定时任务或者触发器,周期性地对比 MySQL 和 Redis 中的数据,若有差异就进行更新。
缓存更新策略
- 更新缓存:在更新 MySQL 数据后,同时更新 Redis 中的缓存。不过这种方式可能会遇到并发问题,需要使用锁机制来保证数据一致性。
- 延迟双删:更新 MySQL 数据后,先删除 Redis 缓存,然后在一定时间后再次删除 Redis 缓存,以此避免在更新 MySQL 数据时,有其他请求读取到旧数据并写入 Redis。