缓存不一致的情况有两种:
- Redis缓存中是旧值;
- 数据库中值是旧值;
缓存一致性需要保证的是,当缓存中有值的时候,数据库的值必须与缓存一致。
根据是否接收写请求,可以将缓存分为读写缓存和只读缓存。两种发生缓存不一致的情况不同,需要分开来应对。
在只读缓存中,新增数据会直接写到数据库中,不会操作缓存,所以不会出现缓存不一致。删改数据时,需要删除数据库和缓存中的数据,在删改数据库和缓存时,无论哪个先后,中间出现故障都会产生旧值,即缓存不一致的情况。
如何解决数据不一致
重试机制
可以将要删除的缓存值或者要更新的数据库值暂存在消息队列中。当应用没有能够成功删除缓存或者更新数据库值时,可以从消息队列中重新读取这些值,然后再次进行删除或更新。
如果能够成功地删除或更新,就需要将这些值从消息队列中去除,以免重复操作。如果重试超过一定次数,还是没有成功,就需要向业务层发送报错信息了。
但重试机制在并发量大时,依旧会有问题。要分先删缓存还是先更新数据库两种情况说说。
情况一:先删缓存,后更新数据库。
在并发的情况下,假设线程A先删除缓存,后更新数据库;那么在这期间线程b读取数据,发现缓存缺失,就会去数据库读取数据,那么就会产生两个问题:
- 线程b读取到的旧数据;
- 线程b读到旧数据后发现缓存缺失,那么就会去更新缓存,将缓存更新为了旧值,这样后面读取的线程访问的就是旧值,产生了数据不一致;
这样的情况可以采用延时双删的策略,即在线程A更新完数据库后,sleep一段时间,再删除缓存。这样做的目的是为了当线程b发现缓存缺失后将缓存更新了旧值,然后将这个旧值缓存给删除掉。
个人觉得这个策略并不是很好的做法。首先延时的时间不好确定,无法得知下一个读线程会更新缓存的时间。其二在并发量大的情况下,当线程a还没做延时双删时,会有大量的线程读取到缓存中的旧值。所以觉得这种方案只是会降低缓存不一致的概率,并非是"银弹。
情况二:先更新数据库值,再删除缓存值。
在并发的情况下,假设线程a先更新数据库,再删除缓存值。那么在更新数据库后删除缓存值前,会也线程读取到缓存中的旧值。不过由于删除缓存操作是内存操作,所以可以认为这一步是很快的,影响量不会很大。
不过对于正常业务来说,这种短时间内的数据不一致并不会影响很大。如果对于数据一致性要强要求的,要么不使用缓存,要么是利用分布式锁将请求变为串行。具体的做法得分场景选择了。