1. 方案

在并发下的四种方案:

1.1 先更新数据库,后更新缓存

如果数据库更新成功,缓存失败,那么我们在缓存失效之前,读取到的一直都是老数据,而数据库里面的是新数据。(无法保证原子性,不推荐)

1.2 先更新缓存,后更新数据库

如果更新缓存成功,而数据库失败,导致我们读取到的值一直都是错误的缓存值。(无法保证原子性,不推荐)

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

实际中有一定的使用量,即时更新数据库失败也没问题,数据都是老的。不过在高并发下会有问题,例如A线程删除缓存后,正在更新数据库,此时B线程查询缓存,发现没有数据,就从数据库读取后,放入缓存,导致缓存中存在的一直是老数据。
针对上面的场景,可以使用延迟双删策略,先删除缓存,更新数据库,休眠一段时间,再删除缓存。但是休眠的时间不好把控。

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

这种方案比较流行,支持高并发。如果更新数据库成功,删除缓存失败,那么缓存失效前读取到的也是老数据。这种概率极小。
可能存在的问题:A线程查询数据库,得到一个旧值,B先将新值写入数据库,B线程删除缓存,A线程将查到的旧值写入缓存。这种情况下会存在脏数据。但是仔细想想,A查到旧值后写入缓存通常会比B更新数据库要快,因此该问题出现的概率也是极小。

2. 如何应对删除失败

要保证一致性,我们通常会使用方案3或方案4,它们的核心就是如何保证删除缓存成功。

  • 消息队列补偿:删除失败的消息放入队列,监听队列,消费后重新执行删除
  • 用canal监听binlog:当mysql的数据发生变化后,canal会受到一条消息,这个时候进行删除缓存,好处是可以代码解耦,减少业务复杂度,并且中间件保证了我们的高可用性。