缓存双写一致性


查询流程

缓存双写一致性-1


双写一致性概念

当修改了数据库的数据也要同时更新缓存的数据,缓存和数据库的数据要保持一致。

  • 如果Redis中有数据,需要和数据库中的值相同

  • 如果Redis中无数据,数据库中的值要是最新值,且回写Redis

缓存按照操作来分,可以分为两种:

  • 只读缓存

  • 读写缓存

    大部分都是读写缓存,其有两种回写同步策略:

    • 同步直写策略

      写数据库后也同步写Redis缓存,缓存和数据库中的数据一致;

      对于读写缓存来说,要想保证缓存和数据库中的数据一致,就要采用同步回写策略。

    • 异步缓写策略

      正常业务运行中,数据库变动了,但是可以在业务上容许出现一定时间后才作用于Redis;

      当出现异常情况,不得不将失败的动作重新修补,可能需要借助消息中间件实现重试重写。


双检加锁策略

当高并发查询时,如果Redis没有数据,大量请求会落入数据库中,同时大量的回写操作造成数据覆盖,会耗费不必要的资源。

解决方案:采用双检加锁策略

多个线程同时去查询数据库的这条数据,我们可以在第一个查询数据的请求上使用一个互斥锁。其他线程等第一个线程查询完回写缓存释放锁后,直接加载缓存数据。


更新策略

目的:保证最终一致性

原则:以MySQL的数据为准

下述共4种更新策略,四种策略无最优解,不保证适配所有情况,视实际情况选择。

如果是停机更新,下面方法都可以使用,如果是无感更新,不同策略可能存在一些问题


先更新数据库,再更新缓存

问题:

  • 如果更新数据库成功,但Redis更新失败了,两边数据不一致,读到Redis脏数据。

  • 多线程环境下,线程有快有慢,可能存在如下情况:

    线程A、B同时更新

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    # 正常情况
    A update mysql 100
    A update redis 100
    B update mysql 80
    B update redis 80
    # 异常情况
    A update mysql 100
    B update mysql 80
    B update redis 80
    A update redis 100

    导致数据库和Redis数据不一致


先更新缓存,再更新数据库

问题:

  • 这种方式并不推荐,因为业务上一般把MySQL作为底单数据库,保证最后解释。

  • 同第一种更新策略,多线程环境下,线程有快有慢,可能存在如下情况:

    线程A、B同时更新

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    # 正常情况
    A update redis 100
    A update mysql 100
    B update redis 80
    B update mysql 80
    # 异常情况
    A update redis 100
    B update redis 80
    B update mysql 80
    A update mysql 100

    导致数据库和Redis数据不一致


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

问题:

  • 缓存删除成功,但数据库更新失败,导致读取到旧值。

  • 线程A已经删除缓存,数据库更新还在执行中(可能网络延迟,并没有真正到MySQL的update操作),另一个线程B此时进行查询操作。

    此处就会有两个问题:

    • 线程B从MySQL中获得了旧值
    • 线程B将旧值回写给Redis

    导致数据库和Redis数据不一致

解决方法:延时双删策略。在更新数据库前后都删除一次缓存。更新数据库后的删除需要一段延迟时间,这段时间是为了让线程B能够先从数据库读取数据,再把缺失的数据写入缓存,所以这个时间需要大于线程B读取数据再写入缓存的时间。


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

较为推荐的做法。但也有一些问题:

  • 缓存删除失败或者来不及,会导致请求再次访问Redis时缓存命中,读取到的是缓存旧值。

强一致性解决方案

借助消息中间件

缓存双写一致性-2

  • 可以把要删除的缓存值或者是要更新的数据库值暂存到消息队列
  • 当程序没有成功删除缓存或者更新数据库时,可以从消息队列中重新读取这些值,再次尝试
  • 如果成功删除或更新,这些值就要从消息队列中去除,避免重复操作。此时,就可以保证数据库和缓存的数据一致性。否则还需要继续重试
  • 如果超过一定次数还未成功,需要业务层发送错误信息,通知运维人员。

Canal

cancal主要用途是用于MySQL数据库增量日志数据的订阅、消费和解析,是阿里巴巴开发并开源的,采用Java语言开发。

https://github.com/alibaba/canal