实际项目中有可能会使用Redis缓存数据,那么在更新数据的时候如何保证数据库中的数据和Redis缓存的数据一致,缓存同步策略的选择是一个很重要的问题。网上有各种说法,大概总结有以下几种,看看每种方案是否可行以及存在的问题和适用场景。
1、先更新Redis,再更新数据库 (不可行 )
数据库是比较复杂的,而且还会涉及事务,因为超时等原因更新操作失败的可能性较大,这种方案很可能因为数据库更新失败,导致缓存和数据库的数据不一致。
如上图所示,如果第2步更新数据库失败了,那么缓存的数据被更新为20,和数据库值10不一致了。
这种情况可能会想到补救措施:数据库更新失败了再将Redis数据做逆向操作进行回退,但是如果Redis数据回退操作也失败了呢?甚至还要继续针对这种失败做重试?显然事情越做越复杂了,这种方式不可行。
2、先更新数据库,再更新Redis (部分场景可用,不推荐 )
上面的方案不可取,这种先更新数据库的是否就可行呢?假设有两个请求更新数据,时序图如下:
如果是并发量不高,对一致性要求没有特别高时,如上图更新完数据库再更新Redis没有问题。
如果是并发量较高,如图所示这种场景下虽然请求1和请求2先后完成数据库更新,但更新缓存时却是请求2和请求1的顺序,那就很可能会把旧数据更新到缓存中导致数据不一致。
3、先删除Redis,再更新数据库,访问的时候再加载数据到缓存 (不可行 )
这里同样以两个请求的场景为例,时序图如下:
从图上可以看到,当并发场景下如果请求1更新耗时较长,还未来得及更新数据库中的值,请求2已经先读取了旧值并加载到缓存中了。这种也会导致两边数据不一致,而且并发量很高的时候这种概率也会更大。
针对这个方案存在的问题可以在请求1更新完数据库后再对Redis做一次删除操作。也就是缓存双删
4、先删除Redis,再更新数据库,再删除Redis,访问的时候再加载数据到缓存(缓存双删)
这种方案一定程度上解决了方案4数据不一致的问题,但是也有一个关键点,如上图所示必须保证第6步删除缓存操作在第5步回写入缓存操作之后执行,否则还是会有问题。那么这又引出了另外两个问题:
- 问题一:如何保证第二次删除缓存一定在回写后面执行呢?
关于双删的这个问题网上有方案是让请求1删除缓存时等待xxx毫秒,这个方案似乎可行,但是这个时间不好控制还是会存在一定风险。
另外也有博主给出的建议方案是将删除请求加入消息队列,异步串行化处理删除
- 问题二:如果双删失败了怎么办?
同样的网上也有方案:给redis加一个缓存过期时间、删除加入消息队列利用消息队列的重试机制、自己记录删除失败进行重试等
5、先更新数据库,再删除Redis,访问的时候再加载数据到缓存
这种方案除了请求2第一次查询这一次不一致,还有另一个极端场景会存在数据不一致。请求1更新数据节点缓存刚好失效了,另一个请求2刚好这时读取缓存没有进而读了数据库旧值,如下图所示:
这个极端场景需要满足缓存更好失效,而且请求2读取数据库及回写缓存耗时较请求1更新数据库更长,发生的概率非常小,可以忽略,所以相比较而言,方案5是最推荐的处理策略。
通过以上几种处理方案的分析,可以看到不管哪种方案都存在一定的问题,在满足实时性的条件下,尽量保证不一致出现的概率更低,或者不一致持续的时间非常短暂,避免长期不一致。非要满足强一致性可能就需要考虑使用锁的方案了。
总的来说,针对缓存同步更推荐的方式是,缓存中的数据不由数据更新操作主动触发,统一在需要使用的时候按需加载,数据更新后及时删除缓存中的数据。