缓存与数据库双写不一致问题分析
在高并发、高可用架构中缓存是一个很重要环节。缓存主要解决了关系型数据库并发量低的问题,有助于缓解关系型数据库在高并发场景下的压力,提高系统的吞吐量。
既然有了缓存与数据库,那么就会存在是先更新缓存还是先更新数据库的问题?
一:四种更新方案:
方案1. 先更新缓存,后更新数据库
如果数据库更新失败,那么会导致缓存中是新数据,数据库中是旧数据,出现数据不一致的情况。假如缓存服务器宕机,从数据库恢复数据后,缓存中是旧数据,会把新数据相关的操作都丢失了,所以这种方案我们一般不采用。
方案2. 先更新数据库,后更新缓存
如果缓存更新失败,那么会导致缓存中是旧数据,数据库中是新数据,出现数据不一致的情况。
方案3. 先更新数据库,后删除缓存
如果缓存删除失败,那么会导致缓存中是旧数据,数据库中是新数据,出现数据不一致的情况。这种方案看起来和方案2 没什么区别,当缓存数据计算复杂的时候,更新缓存也是很耗费服务器资源的,我们可以采取删除缓存而不是更新缓存。通过下一次的读操作去更新缓存。
方案4. 先删除缓存,后更新数据库
如果数据库更新失败,那么下一次的读操作会更新缓存,会导致缓存中是旧数据,数据库中也是旧数据,此时缓存与数据库是一致的。
二:具体业务场景分析
2.1 并发量低的业务场景:
- 写多读少的场景:我们可以采取 方案3 或者 方案4。删除缓存后通过下一次的读操作去更新缓存,因为我们读操作很少,所以发生缓存穿透的次数很少。
- 写少读多的场景:可以采取方案2。如果采取方案3 或者 方案4。因为是删除缓存,所以会发生大量缓存穿透,导致对数据库服务器造成压力。
2.2 并发量高的业务场景:
2.2.1 写多读少的场景:
采取方案3
一个写请求和读请求同时到服务器,写请求更新数据库的同时,读请求读到了旧值,写请求数据库写成功,然后删除缓存,读请求更新缓存,因为读到的是旧值,会导致缓存中是旧数据,数据库中是新数据,出现数据不一致的情况。
对于方案4
也是类似的情况,一个写请求和读请求同时到服务器,写请求删除缓存然后更新数据库的同时,读请求读到了旧值,读请求更新缓存,因为读到的是旧值,会导致缓存中是旧数据,数据库中是新数据,出现数据不一致的情况。
解决办法就是:延时双删策略
方案3 :先更新数据库,再删除缓存,异步等待一段时间后,再次删除缓存。
方案4:先删除缓存,后更新数据库,异步等待一段时间后,再次删除缓存。
2.2.2 写少读多的场景:
采取方案2
一个写请求和读请求同时到服务器,写请求更新数据库成功后再更新缓存,读请求读到的一直是新值。此时缓存与数据库是一致的。
2.3 并发量高的强一致业务场景:
上面的两种业务场景都存在短暂的缓存与数据库不一致的问题。如果我们的业务就是需要缓存与数据库必须一致。那么我们该怎么解决了?
2.3.1 串行队列:
不一致的情况是因为同时有大量的写请求和读请求才导致的。那么我们可以使用串行队列来解决,将同一个缓存操作场景的读写请求都放到一个串行队列去处理,这样就不存在同时读写的问题了。
问题1:
写多读少的场景:如果串行队列中写请求很多,会导致队列后面的读请求超时。
写少读多的场景:如果读场景很多,但还是一个一个的去处理,能不能读请求并发的去处理呢?
2.3.2 双串行队列:
对于同一个缓存操作场景的读写我们创建两个串行队列,一个写串行队列,一个读串行队列。读写请求同时来了,我们分别放到两个队列中,先去执行写串行队列,读进入等待,那么读队列什么时候去执行了,有两种方式,
一:当写队列执行完毕了,就主动唤醒读队列。
二:读队列的等待时间到了(比如:50ms),就暂停写队列的执行,然后执行读队列,读队列我们可以并发的去执行一批请求(比如:1000个)。
此时写队列又进入了等待,那写队列什么时候唤醒了,机制和上面是一样的。
双串行队列.png对于等待时间和请求数,我们还是要根据我们的业务要求去设置的。
参考文章
https://blog.51cto.com/u_15191078/2983020
https://juejin.cn/post/6844904037943345165#comment