如何保证缓存一致性?
- 缓存的意义
- 常见解决方案
- 如何应对删除失败
缓存的意义
缓存的意义
我们看一下上面这个图,从开始到缓存中先查询是否有数据,如果有的话,那就是yes,我们就一定会返回这个数据,如果否的话,那就是no,我们会从数据库中加载数据,并将数据写入到缓存中再进行返回,这样整体的一个过程就表示我们从数据库数据到缓存数据中的一个扭转,这样可以避免我们每次都查询数据库,提高了我们的性能,因为redis的内存处理会比磁盘快,但是这样会有一个问题。
如果我们缓存中的数据和数据库的数据发生了不一致,那会造成什么呢?
造成我们整体的业务就会产生两面性:
一方面来说,假如我们想查询毛巾库存数据,从一个地方查询到的数据是毛巾有2条,另一个地方中查询到的毛巾数量有3条,那么就会对我们造成很大影响,所以说我们在使用缓存的时候一定要保持跟我们数据库是一致的。
常见解决方案
错误方案参考
1. 更新数据库后,更新缓存
如果数据库更新成功了,缓存失败,那么我们在缓存失效之前,读取到的一直都是老数据,而数据库里面是新数据。
2. 更新缓存后,更新数据库
缓存更新成功后,而数据库失败,导致我们读取到的值一直都是错误的缓存值。
*[注意]:这两种方案都不可取,因为它们都无法保证原子性,所以我们千万不要选择更新!!!!!!
正确操作
1.先删除缓存,再更新数据库
实际中有一定的使用量,即使更新数据库失败也没有问题,数据都是老的。不过在高并发下会有问题,比如A线程删除缓存后,正在更新数据库,这个时候B线程查询了缓存,发现没有缓存,于是就从数据库读取后,放入进来。导致缓存中存在的一直都是老数据。
针对以上这种问题,我们可以选择双删,先删除缓存,再更新数据库,再删除缓存。
2.先更新数据库,再删除缓存
这种方式是比较流行的,支持高并发。如果更新数据库成功,删除缓存失败,那么缓存失效前读取到的也是老数据,这种概率极小,但也可能存在的问题:A线程查询数据库,得到一个旧的值,B先将新值写入数据库,B线程删除缓存,A线程将查到的旧值写入缓存,这种情况下会存在脏数据。
一般来说,造成缓存跟数据库长度不一致都是由于一个多线程之间的来回切换所造成的。
以上解决方案,我们选择的肯定是先删除缓存,再更新数据库或者先更新数据库再删除缓存,核心都是要求先删除缓存,这种情况我们要保证一个问题,就是我们删除缓存一定是成功的,如果删除缓存失败了,那么对于我们业务的影响就说我们的数据还是达不到一致性,所以我们要看下如何保证我们的删除缓存可以成功?
如何应对缓存删除失败?
第一种方案:消息队列补偿
如果我们发现删除某个消息失败了,那么我们可以直接将删除失败的消息打入队列,然后我们的服务端监听队列,再次执行一个重试删除。这种方案我们要引入一条消息队列中间件,那么说明对我们整体架构来说,要引入一个更复杂的东西。
第二种方案:用canal中间件监听binlog
当mysql的数据发生变化后,canal会收到一条消息,这个时候我们收到消息后就进行删除,好处就说可以代码解耦,减少业务复杂度,并且canal这个中间件保证了我们的高可用性。