缓存和数据库保持一致

203 阅读4分钟

如何使用缓存?

通常情况下,我们使用缓存的主要目的是为了提升查询的性能。大多数情况下,我们是这样使用缓存的:

  1. 用户请求过来之后,先查缓存有没有数据,如果有则直接返回。
  2. 如果缓存没数据,再继续查数据库。
  3. 如果数据库有数据,则将查询出来的数据,放入缓存中,然后返回该数据。
  4. 如果数据库也没数据,则直接返回空。

问题来了,当数据更新了,缓存怎么更新?

目前有以下4种方案:

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

缓存和数据库如何保持一致?

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

我们先来看看先更新缓存,再更新数据库有什么问题?

假设,缓存和数据库中有一个 age = 18 的值,现在有一个写请求要将 age修改成20,此时,如果写入缓存成功了,但是写入数据库库的时候,出现异常了,回滚了,那么缓存中的不就成了脏数据了。

缓存的主要目的是把数据库的数据临时保存在内存,便于后续的查询,提升查询速度。但如果某条数据,在数据库中都不存在,你缓存这个脏数据又有啥意义呢?

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

先更新数据库,再更新缓存也有问题。

比如,你更新数据库的时候成功了,但是更新缓存的时候失败了,那么数据还不是出现了不一致。

不仅如此,如果是那种写多读少的场景下,你就得频繁的更新缓存,那么对系统来说也是一种额外的开销。

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

我们来看看先删除缓存,再更新数据库有什么问题?

现在假设有一个写请求和一个读请求:

  1. 现在写请求过来了,将缓存给删除了,刚想写数据库的时候,网络卡了,此时还没来及的写入。
  2. 这时候,读请求来了,查询了缓存,数据不存在,去查询数据库,发现有了,然后写入到缓存中。
  3. 最后,写请求恢复过来了,更新了数据库
  4. 那么,缓存中的就是旧值,而数据库中的是新值。

那么怎么解决呢?其实感觉还是挺简单了,既然缓存的是旧值,那么,写请求再更新数据库后,再删一次缓存不就行了,这种方式,有个好听的名字,延迟双删

那么,为什么要延迟呢?动你的小脑瓜子想想,假设读请求查询数据库中的数据,正要写入缓存,此时怎么又这么巧呢?又卡了,此时,写请求更新了数据库,继续再删除一次缓存,此时读请求恢复过来,将旧值写入到缓存中。你说怎么能这么背呢?删缓存,删了个寂寞。

所以说,才想要延迟一会,避免这种情况的发生,但是,这里但是,延迟多少呢?其实不太好确定。

并且,如果最后一个删除缓存失败了呢?这里先留个悬念。

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

最后一个方案了,先更新数据库,然后再删除缓存,其实还是会有问题,比如:

从上面的理论上分析,先更新数据库,再删除缓存也是会出现数据不一致性的问题,但是在实际中,这个问题出现的概率并不高因为缓存的写入通常要远远快于数据库的写入。

但是,还是会出现缓存删除失败的情况。

缓存删除失败怎么办?

重试。

如果是因为网络的原因删除失败,可以短暂的重试,

如何重试?

  1. 记录删除失败的数据,通过定时任务去重试
  2. 使用消息队列,删除缓存失败了,产生一条消息,消费者读取消息进行重试。
  3. 订阅binlog日志 + 消息队列