缓存一致性问题分析

181 阅读3分钟

缓存一致性问题就是指:当数据发生变更时,缓存中的数据和数据库中的数据一致性的问题。

主要有两种策略:超时剔除策略主动更新策略

其中超时剔除策略适用于低一致性需求,通过设置 Redis 的键值对过期时间实现。主动更新策略适用于高一致性需求。

超时剔除策略

适用于低一致性需求(很少发生变更的数据)。

通过设置 Redis 的过期时间实现。

主动更新策略

适用于高一致性需求(常发生变更的数据)。

通过程序员编码来实现。

更新缓存

如果是先更新数据库再更新缓存,这种情况下相比于删除缓存,会大大提高缓存的命中率

但如果在写多读少的情况下,可能还没读到缓存,缓存就又被更新了,缓存性能消耗比较大。

在读多写少的情况下,推荐使用删除缓存,因为写少,删缓存的次数也会减少。

先删除缓存,后更新数据库

若先删除缓存,再更新数据库,会出现读写并发问题,可以使用延时双删解决。

延时双删: 先删除缓存,在更新数据库后,线程睡眠一段时间,第二次再次删除缓存。

为什么要延时删除?

如果第二次删的过快,在第二次删除过后,新的线程查询旧数据后才把旧数据写入到缓存了,此时仍存在数据不一致的问题。

为什么要删除两次缓存?

当线程1第一次删除缓存,没来得及更新数据库时,线程2查询缓存没命中,就去查询数据库中的数据,并写入缓存,此时线程1将数据库的值更新,此时会出现缓存和数据库不一致的情况。

延时双删存在的问题:

  1. 延时双删需要把控第二次删除延迟的时间,不然可能会出现第二次删完了,另一个线程才开始查询缓存写入旧值。

  2. 在删除缓存后,此时再来别的请求查不到缓存,会去数据库中找,如果大量的请求同时进来,会出现缓存击穿的问题。

先更新数据库,后删除缓存

如上图,可能会存在脏数据的情况,但几率很小,因为update语句涉及 MVCC,加锁,所以很慢。而select不涉及加锁,速度很快。

例如 Spring Cache 就是使用的这种方式。

但这种情况下,如果删缓存失败,就会造成数据库和缓存不一致的问题。

如何选择

一般情况下,因为业务中对于缓存一般不要求强一致性,所以选择先更新数据库再删缓存,因为一个服务中接口的 RT 是不好确定的,每次响应时间也不一样,所以延迟双删等待第二次删除的时间不好确定

上述无论哪种方法,都不能保证强一致性,如果要保证强一致性,应该使用锁,但这会影响系统的性能。

解决删除缓存失败问题

删除缓存这一步操作是有可能失败的,可以通过消息队列 + Canal 订阅数据库变更日志 binlog解决,利用了消息队列的异步重试机制。

使用 Canal 中间件,用于监听数据库的 binlog,一旦数据库发生变更,binlog 日志就会发生变更,Canal 会自动投递消息到 mq,然后消费者收到消息后删除缓存,借助消息队列的重试机制来实现。