在我们生产的过程中,我们会发现我们80%的业务是由20%的数据来驱动的,这20%的数据往往被我们称为热数据,这种现象也被称为二八定律。这种数据访问不均匀的现象,使得我们可以采用最有效的技术——缓存来提升我们整个系统的性能,但是用到缓存我们不可避免地要考虑一个问题缓存和数据库数据一致性的问题。
分布式下的数据一致性问题 可参考我的博客:分布式下的数据一致性问题
缓存和数据库双写一致性问题
强一致性:缓存和数据库数据始终保持一致最终一致性:缓存和数据库数据有一段时间不一致,单不影响查询结果。解决缓存一致性的解决方案
延时双删策略通过消息队列来更新缓存通过binlog来同步mysql数据库到redis中延时双删策略一个写操作会进行以下流程:
先淘汰缓存再写数据库休眠1秒,再次淘汰缓存接着我们要明确为什么要采用先淘汰缓存,再写数据库的策略。
先写数据库再更新缓存的弊端
1.线程安全方向(为什么要先操作缓存,再操作数据库) 同时有请求A和请求B进行更新操作,那么会出现:
线程A更新了数据库;线程B更新了数据库;线程B更新了缓存;线程A更新了缓存;(A出现网络波动)这就出现请求A更新缓存应该比请求B更新缓存早才对,但是因为网络等原因,B却比A更早更新了缓存。这就导致了脏数据。 并且这种情况只能等缓存失效,才能够得到解决,这样的话很大程度会对业务产生比较大的影响。
2.业务方向(为什么选择淘汰缓存,而不是更新缓存)
缓存的意义就是为了提升读操作的性能,如果你写操作比较频繁,频繁更新缓存且没有读操作,会造成性能浪费,所以应该由读操作来触发生成缓存,故而在写操作的时候应采用淘汰缓存的策略。有的时候我们在存入缓存可能也会做一些其他转化操作,但是如果又立马被修改,也会造成性能的浪费。采用先淘汰缓存,再写数据库事实上不是完美的方案,但是是相对而言最合理的方法,它有下面的特殊情况:
写请求A进行写操作,删除缓存;读请求B查询发现缓存不存在;读请求B去数据库查询得到旧值;读请求B将旧值写入缓存;写请求A将新值写入数据库;(这里不采取行动,会造成数据库与缓存数据不一致)上述情况就会导致不一致的情形出现。
延时双删策略是为了解决采用先淘汰缓存,再写数据库可能造成数据不一致的问题,这个时候写请求A应采用休眠1秒,再次淘汰缓存的策略:
采用上述的做法,第一次写操作,会出现将近1秒(小于 1秒减去读请求操作时间)的数据不一致的问题,1秒后再次执行缓存淘汰,下次读操作后就会保证数据库与缓存数据的一致性了。
这里提到的1秒,是用来确保读请求结束(一般是几百ms),写请求可以删除读请求造成的缓存脏数据。
另外还存在一种极端情况是:如果第二次淘汰缓存失败,会导致数据和缓存一直不一致的问题,所以
缓存要设置失效时间设置重试机制或者采用消息队列的方式保证缓存被淘汰。采用消息队列中间件的方式能够保证数据库数据与缓存数据的最终一致性。
实现了异步更新缓存,降低了系统的耦合性但是破坏了数据变化的时序性成本相对比较高Mysql数据库任何时间对数据库的修改都会记录在binglog中;当数据发生增删改,创建数据库对象都会记录到binlog中,数据库的复制也是基于binlog进行同步数据
在mysql压力不大情况下,延迟较低;和业务完全解耦;解决了时序性问题;成本相对而言比较大。 溪源的奇思妙想 认证博客专家 Java Redis 架构 微信公众号:溪源的奇思妙想 溪源,一个在IT技术圈和经济学之间的求知者——既对人工智能、物联网等前沿技术兴致勃勃,又对机会成本、边际收益等经济学理论流连忘返。人生是一场孤独的旅行,只是我还是侥幸期待有同路人,我希望认识同样热爱技术、迷恋经济学的你。