|真香,数据库和缓存一致性的几种实现方式
缓存是互联网高并发系统里常用的组件 , 由于多增加了一层 , 如果没有正确的使用效果可能适得其反 , 诸如“缓存是删除还是更新?” , “先操作数据库还是先操作缓存?”都是些老生常谈的话题 , 今天我们就来聊一聊缓存与数据库的双写一致性的解决方案 。
Cache Aside Pattern
在一开始先科普下最经典的缓存+数据库读写的模式 , 就是 Cache Aside Pattern 。
- 读的时候 , 先读缓存 , 缓存没有的话 , 就读数据库 , 然后取出数据后放入缓存 , 同时返回响应 。
- 更新的时候 , 先更新数据库 , 然后再删除缓存 。
本文插图
为什么是删除缓存 , 而不是更新缓存?
更新缓存在并发下会带来种种问题 , 直接删除缓存比较简单粗暴 , 稳妥 。 而且还有懒加载的思想 , 等用到的时候在去数据库读出来放进去 , 不用到你每次去更新他干嘛 , 浪费时间资源 , 而且还有更新失败、产生脏数据的一些风险 ,达成这一点共识以后 , 我们来开始今天的讨论 。
先更新数据库 , 再删除缓存
1、更新数据库成功 , 删除缓存成功 , 没毛病 。
2、更新数据库失败 , 程序捕获异常 , 不会走到下一步 , 不会出现数据不一致情况 。
3、更新数据库成功 , 删除缓存失败 。 数据库是新数据 , 缓存是旧数据 , 发生了不一致的情况 。 这里我们来看下怎么解决
- 重试的机制 , 如果删除缓存失败 , 我们捕获这个异常 , 把需要删除的key发送到消息队列 ,然后自己创建一个消费者消费 , 尝试再次删除这个 key 。
- 异步更新缓存 , 更新数据库时会往 binlog 写入日志 , 所以我们可以通过一个服务来监听 binlog 的变化(比如阿里的 canal) , 然后在客户端完成删除 key 的操作 。 如果删除失败的话 , 再发送到消息队列 。
先删除缓存 , 再更新数据库
1、删除缓存成功 , 更新数据库成功 , 没毛病 。
2、删除缓存失败 , 程序捕获异常 , 不会走到下一步 , 不会出现数据不一致情况 。
3、删除缓存成功 , 更新数据库失败 , 此时数据库中是旧数据 , 缓存中是空的 , 那么数据不会不一致 。 因为读的时候缓存没有 , 则读数据库中旧数据 , 然后更新到缓存中 。
虽然没有发生数据不一致的情况 , 看上去好像一切都很完美 , 但是以上是在单线程的情况下 , 如果在并发的情况下可能会出现以下场景
1)线程 A 需要更新数据 , 首先删除了 Redis 缓存 2)线程 B 查询数据 , 发现缓存不存在 , 到数据库查询旧值 , 写入 Redis , 返回 3)线程 A 更新了数据库 复制代码
本文插图
这个时候 , Redis是旧的值 , 数据库是新的值 , 还是发生了数据不一致的情况 。
延时双删
针对上面这种情况 , 我们有一种延时双删的方法
1)删除缓存 2)更新数据库 3)休眠 500ms(这个时间 , 依据读取数据的耗时而定) 4)再次删除缓存 复制代码
本文插图
你把旧值存在Redis以后 , 过一段时间我在删除一次 , 这时把旧值给删掉了 , 这样就能保证Redis和数据库是同步的了 , 这么做在一定程度上可以缓解这个问题 , 但也不是十分完美 , 比如第一次缓存删除成功了 , 第二次缓存删除失败 , 又该怎么办?
内存队列
除了延时双删这个方法 , 还有个方案就是内存队列 , 他的思想是串行化 , 我们在JVM中维护一个内存队列 。 当更新数据的时候 , 我们不直接操作数据库和缓存 , 而是把数据的Id放到内存队列;当读数据的时候发现数据不在缓存中 , 我们不去数据库查放到缓存中 , 而是把数据的Id放到内存队列 。
推荐阅读
- 苹果手机|从8316来到2449,苹果第一款刘海屏旗舰彻底沦陷,256G真香!
- 技术编程|如何利用数据库进行世界史研究
- 驱动中国|iPhone 12是否真香,这些提前曝光告诉你答案!
- 智能穿戴|小米有品众筹上新了,不到3百Haylou T16耳机,外观小巧音质真香
- 数据库|面试官:说说MySQL数据库分库分表,并且会有哪些问题?
- 刘作虎|刘作虎得意之作:顶级OLED+双扬声器加线性马达,真香!
- 5g|千元真香游戏手机:5G、120Hz、4200mAh,网友:这价格爱了
- 小米手机|最保值的骁龙865旗舰机,发布5个月仅降价300,目前依旧真香?
- 新机发布,5G手机|5G“真香千元机”到来!120Hz+128GB+4200mAh,仅1850元
- 英特尔|两款还未官宣的10代赛扬出现在海外电商平台,赛扬首次拥有4MB三级缓存
