赞
踩
对于热点数据(经常查询,但不经常修改的数据),我们可以放入redis缓存中,因为如果我们使用Mysql的话,DB是扛不住的。因此采用缓存中间件来增加查询效率,但需要保证Redis中读取数据与数据库存储数据是一致的。
客户端对于数据库主要是读写两个操作。针对放入redis中缓存的热点数据,当客户端想读取数据的时候就在缓存中直接返回数据,即缓存命中,当读数据不在缓存内,就需要从数据库中将数据读入缓存,即缓存未命中。我们可以看到读操作不会导致缓存与数据库的数据不一致。
通常情况下,我们使用缓存的主要目的是为了提升查询的性能。大多数情况下,我们是这样使用缓存的:
建议缓存一致的处理:
3. 先删除缓存,在更新数据库
4. 先写数据库,在删除缓存
我们想一下,如果我们每次写操作后,刚写完缓存,突然网络不好导致数据库写入失败。
**缓存更新成了最新数据,但数据库没有,这样缓存中的数据不就变成脏数据了?**如果此时该用户的查询请求,正好读取到该数据,就会出现问题,因为该数据在数据库中根本不存在,这个问题非常严重。
我们都知道,缓存的主要目的是把数据库的数据临时保存在内存,便于后续的查询,提升查询速度。
但如果某条数据,在数据库中都不存在,你缓存这种“假数据”又有啥意义呢?
因此,先写缓存,再写数据库的方案是不可取的,在实际工作中用得不多
用户的写操作,先写数据库,再写缓存,可以避免之前“假数据”的问题。但它却带来了新的问题。
什么问题呢?
从上我们可以看到先写数据库在写缓存是比较浪费系统资源的,不建议使用
如果我们的业务对缓存命中率有很高的要求,我们可以采用「更新数据库 + 更新缓存」的方案,因为更新缓存并不会出现缓存未命中的情况。
高并发下
A线程删除缓存,但是此时更新数据库的操作还未完成,此时B线程来读取缓存发现缓存没有数据,就去读取数据库的旧值,更新到缓存中,此时A线程更新完了,将新值写入数据库。这种场景下的数据不一致性问题怎么解决呢?。
A线程删除缓存在更新数据库,此时A的更新操作还未完成,而B线程来读取缓存发现缓存没有,去读取数据库,读取的是旧值,然后把旧值写入缓存。A线程Sleep到B线程写入缓存后,在执行删除缓存操作。当其他线程来读取时,数据库就是最新值。
如果第二次删除删除缓存失败,那么可以采用消息队列的重试机制。
如果应用删除缓存失败,可以从消息队列中重新读取数据,然后再次删除缓存,这个就是重试机制。当然,如果重试超过的一定次数,还是没有成功,我们就需要向业务层发送报错信息了。
如果删除缓存成功,就要把数据从消息队列中移除,避免重复操作,否则就继续重试。
「先更新数据库,再删缓存」的策略的第一步是更新数据库,那么更新数据库成功,就会产生一条变更日志,记录在 binlog 里。
于是我们就可以通过订阅 binlog 日志,拿到具体要操作的数据,然后再执行缓存删除,阿里巴巴开源的 Canal 中间件就是基于这个实现的。
**Canal 模拟 MySQL 主从复制的交互协议,把自己伪装成一个 MySQL 的从节点,**向 MySQL 主节点发送 dump 请求,MySQL 收到请求后,就会开始推送 Binlog 给 Canal,Canal 解析 Binlog 字节流之后,转换为便于读取的结构化数据,供下游程序订阅使用。
所以,如果要想保证「先更新数据库,再删缓存」策略第二个操作能执行成功,我们可以使用「消息队列来重试缓存的删除」,或者「订阅 MySQL binlog 再操作缓存」,这两种方法有一个共同的特点,都是采用异步操作缓存
1.更新数据库,在手动清除Redis缓存,在重新查询最新的数据同步到Redis中。
2.更新Mysql数据库,在采用MQ异步的形式同步数据到Redis中,优点是解耦,缺点是延迟的概率大。
3.更新数据库,在基于订阅数据库中的binlog日志采用mq异步的形式同步到Redis中。
4.订阅mysql中的Binlog文件,异步的形式同步到Redis中(canal框架)
阿里云开发和社区:如何保证数据库和缓存双写一致性?
小林coding:数据库和缓存如何保证一致性
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。