redis缓存与数据库双写不一致问题以及解决方案

1,048 阅读4分钟

参考:

缓存与库先写哪个,这十几张图告诉你

分布式之数据库和缓存双写一致性方案解析

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

  • 不可行。

  • 原因分析:

    • 频繁更新缓存浪费资源(写多读少的场景)

    • 缓存数据计算复杂,浪费性能(可能涉及多张表的计算)

    • 线程并发安全问题。(更新缓存的顺序不一致,导致脏数据)

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

旁路缓存策略(Cache Aside Pattern)

  • 使用方式

    • 基本读取: 先读缓存,缓存中没有数据的话,去数据库中读取,然后存入缓存中,同时返回响应。

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

  • 存在问题:

    • 数据库更新成功,缓存删除失败。导致数据库中的数据是最新的,但缓存中的是旧数据

    • 并发问题

      • 读请求去查询缓存时,缓存刚好失效。

      • 读请求去查询数据库,得到旧值。

      • 写请求将新值写入数据库,写请求删除缓存。

      • 读请求将旧值写入缓存。

    • 分析并发问题出现的概率

      • 概率非常低,因为条件需要具备读缓存失效,而且并发一个写操作。读操作必须在写操作前进入数据库操作,而又要晚于写操作更新缓存。但实际上写操作比读操作慢得多,所以概率非常小。
  • 改进:

    • 提供一个保障的重试机制即可。目前有两种方案。

    • 方案一:可以使用消息队列来保证缓存一致性。

      • 流程:

        • 更新数据库

        • 缓存删除失败

        • 将需要删除的key发送至消息队列

        • 自己消费消息,获得需要删除的key。

        • 继续重试删除操作,直到成功。

      • 但这种方式借助了消息中间件,增加了复杂度,对业务代码造成大量侵入。对于一些一致性要求没那么高的场景,就显得没必要了。

    • 方案二:启动一个订阅程序去订阅数据库的binlog,获得需要操作的数据。在应用程序中,另起一段程序,获得这个订阅程序传来的信息,进行删除缓存操作。

3.先删缓存,再更新数据库

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

  • 假设缓存删除成功,但数据库更新失败。此时新的请求过来,就去数据库中读取旧数据更新到缓存中。

  • 并发度很低时适用。

缓存延时双删策略

  • 先删除缓存,后更新数据库方案存在的问题分析:

  • 同时进入两个请求,一个写请求,一个读请求。

  • 写请求先删除redis中的数据,然后去数据库进行更新操作。

  • 读请求判断redis中有没有数据,没有数据时去请求数据库,拿到数据后写入缓存。(此时写请求还未更新DB成功,故读请求拿到的是旧数据)

  • 写请求更新DB成功后,出现缓存与数据库不一致问题。

  • 使用了MySQL的读写分离架构,造成双写不一致的原因:

    • 同时进入两个请求,一个写请求,一个读请求。

    • 写请求删除缓存,更新成功主库的数据。但还没同步到从库。

    • 读请求判断redis中有没有数据,没有数据时去请求从库,拿到数据后写入缓存。(此时拿到的是旧值)

    • 数据库完成主从同步,从库变为新值。

  • 解决办法:延时双删策略

    • 先删除缓存

    • 再写数据库

    • 异步等待一段时间后,再次淘汰缓存。(这里的时间设定主要是保证读请求结束,写请求可以删除读请求遭成的缓存脏数据,需要自行评估确定。)

  • 该方案解决了高并发情况下,同时有读请求与写请求时导致的不一致问题。读取速度快,但是可能会出现短时间的脏数据。

  • 如果第二次删除也删除失败,则需要添加重试机制保证一定删除成功。