PS:本文已收录到1.1K Star数开源学习指南——《大厂面试指北》,若是想要了解更多大厂面试相关的内容及获取《大厂面试指北》离线PDF版,请扫描下方二维码码关注公众号“大厂面试”,谢谢你们了!项目地址:github.com/NotFound9/i…html
《大厂面试指北》项目截图:git
获取《大厂面试指北》离线PDF版,请扫描下方二维码关注公众号“大厂面试”github
咱们平常开发中,对于缓存用的最多的场景就像下图同样,可能仅仅是对数据进行缓存,减轻数据库压力,缩短接口响应时间。 面试
高并发读写时,请求执行各步骤的顺序是不可控的。假设此时有一个请求A,B都在在执行写流程,请求A是须要将某个数据改为1,请求B是须要将某个数据改成2,执行操做以下时就会致使数据不一致的问题:数据库
1.请求A执行操做1.1删除缓存。缓存
2.请求A执行操做1.2更新数据库,将值改成1。安全
3.请求B执行操做1.1删除缓存。服务器
4.请求B执行操做1.2更新数据库,将值改成2网络
5.假设说请求B所在服务器网络延迟比较低,请求B先更新缓存,此时缓存中的key对应的value是2。并发
6.请求A更新缓存,将缓存中B更新的数据进行覆盖,将key对应的值改成1。
此时数据库中是B修改后的数据,值为2,而缓存中的数据是1,这样在缓存过时钱,用户读到的都是脏数据,与数据库不一致。
高并发读写时,请求执行各步骤的顺序是不可控的。假设此时有一个请求A在执行写流程,将原值由1改为2,请求B执行读流程,执行操做以下时就会致使数据不一致的问题:
1.写请求A执行1.1操做删除缓存key,value是原值1。
2.读请求B执行2.1操做发现缓存中没有数据,就去执行2.2操做读数据库,读到旧数据,值为1。
3.写请求A执行1.2操做更新数据库,将数据由1改成2。
4.写请求A执行1.3操做更新缓存,此时缓存中的数据key对应的value是2。
5.读请求B执行2.3操做更新缓存,将以前读到的旧数据1设置到缓存中,此时缓存中的数据key对应的value是1。
因此若是说读请求B所在服务器网络延迟比较高,去执行2.3操做比写请求A晚,就会致使写请求A更新完缓存后,读请求B使用以前读到的旧数据去更新缓存,此时缓存中数据就与数据库中的不一致。
保证数据一致性,网上有不少种方案,例如:
1.先删除缓存,再更新数据库。
2.先更新数据库,再删除缓存。
3.先删除缓存,再更新数据库,而后异步延迟一段时间再去删一次缓存。
可是这些方案都是存在各类各样的问题,这里篇幅有限,只给出目前相对正确的三套方案,目前的这些方案也有本身的局限性。
1.写请求更新以前先获取分布式锁,得到以后才能去数据库更新这个数据,获取不到就进行等待,超时后就返回更新失败。
2.更新完以后去刷新缓存,若是刷新失败,放到内存队列中进行重试(重试时取数据库最新数据更新缓存)。
读请求发现缓存中没有数据时,直接去读取数据库,读完更新缓存。
这种技术方案经过对写请求的实现串行化来保证数据一致性,可是会致使吞吐量变低。比较适合银行相关的业务,由于对于银行项目来讲,保证数据一致性比可用性更加剧要,就像是去存款机存钱,取钱时,为了保证帐户安全,都是会让用户执行操做后,等待一段时间才能得到反馈,这段时间其实取款机是不可用的。
1.先更新数据库
2.异步删除缓存(若是数据库是读写分离的,那么删除缓存时须要延迟删除,不然可能会在删除缓存时,从库尚未收到更新后的数据,其余读请求就去从库读到旧数据而后设置到缓存中。)
3.删除缓存失败时,将删除的key放到内存队列或者是消息队列中进行异步重试
在更新完数据库后,咱们为何不直接更新,而是采用删除缓存呢?
这是由于直接更新缓存的话,在高并发场景下,有多个更新请求时,难以保证后更新数据库的请求会后更新缓存,也就是上面的高并发写问题。若是采用删除缓存,可让下次读时读取数据库,更新缓存,保证一致性。
2.cannal项目会读取数据库的binlog,而后解析后发消息到kafka。
3.而后缓存更新项目订阅topic,从kafka接收到更新数据库操做的消息后,更新缓存,更新缓存失败时,新建异步线程去重试或者将操做发到消息队列,后续再进行处理。
可是这种方案在更新数据库后,缓存中仍是旧值,必须等缓存更新项目消费消息后,更新缓存,缓存中才是最新值。因此更新操做完成与更新生效之间会有必定的延迟。
你们有了解其余的技术方案,欢迎进群一块儿讨论!
参考连接: