Redis 缓存一致性

缓存一致性

首先我们先说说什么是缓存一致性问题,为什么要解决,以及方案是什么。

缓存一致性问题是指在使用缓存时,由于缓存数据与数据库数据的不一致性,可能会导致数据错误或者数据丢失等问题。这种问题在高并发场景下尤为常见,因为多个线程同时读写同一份数据时,很难保证数据的一致性。如何保证和Redis内存中的数据与DB数据库中的数据保持一致,并且不能出现数据不一致的情况(高并发场景中),如果出现数据不一致对于某些情况来说可能会出现大麻烦,缓存一致性的解决方案也很重要。

以下是我列出常见的几种方案,以及它们在高并发场景下各自的问题。

直接写入缓存 (不推荐)

在3,4步执行时高并发场景下无法保证写入Redis数据的Java线程会进行顺序执行(因为CPU时间分片问题,并且加上分布式、微服务情况更加明显)。才会导致最新数据可能被其他线程中的旧数据覆盖,如果发生了之后后续没有其他线程执行,可能缓存数据一直会保持旧数据情况。

这里可能有人会直接使用分布式锁(单应用加JVM锁即可),加锁需要控制整个DB写入到Redis写入的流程,并且每个Key操作的函数范围需要自己把控,且效率肯定下降得厉害,不过也可以自己控制一下锁的粒度,加分布式锁这种方案比较适合强一致性场景,为了一致性牺牲性能(强一致性场景推荐)。

写入数据库后删除缓存(单删策略-不推荐

单删策略看似可以很好的避免写入被覆盖问题,但其他查询数据的线程加入到逻辑中就会出现问题,查询线程在获取数据库数据时,可能其他线程正在提交数据库事务,而此时该查询线程就会获取到提交事务前的旧数据而存入缓存中造成数据不一致问题。

延时双删策略 (推荐)

通过加入延迟队列,等待一定时间后再次删除该Key的策略,解决掉了单删遗留的问题,也没有进行加锁操作,所以缓存双删策略,我个人觉得更适合允许部分数据短时间不一致的应用场景。

除开以上的方案,还有很多方案可以选择:

  1. 读写锁。在写操作时获取写锁,其他线程无法读写缓存,直到写操作完成。这种方案可以保证数据的一致性,但是可能会增加读操作的延迟和锁的竞争问题。
  2. 版本号控制。在缓存中存储数据版本号,每次更新数据时同时更新版本号。在读取数据时,比较版本号是否一致,如果不一致则重新加载数据。这种方案可以保证数据的一致性,但是需要增加数据的存储空间和读取的开销。
  3. 带超时的缓存。在缓存中存储数据时,设置缓存的过期时间,超过时间后缓存自动失效,需要重新加载数据。这种方案可以减少缓存数据与数据库数据不一致的可能性,但是可能会增加数据的存储空间和读取的开销。
  4. 数据库异步更新。在写入数据库时,不直接更新缓存,而是将更新操作异步发送到消息队列中,由消费者负责更新缓存。这种方案可以减少数据库的负载压力,但是可能会增加消息队列的复杂度和延迟。