前情提要

先做一个说明,从理论上来说,给缓存设置过期时间,是保证最终一致性的解决方案。这种方案下,我们可以对存入缓存的数据设置过期时间,所有的写操作以数据库为准,对缓存操作只是尽最大努力即可。也就是说如果数据库写成功,缓存更新失败,那么只要到达过期时间,那么后续的请求会从数据库拿取最新的数据。

在本文中主要讨论三种策略:

  1. 先更新数据库,再更新缓存
  2. 先删除缓存,再更新数据库
  3. 先删除缓存,再更新数据库,延迟1s,再删除缓存(延时双删)

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

对于这种方案,是大家都比较反对的,不采用理由如下:

线程安全问题

  1. 线程A更新了数据库
  2. 线程B更新了数据库
  3. 线程B更新了缓存
  4. 线程A更新了缓存

正常来讲A会比B先更新缓存,但由于网络波动等原因导致B先更新,这就导致了缓存为脏数据。如果没有设置缓存时间,那永远就是脏数据
业务场景角度

  1. 如果你是一个写数据库场景比较多,而读数据场景比较少的业务需求,采用这种方案就会导致,数据压根还没读到,缓存就被频繁的更新,浪费性能。
  2. 如果你写入数据库的值,并不是直接写入缓存的,而是要经过一系列复杂的计算再写入缓存。那么,每次写入数据库后,都再次计算写入缓存的值,无疑是浪费性能的。显然,删除缓存更为适合。

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

该方案会导致不一致原因是。同时一个请求A进行更新操作,另一个请求B进行查询操作。那么会出现如下情形:

  1. 请求A进行写操作,删除缓存
  2. 请求B查询发现没有缓存
  3. 请求B去数据库查询到旧数据
  4. 请求B将旧数据写入缓存
  5. 请求A将新值写入数据库

还是一样,如果没有设置缓存时间,那永远就是脏数据。

延迟双删除策略

操作步骤如下:

  1. 先删除缓存
  2. 修改更新数据库
  3. 休眠1s(具体时间由修改数据库业务的执行时间决定)
  4. 再次删除缓存(将修改数据库的时间内产生的脏数据删除)

在设置休眠时间时如果需要考虑到主库数据同步到从库的情况,那么再把延迟时间增长几百ms

当然,还有一种情况就是在第二次删除的时候删除失败怎么办,这样还会导致脏数据。

解决方案:设置重试机制,多删几次直到成功

  1. 更新数据库数据
  2. 由于各种原因导致删除缓存失败
  3. 将删除失败的key放入消息队列
  4. 自己消费消息,获取需要删除的key
  5. 重试删除操作,直到删除成功为止

redis缓存双写不一致问题 redis mysql双写_缓存