如何使用缓存?
通常情况下,我们使用缓存的主要目的是为了提升查询的性能。大多数情况下,我们是这样使用缓存的:
- 用户请求过来之后,先查缓存有没有数据,如果有则直接返回。
- 如果缓存没数据,再继续查数据库。
- 如果数据库有数据,则将查询出来的数据,放入缓存中,然后返回该数据。
- 如果数据库也没数据,则直接返回空。
问题来了,当数据更新了,缓存怎么更新?
目前有以下4种方案:
- 先更新缓存,再更新数据库
- 先更新数据库,再更新缓存
- 先删除缓存,再更新数据库
- 先更新数据库,再删除缓存
缓存和数据库如何保持一致?
1. 先更新缓存,再更新数据库
我们先来看看先更新缓存,再更新数据库有什么问题?
假设,缓存和数据库中有一个 age = 18 的值,现在有一个写请求要将 age修改成20,此时,如果写入缓存成功了,但是写入数据库库的时候,出现异常了,回滚了,那么缓存中的不就成了脏数据了。
缓存的主要目的是把数据库的数据临时保存在内存,便于后续的查询,提升查询速度。但如果某条数据,在数据库中都不存在,你缓存这个脏数据又有啥意义呢?
2. 先更新数据库,再更新缓存
先更新数据库,再更新缓存也有问题。
比如,你更新数据库的时候成功了,但是更新缓存的时候失败了,那么数据还不是出现了不一致。
不仅如此,如果是那种写多读少的场景下,你就得频繁的更新缓存,那么对系统来说也是一种额外的开销。
3. 先删除缓存,再更新数据库
我们来看看先删除缓存,再更新数据库有什么问题?
现在假设有一个写请求和一个读请求:
- 现在写请求过来了,将缓存给删除了,刚想写数据库的时候,网络卡了,此时还没来及的写入。
- 这时候,读请求来了,查询了缓存,数据不存在,去查询数据库,发现有了,然后写入到缓存中。
- 最后,写请求恢复过来了,更新了数据库
- 那么,缓存中的就是旧值,而数据库中的是新值。
那么怎么解决呢?其实感觉还是挺简单了,既然缓存的是旧值,那么,写请求再更新数据库后,再删一次缓存不就行了,这种方式,有个好听的名字,延迟双删。
那么,为什么要延迟呢?动你的小脑瓜子想想,假设读请求查询数据库中的数据,正要写入缓存,此时怎么又这么巧呢?又卡了,此时,写请求更新了数据库,继续再删除一次缓存,此时读请求恢复过来,将旧值写入到缓存中。你说怎么能这么背呢?删缓存,删了个寂寞。
所以说,才想要延迟一会,避免这种情况的发生,但是,这里但是,延迟多少呢?其实不太好确定。
并且,如果最后一个删除缓存失败了呢?这里先留个悬念。
4. 更新数据库,再删除缓存
最后一个方案了,先更新数据库,然后再删除缓存,其实还是会有问题,比如:
从上面的理论上分析,先更新数据库,再删除缓存也是会出现数据不一致性的问题,但是在实际中,这个问题出现的概率并不高。因为缓存的写入通常要远远快于数据库的写入。
但是,还是会出现缓存删除失败的情况。
缓存删除失败怎么办?
重试。
如果是因为网络的原因删除失败,可以短暂的重试,
如何重试?
- 记录删除失败的数据,通过定时任务去重试
- 使用消息队列,删除缓存失败了,产生一条消息,消费者读取消息进行重试。
- 订阅binlog日志 + 消息队列