版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/liuzhixiong_521/article/details/84588549
前言
从理论上来说,给缓存设置过期时间,是保证最终一致性的解决方案。这种方案下,我们可以对存入缓存的数据设置过期时间,所有的写操作以数据库为准,对缓存操作只是尽最大努力即可。也就是说如果数据库写成功,缓存更新失败,那么只要到达过期时间,则后面的读请求自然会从数据库中读取新值然后更新缓存。
因此,接下来讨论的思路不依赖于给缓存设置过期时间这个方案。
先更新数据库,再更新缓存
一、线程安全问题
假设同时有请求A和请求B进行更新操作,那么会出现如下情景
- 线程A更新了数据库
- 线程B更新了数据库
- 线程B更新了缓存
- 线程A更新了缓存
正确结果应为缓存A数据,但是因为网络原因或者其他原因,导致缓存了B数据,出现脏数据问题。
二、使用场景问题
- 若写请求较多,读请求较少,导致缓存数据被频繁更新,浪费性能。
- 若缓存数据由数据库数据计算得来,则每次写库都要耗费CPU性能做计算更新缓存,浪费性能。
综上,先更新数据库,再更新缓存不适用于作为redis缓存一致性解决方案。
先删缓存,再更新数据库
一、问题
假设同时有请求A进行更新操作和请求B进行读取操作,那么会出现如下情景
- 请求A进行写操作,删除缓存
- 请求B查询发现缓存不存在
- 请求B去数据库查询得到旧值
- 请求B将旧值写入缓存
- 请求A将新值写入数据库
上述情景则会导致缓存不一致问题出现。
二、延时双删策略
流程为
- 先删除缓存
- 再写数据库
- 休眠一段时间
- 再次删除缓存
睡眠时间如何确定?
由于睡眠的原因是为了保证,后续能够删除读库操作误更新的缓存数据,则第二次删除操作应在读库以及更新操作之后,所以睡眠时间应为读库操作时间加上几百毫秒。
如果数据库采用了读写分离架构怎么办?
睡眠时间为主从同步延迟时间加上读库耗时加上几百毫秒。
第二次删除失败解决方案
- 若删除失败,将需要删除的key发送至消息队列,另写代码执行删除操作,直到删除成功。
- 另起程序,订阅数据库的binlog,对删除失败的key进行删除操作。
先更新数据库,再删缓存
问题
假设同时有请求A进行读取操作,请求B做更新操作,那么会出现如下情景
- 缓存刚好失效
- 请求A查询数据库,得一个旧值
- 请求B将新值写入数据库
- 请求B删除缓存
- 请求A将查到的旧值写入缓存
但是,操作3耗时远大于操作2耗时,故这种情况出现的可能性几乎为0,可以忽略不计。
综上所述,先更新数据库,再删缓存为最优提供缓存一致性解决方案(当然,这里讲的方案不包含各种优化)。