当前位置: 首页 > 科技观察

双文绕了一圈又回到了连载之路

时间:2023-03-14 00:04:28 科技观察

本文转载自微信公众号“月聊科技”,作者月聊科技。转载本文请联系月聊科技公众号。什么是双写?让我们开门见山。这很容易理解。双写是指数据库中保存一份数据,缓存中保存一份数据。给缓存一个过期时间,缓存无法读取时从数据库中读取。出并写入缓存。为什么需要双写?当请求量越来越大的时候,系统就会慢慢出现瓶颈,因为数据库连接有限,无法支撑高QPS,所以我们得想办法分担数据库的压力,所以有了双写,数据写入缓存,客户端直接从缓存中读取数据,可以提高系统的性能。但是如果要使用双写,无论是先更新缓存还是先更新mysql,总会有一个时间间隔,那么你必须保证你的业务在一定程度上允许短期的数据不一致,否则是不行的推荐使用是的。然后有人问?双写不能保证强一致性吗?答案是肯定的,只要将所有与之相关的读写请求都用一个队列序列化,就可以保证双写的强一致性,但是这样会大大降低系统的QPS,不推荐。既然要双写,那么肯定会出现数据库和缓存数据不一致的情况。如何避免?如何解决双写不一致的问题1、先更新数据库再更新缓存有什么问题?看下图:先是a先更新数据库,按照正常流程进行,然后a线程删除了缓存,但是突然b线程后面来了,由于各种业务原因导致a线程卡住了,导致b线程先完成,然后a线程更新缓存。这时候突然有其他线程进来读取数据,本来会读取a的数据,但是按照业务流程应该是读取b的数据。这时候就出现了数据混乱的问题。1.线程a更新数据库2.线程b更新数据库3.线程b更新缓存4.线程a更新缓存5.其他线程读取数据(读错)此时我们会发现直接更新缓存是一个很大的问题是的,而且很多时候,在复杂的缓存场景中,缓存不仅仅是直接从数据库中取一个值,它可能是结合很多其他数据计算出来的一个值。而且可能会有这样的场景,我们经常更新完数据库就直接更新缓存,但是中间没有访问缓存的必要,这样就做了很多无用功,付出了很多代价。大家应该对单例模式有所了解。有一种懒加载的思想,就是需要的时候加载。很适合双写,先有下面updatedatabase。,然后删除缓存的架构。2、先更新数据库再删除缓存有什么问题?当然,这仍然是一个有问题的解决方案。让我们按照图表。1:线程a更新数据库2:程序挂了,没来就删除缓存3.其他线程读取数据(都是错误的)。实际业务的话,应该读取线程a的值,但是你一直在读取之前的值。这个解决方案优化了吗?当然有。其实我们可以在每次写的时候记录日志,等修改完成后再记录日志。使用日志状态判断是否写入成功。如果写入不成功,后续没有新的写入请求,则重写,否则不处理。但是这种情况也会出现不一致的情况,就是如果写数据库的程序中断了,直到下次恢复数据时,仍然会出现数据不一致的情况。而如果频繁写入,很有可能日志机制不起作用,会写入新的数据并覆盖,日志系统会占用额外的资源。我明白!你应该先删除缓存,然后再更新数据库,就是这样!3.先删除缓存,再更新数据库。来来,继续贴图,你眼熟吗?这个解决方案会不会有问题?当然继续磁盘:1:线程a删除缓存2:线程b删除缓存3:线程a卡死4:线程b更新数据库5:线程a更新数据6:其他线程读取数据,读取a(错误再次)完了,这种情况其实是有问题的。线程a不管行不行,每次出事的都是你。这样的话,中间会有一段数据乱了,但是下次更新数据还是正确的。最终的解决办法是先删除缓存,然后更新数据库,再更新缓存??4.先删除缓存,再更新数据库,再删除缓存继续映射1.线程a删除缓存2.其他线程读取数据,读取的是a之前的数据3.线程a更新数据库4、线程a删除缓存5、其他线程设置缓存数据,就是a之前的数据(此时应该是a)。是不是又发现了,这个设计还是可以的,如果出现问题,要等到下一次数据更新时,才能正确恢复数据。来来来,大家经常讨论的最后一个延迟双删的解决方法,一起来玩个游戏吧。5.延迟双删继续1.先删除缓存2.再写入数据库3.休眠一段时间(视具体业务时间而定)4.再次删除缓存这里加了一个延迟操作,保证修改之前database->clearcache,已经执行了其他事务的changecache操作。所有写操作都以数据库为准。只要到了缓存过期时间,后续的读请求自然会从数据库中读取新的值,然后回填缓存。但是难免会查询到大量旧的缓存数据,因为延迟时间是根据业务本身定义的。时间过长或过短,在高并发的情况下都会查询到脏数据。最坏的情况是超时时间内数据不一致。结论到这里,你应该发现,除了序列化方式之外,其他任何方式都会出现或大或小的数据不一致,有时还需要大量额外繁重的操作来维护数据的一致性。比如增加一些日志来处理状态下的双写问题。具体的方案选择还是要看业务的敏感度。