☆* o(≧▽≦)o *☆嗨~我是小奥
个人博客:小奥的博客
CSDN:个人CSDN
Github:传送门
面经分享(牛客主页):传送门
文章作者技术和水平有限,如果文中出现错误,希望大家多多指正!
如果觉得内容还不错,欢迎点赞收藏关注哟! ❤️
推荐好文 美团二面:Redis与MySQL双写一致性如何保证? - 掘金 (juejin.cn)
旁路缓存模式中服务器需要同时维护数据库和缓存,并且以数据库结果为准。
旁路缓存模式,流程如下:
(1)读模式:读的时候,先读取缓存,缓存命中,直接返回数据;缓存未命中,查询数据库,写入缓存,返回数据;
(2)写模式:先更新数据库,再删除缓存。
适用场景
旁路缓存适合读多写少的场景,不适合写多的场景。因为当写入比较频繁时,缓存中的数据会频繁的清理,这样多缓存命中率会有一些影响。如果业务中对缓存命中率有要求,可以适用以下两种解决方案:
存在的问题
(1) 初次请求数据不一定在缓存中。
解决方案:将热点数据提前预热存放入缓存中。
(2) 如果写操作频繁,那么缓存中的数据更新比较频繁,导致缓存命中率较低。
解决方案:
读写缓存模式其实是将缓存作为主要的存储,应用的所有读写请求都是直接与缓存服务打交道的。并且数据库的数据由缓存服务来维护和更新。
(1)读模式:从缓存读取数据,缓存命中,直接返回数据;缓存未命中,查询数据库,写入缓存,返回数据;
Read Through Pattern实际上是对Cache Aside的封装,让程序代码变的更加简洁,同时也减少数据源上的负载。
(2)写模式:当发生写请求时,先查询写入的数据是否在缓存中存在,如果存在,则更新缓存中的数据,并且由缓存组件更新到数据库中,然后返回结果。如果不存在,直接更新数据库,然会返回。
异步缓存写入模式就是读写穿透的变形,不同之处在于缓存写入到数据库的时候是异步的。
Write behind只是更新缓存,不直接更新数据库,通过批量异步的方式来更新数据库。这种方式下,缓存和数据库的一致性不强,对一致性要求高的系统要谨慎使用。
适用场景:
异步缓存写入模式适合写多的场景,因为发生写操作的时候,只需要更新缓存,就立马返回了。但带来的问题是,数据不是强一致性的,而且会有数据丢失的风险。
要想保证缓存与数据库的双写一致,一共有以下四种策略:
(1)更新缓存还是删除缓存的问题
更新:① 优点:每次数据变化都能及时更新缓存,查询时不容易出现未命中的情况 ②缺点:更新的操作对性能的消耗比较大,频繁的更新缓存会影响服务器的性能,如果是写入频繁的场景,那么频繁的更新,却不访问,这就造成了性能的浪费。
删除:① 优点:操作简单,直接删除缓存即可。② 缺点:删除缓存后,下一次查询会出现未命中的情况,这时就需要去访问数据库。
整体上看,删除缓存的操作还是最优的选择。
(2)先操作数据库还是先操作缓存
先删除缓存再更新数据库:如果先删除缓存,由于写入数据库的操作相对于删除缓存来说是比较慢的,所以一个新的线程来读取的时候,就会发现缓存为空,于是去数据库查询旧数据并写入缓存,此时缓存中为脏数据。当数据库数据更新完毕,Redis与MySQL中的数据就会出现不一致现象。
先更新数据库再删除缓存:如果先操作数据库,再删除缓存,如果删除缓存的线程没有成功删除,那么新的线程读取的时候就会直接读取缓存中的旧数据,也会导致数据不一致现象。需要注意的是,当数据库中有事务回滚,最好也要将缓存删除。
延时双删:在操作数据库的前后都对缓存进行一次删除,并设定合理的超时时间。
具体步骤如下:先删除缓存,然后进行数据库数据的写入,让删除线程休眠一会,再次删除缓存。
休眠时间的确定:需要评估自己读数据的业务逻辑耗时,这样做是为了确保读请求结束,写请求可以删除读请求造成的脏数据;写请求的休眠时间一般在读数据业务上的耗时,再加上几百ms即可。
注意:双删的第一次删除的是还没有更新的旧数据,第二次删除的是因为读并发而导致缓存重新写入的脏数据。
如果删除失败,可以将删除失败的缓存放入消息队列中,利用消息队列的异步通知继续尝试删除缓存,如果删除仍失败,
可以增加重试的次数,但是这个次数要有一定的限制,如果超过了这个次数,要采取报错、记录日志等提醒措施。
数据库的更新操作会记录到binlog中,所以我们可以采集binlog日志的变化,利用binlog进行增量订阅消费,将消息通过消息队列消费更新Redis中的缓存。