因为最近项目由于性能优化,引入了redis,但是随之而来的是大家关于缓存一致性问题的讨论。接下来主要是想和大家来聊一聊关于这个缓存一致性的带来的问题和一些处理方式。
关于缓存的读取,没猜错的话,大家应该都是按照如下的流程进行业务处理的。
注:什么是缓存一致性的问题,其实就是我们最终落库数据和我们缓存中的数据不一致。
但是我们对于缓存数据的更新方式上大家有各种处理方式,下面针对不同的处理,以及各自对应的缺陷进行一下总结。
1.设置过期时间
这种方案我们是为缓存的key值设置失效时间,这种情况下不管我们数据库数据怎么折腾,最晚会在缓存失效后,重新从数据库中获取最新的数据,加载到缓存中。
优点:这种方式注重在最终缓存内数据和数据库中数据是一致的。
缺点:内容更新不及时。
我们通常将这个中方式用作后续讲到方式的补充。
2.先更新数据库,再更新缓存
这种方案的问题在于,当有两个或者多个线程同时进行数据更新时,会发生数据不一致的情况。流程如下图:
由此可以看出,当线程1 先更新数据库,但是由于网路等原因晚于线程2更新缓存,最终导致的结果是缓存数据的不一致。
2.先删除缓存,在更新数据库
此方案问题在于,当有一个线程读取缓存时,缓存已经被另一个更新线程删除了,此时读取线程会去数据库查询数据,并且放到缓存中,此时更新线程还没有完成数据库的更新操作,依旧会出现缓存一致性的问题。流程如下:
那么这种情况我们可以解决吗?可以 我么你可以采用延迟双删的策略,在我们更新数据库之后,延时一段时间,然后将缓存再次删除掉,这样其他读取线程再次读取时候,还是会加载最新的数据。
3.先更新数据库,再删除缓存。
可以看到此时数据库和缓存中的数据是一致的,有些朋友会问,难道这种情况就可以完全杜绝一致性问题吗?答案不是的。如果我们的读取数据库数据在更新之前,并且更新缓存数据在删除缓存之后,也同样会出现一致性问题。流程如下:
如图也会出现缓存一致性问题,但是这种出现概率很小。原因如下:
通常我们读取操作的耗时会明显短于更新操作,所以即使先于更新读取了数据,但是由于更新线程最终删除了缓存,所以下次在读取时还会重新载入。流程如下
所以大部分公司会选择这种更新方式。来最大限度避免一致性问题。
4.使用分布式锁
有些朋友会说,我们使用分布式锁是不是可以彻底解决这个一致性问题呢。答案是的。当我们在更新和读取线程同时加锁,我们会按照顺序进行执行,这样我们确实可以杜绝,但是为什么好多公司不这么做呢?答案是效率问题,因为加了锁,在高并发的情况下,我们大量读取缓存的操作可能会因为锁而排队。