赞
踩
读写分离 + 主从复制延迟情况下,缓存和数据库一致性的问题
在「先更新数据库,再删除缓存」方案下,「读写分离 + 主从库延迟」其实也会导致不一致:
线程 A 更新主库 X = 2(原值 X = 1)
线程 A 删除缓存
线程 B 查询缓存,没有命中,查询「从库」得到旧值(从库 X = 1)
从库「同步」完成(主从库 X = 2)
线程 B 将「旧值」写入缓存(X = 1)
和「先删除缓存,再更新数据库」类似,缓存中都是留下了脏数据
解决方案:
- 先删除缓存,再更新数据库:在线程 A 删除缓存、更新完数据库之后,先「休眠一会」,再「删除」一次缓存。(延迟时间要大于线程 B 读取数据库 + 写入缓存的时间)
- 读写分离 + 主从库延迟:线程 A 可以生成一条「延时消息」,写到消息队列中,消费者延时「删除」缓存。(延迟时间要大于「主从复制」的延迟时间)
旁路缓存模式(Cache Aside Pattern)
读写步骤:
DB
,然后直接删除cache
。。cache
中读取数据,读取到就直接返回,cache
中读取不到的话,就从DB
读取返回。再把数据写到cache
中。问题:
可以先删除cache,再更新DB吗?
不可以。
如果先写BD,再删除cache就不会造成数据不一致了吗?
理论上来说还是会出现数据不一致的问题,不过概率很小,因为缓存的写入速度是比数据库写入速度快很多。
缺点:
缓存冷启动问题:首次请求的数据一定不在cache的问题
解决方法:以将热点数据提前写入cache
中。
写操作比较频繁的话导致cache中的数据会被频繁的删除,这样会影响缓存命中率。
解决方法:
DB
的时候同样更新cache
,不过需要加一个锁/分布式锁来保证更新cache
的时候不存在线程安全问题。DB
的时候同样更新cache
,但是给缓存加一个比较短的过期时间,保证最终一致性。读写穿透(Read/Write Through Pattern)
读写步骤:
cache
,cache
中不存在,直接更新DB
。cache
中存在,则先更新cache
,然后cache
服务自己更新DB
(同时更新DB
和cache
)。cache
中读取数据,读取到直接返回。从cache
中读取不到,则先从DB
加载写入到cache
后返回响应。缺点:
cache
中的问题,对于热点数据可以提前写入缓存中。异步缓存写入(Write Behind Pattern)
异步缓存写入和读写穿透很相似,两者都是由cache
服务来负责cache
和DB
的读写。
写穿透是同步更新DB
和cache
,而异步缓存写入则是只更新cache
,不直接更新DB
,而是改为异步批量的方式更新DB
。
消息队列中消息的异步写入磁盘、MySQL
的InnoDB Buffer Pool
机制都用到了这种策略。
异步缓存写入的写性能非常高,非常适合写数据经常变化又对数据一致性要求没那么高的场景下使用,比如浏览量、点赞量等。
并发读写时会出现脏数据问题,ABBA
上述情况会导致不一致的情形出现。而且,如果不采用给缓存设置过期时间策略,该数据永远都是脏数据。
但由于缓存的写入要快于MySQL的写入,一般不可能在2跟4之间,穿插一个3操作【3还操作了数据库】【因为写入缓存很快】
- 如果顺序是【2,4,3】的话,A设置了缓存之后也没关系,因为后续B还会再次删除缓存
- 之后的查询,发现缓存过期会去数据库中查询得到最新的数据
解决办法:
异步延时双删 + 设置缓存过期时间(保证最终一致性)
先淘汰缓存
再写数据库(这两步和原来一样)
休眠一段时间,再次淘汰缓存
这么做,可以将这段时间内所造成的缓存脏数据,再次删除。
使用消息队列重试:
订阅binlog日志:
原理:更新数据库成功,就会产生一条变更日志,记录在 binlog 里。
于是我们就可以通过订阅 binlog 日志,拿到具体要操作的数据,然后再执行缓存删除,阿里巴巴开源的 Canal 中间件就是基于这个实现的。
具体流程:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。