如何保障缓存数据库一致性?
- 数据库和缓存不一致采用什么方案?
- 实现商铺和缓存与数据库双写一致
- 背景
- 点评项目使用了什么策略?
- 存在什么问题?
- 延迟双删(强一致场景)
- 分布式锁(强一致场景)
- 异步通知(强一致场景)
- 基于 MQ 的异步通知
- Canal配合binlog
数据库和缓存不一致采用什么方案?
选取方案:缓存调用者在更新完数据库后再去更新缓存,也称之为双写方案。
操作缓存和数据库时有三个问题需要考虑:
-
删除缓存还是更新缓存?
- 更新缓存:每次更新数据库都更新缓存,无效写操作较多
删除缓存:更新数据库时让缓存失效,查询时再更新缓存
-
如何保证缓存与数据库的操作的同时成功或失败?
- 单体系统,将缓存与数据库操作放在一个事务
- 分布式系统,利用TCC等分布式事务方案
-
先操作缓存还是先操作数据库?
- 先删除缓存,再操作数据库
- 先操作数据库,再删除缓存
我们应当是先操作数据库,再删除缓存,原因在于,在两个线程并发来访问时,假设线程1先来,他先把缓存删了,此时线程2过来,他查询缓存数据并不存在,此时他写入缓存,当他写入缓存后,线程1再执行更新动作时,实际上写入的就是旧的数据,新的数据被旧数据覆盖了。
实现商铺和缓存与数据库双写一致
背景
点评项目的店铺数据因为会被高频访问,所以数据存了两份:MySQL 数据库和 Redis 缓存中。
但是在店铺创建后,进行修改时,需要同时对两者进行变更,因为种种异常原因,如何保障数据库和缓存种短链接的一致性呢?
点评项目使用了什么策略?
根据id查询店铺时,如果缓存未命中,则查询数据库,将数据库结果写入缓存,并设置超时时间。
根据id修改店铺时,先修改数据库,再删除缓存。
修改重点代码1:修改ShopServiceImpl的queryById方法
设置redis缓存时添加过期时间
修改重点代码2
代码分析:通过之前的淘汰,我们确定了采用删除策略,来解决双写问题,当我们修改了数据之后,然后把缓存中的数据进行删除,查询时发现缓存中没有数据,则会从mysql中加载最新的数据,从而避免数据库和缓存不一致的问题
总结:这种方法实现比较简单,不依赖额外中间件,比较适合系统并发不高的情况使用。
存在什么问题?
- 极端情况下的数据不一致
会存在一个很小周期的缓存与数据库不一致的情况,不过对于绝大多数的情况来说,是可以容忍的。除去一些电商库存、列车余票等对数据比较敏感的情况,比较适合绝大多数业务场景。
- 缓存是否能删除?
再思考一个问题,缓存删除真的合适么?在涉及海量并发的场景中,如果程序删除了缓存,可能会导致缓存击穿问题,而更新频繁时则可能引发缓存雪崩。
因此,在考虑缓存一致性模型时,务必充分考虑业务场景是否属于高并发模型。如果是高并发场景,删除缓存可能并不合适,此时应采用最终一致性策略。
那就应该引发出来 Canal 配合 Binlog 的形式解决缓存和数据库最终一致性问题。
下面提一些保证一致性的办法
当面试官问你:redis做为缓存,MySQL的数据如何与redis进行同步呢?(双写一致性问题)
回答一定要设置前提,先介绍自己的业务背景:高一致性要求还是允许延迟一致。
延迟双删(强一致场景)
- 读操作:如果缓存未命中,则查询数据库,将数据库结果写入缓存,并设置超时时间。
- 写操作:
延迟双删
为什么删除两次缓存?
因为在线程1删除缓存后,更新数据库的期间,线程2查询数据库又更新缓存会出不一致。
为什么设置延时?
休眠 1 秒再次淘汰缓存,可以将 1 秒内造成的缓存脏数据再次删除
分布式锁(强一致场景)
更新 DB 时同样更新 cache,保证在一个事务中,通过加锁来保证更新 cache 时不存在线程安全问题
问题:性能低
异步通知(强一致场景)
基于 MQ 的异步通知
对数据的修改后,代码需要发送一条消息到 MQ 中,缓存服务监听 MQ 消息
Canal配合binlog
Cannal是基于MySQL主从同步来实现的。