简介
问题背景
当使用缓存的时候,无论是本地内存做缓存还是 Redis 做缓存,都会存在数据同步问题,很容易造成数据不一致问题。
解决方案讨论
理论上有这么几种基础的方向:
-
先更新数据库,后更新缓存
-
先更新缓存,后更新数据库
-
先删除缓存,后更新数据库
-
先更新数据库,后删除缓存 解决方案有:
-
延时双删
-
更新与读取操作进行异步串行化
先更新数据库,后更新缓存(没人用)
存在问题
并发更新数据库场景下,会将脏数据刷到缓存。
先更新缓存,后更新数据库(没人用)
存在问题
如果先更新缓存成功,但更新数据库失败的话,一定会造成数据不一致问题,
先删除缓存,后更新数据库(有风险)
存在问题
会带来脏数据的问题,如果在 B 读取 DB 并做缓存到 A 更新 DB 这段时间,缓存中的数据一直是脏数据,而脏数据的存在时间若过长,这是不被允许的。
而且如果不采用给缓存设置过期时间策略,那么该数据就永远都是脏数据了。
先更新数据库,后删除缓存()
存在问题
如果更新数据库成功了,但是删除缓存阶段出错导致删除失败,那么就会有脏数据一直存在的风险了。
这个问题的解决方案就是我们可以订阅 MySQL 的 binlog日志,对缓存进行操作,
先删除缓存,后更新数据库方向
延时双删
简介
延时双删就是:先删除缓存,然后更新数据库,最后休眠 X秒后再次删除缓存。
X 一般就是读数据的业务耗时基础上加个几百 ms 即可。
就是说,前面两步还是一样,但是再加一步延时删除,这样的话如果在第一步删除缓存到更新数据库这段时间内有读请求读到了脏数据的话,等这个读请求完全结束我再去删除缓存。
然后如果是读写分离或者集群的话,会有同步的时间差。一般的解决办法是强制让向 Redis 填充数据的查询数据库操作去向主库进行查询。
更新与读取操作进行异步串行化
简介
异步串行化:在系统内部维护 n个内存队列,更新数据的时候,将数据的 key 作为唯一标识,当发生缓存更新时对该数据的操作就需要串行排队了。这样的话,一个更新数据的操作,先删除缓存,再去更新数据库,如果在更新数据库之前有读请求过来,读到了空缓存,想要读库更新缓存就得去排队,排在更新库的操作后面。
读操作去重:多个读库更新缓存的请求堆积是无意义的,那么就可以做一个过滤,如果队列中已经有该数据的读库更新缓存请求了,我就不放进去了,然后阻塞等到队列中的读库更新缓存的请求完成,我再继续干活。