缓存和数据库一致性问题,看这篇就够了
源 / 文/
到底是更新缓存还是删缓存? 到底选择先更新数据库,再删除缓存,还是先删除缓存,再更新数据库? 为什么要引入消息队列保证一致性? 延迟双删会有什么问题?到底要不要用? ...
引入缓存提高性能
数据库的数据,全量刷入缓存(不设置失效时间) 写请求只更新数据库,不更新缓存 启动一个定时任务,定时把数据库的数据,更新到缓存中
缓存利用率低:不经常访问的数据,还一直留在缓存中 数据不一致:因为是「定时」刷新缓存,缓存和数据库存在不一致(取决于定时任务的执行频率)
缓存利用率和一致性问题
写请求依旧只写数据库 读请求先读缓存,如果缓存不存在,则从数据库读取,并重建缓存 同时,写入缓存中的数据,都设置失效时间
先更新缓存,后更新数据库 先更新数据库,后更新缓存
并发引发的一致性问题
线程 A 更新数据库(X = 1) 线程 B 更新数据库(X = 2) 线程 B 更新缓存(X = 2) 线程 A 更新缓存(X = 1)
同样地,采用「先更新缓存,再更新数据库」的方案,也会有类似问题,这里不再详述。
删除缓存可以保证一致性吗?
先删除缓存,后更新数据库 先更新数据库,后删除缓存
线程 A 要更新 X = 2(原值 X = 1) 线程 A 先删除缓存 线程 B 读缓存,发现不存在,从数据库中读取到旧值(X = 1) 线程 A 将新值写入数据库(X = 2) 线程 B 将旧值写入缓存(X = 1)
缓存中 X 不存在(数据库 X = 1) 线程 A 读取数据库,得到旧值(X = 1) 线程 B 更新数据库(X = 2) 线程 B 删除缓存 线程 A 将旧值写入缓存(X = 1)
缓存刚好已失效 读请求 + 写请求并发 更新数据库 + 删除缓存的时间(步骤 3-4),要比读数据库 + 写缓存时间短(步骤 2 和 5)
如何保证两步都执行成功?
立即重试很大概率「还会失败」 「重试次数」设置多少才合理? 重试会一直「占用」这个线程资源,无法服务其它客户端请求
消息队列保证可靠性:写到队列中的消息,成功消费之前不会丢失(重启项目也不担心) 消息队列保证消息成功投递:下游从队列拉取消息,成功消费后才会删除消息,否则还会继续投递消息给消费者(符合我们重试的场景)
写队列失败:操作缓存和写消息队列,「同时失败」的概率其实是很小的 维护成本:我们项目中一般都会用到消息队列,维护成本并没有新增很多
无需考虑写消息队列失败情况:只要写 MySQL 成功,Binlog 肯定会有 自动投递到下游队列:canal 自动把数据库变更日志「投递」给下游的消息队列
如果你有留意观察很多数据库的特性,就会发现其实很多数据库都逐渐开始提供「订阅变更日志」的功能了,相信不远的将来,我们就不用通过中间件来拉取日志,自己写程序就可以订阅变更日志了,这样可以进一步简化流程。
主从库延迟和延迟双删问题
线程 A 要更新 X = 2(原值 X = 1) 线程 A 先删除缓存 线程 B 读缓存,发现不存在,从数据库中读取到旧值(X = 1) 线程 A 将新值写入数据库(X = 2) 线程 B 将旧值写入缓存(X = 1)
线程 A 更新主库 X = 2(原值 X = 1) 线程 A 删除缓存 线程 B 查询缓存,没有命中,查询「从库」得到旧值(从库 X = 1) 从库「同步」完成(主从库 X = 2) 线程 B 将「旧值」写入缓存(X = 1)
问题1:延迟时间要大于「主从复制」的延迟时间 问题2:延迟时间要大于线程 B 读取数据库 + 写入缓存的时间
可以做到强一致吗?
总结
后记
推荐阅读
华为最美小姐姐被外派墨西哥后...
国内有程序员电视剧了,结果看了一分钟,就吐了...
男女洗澡前后区别,太形象了!
END
顶级程序员:topcoding
做最好的程序员社区:Java后端开发、Python、大数据、AI
一键三连「分享」、「点赞」和「在看」
评论