架构师居然这么设计DB+缓存

来源:楼仔
不好的方案
1. 先写 mysql,再写 redis
2. 先写 redis,再写 mysql
3. 先删除 redis,再写 mysql
好的方案
5. 先写 mysql,再删除 redis
6. 先写 mysql,通过 binlog,异步更新 redis
几种方案比较
大家好,这个问题很早之前我就遇到过,但是一直没有仔细去研究,上个月看了极客的课程,有一篇文章专门有过讲解,所以感觉有必要单独出一篇。
我直接先抛一下结论:在满足实时性的条件下,不存在两者完全保存一致的方案,只有最终一致性方案。 根据网上的众多解决方案,总结出 6 种,直接看目录:
不好的方案
1. 先写 mysql,再写 redis
图解说明:
这是一副时序图,描述请求的先后调用顺序;
橘黄色的线是请求 a,黑色的线是请求 b;
橘黄色的文字,是 mysql 和 redis 最终不一致的数据;
数据是从 10 更新为 11;
后面所有的图,都是这个含义,不再赘述。
请求 a、b 都是先写 mysql,然后再写 redis,在高并发情况下,如果请求 a 在写 redis 时卡了一会,请求 b 已经依次完成数据的更新,就会出现图中的问题。
这个图已经画的很清晰了,我就不用再去啰嗦了吧,不过这里有个前提,就是对于读请求,先去读 redis,如果没有,再去读 db,但是读请求不会再回写 redis。 大白话说一下,就是读请求不会更新 redis。
2. 先写 redis,再写 mysql
同“先写 mysql,再写 redis”,看图可秒懂。
3. 先删除 redis,再写 mysql
这幅图和上面有些不一样,前面的请求 a 和 b 都是更新请求,这里的请求 a 是更新请求,但是请求 b 是读请求,且请求 b 的读请求会回写 redis。
请求 a 先删除缓存,可能因为卡顿,数据一直没有更新到 mysql,导致两者数据不一致。
这种情况出现的概率比较大,因为请求 a 更新 mysql 可能耗时会比较长,而请求 b 的前两步都是查询,会非常快。
好的方案
4. 先删除 redis,再写 mysql,再删除 redis
对于“先删除 redis,再写 mysql”,如果要解决最后的不一致问题,其实再对 redis 重新删除即可,这个也是大家常说的“缓存双删”。
为了便于大家看图,对于蓝色的文字,“删除缓存 10”必须在“回写缓存10”后面,那如何才能保证一定是在后面呢?网上给出的第一个方案是,让请求 a 的最后一次删除,等待 500ms。
对于这种方案,看看就行,反正我是不会用,太 low 了,风险也不可控。
那有没有更好的方案呢,我建议异步串行化删除,即删除请求入队列
异步删除对线上业务无影响,串行化处理保障并发情况下正确删除。
如果双删失败怎么办,网上有给 redis 加一个缓存过期时间的方案,这个不敢苟同。个人建议整个重试机制,可以借助消息队列的重试机制,也可以自己整个表,记录重试次数 ,方法很多。
简单小结一下:
“缓存双删”不要用无脑的 sleep 500 ms;
通过消息队列的异步&串行,实现最后一次缓存删除;
缓存删除失败,增加重试机制。
5. 先写 mysql,再删除 redis
对于上面这种情况,对于第一次查询,请求 b 查询的数据是 10,但是 mysql 的数据是 11,只存在这一次不一致的情况,对于不是强一致性要求的业务,可以容忍。 (那什么情况下不能容忍呢,比如秒杀业务、库存服务等。)
当请求 b 进行第二次查询时,因为没有命中 redis,会重新查一次 db,然后再回写到 reids。
这里需要满足 2 个条件:
缓存刚好自动失效;
请求 b 从数据库查出 10,回写缓存的耗时,比请求 a 写数据库,并且删除缓存的还长。
对于第二个条件,我们都知道更新 db 肯定比查询耗时要长,所以出现这个情况的概率很小,同时满足上述条件的情况更小。
6. 先写 mysql,通过 binlog,异步更新 redis
这种方案,主要是监听 mysql 的 binlog,然后通过异步的方式,将数据更新到 redis,这种方案有个前提,查询的请求,不会回写 redis。
这个方案,会保证 mysql 和 redis 的最终一致性,但是如果中途请求 b 需要查询数据,如果缓存无数据,就直接查 db;如果缓存有数据,查询的数据也会存在不一致的情况。
所以这个方案,是实现最终一致性的终极解决方案,但是不能保证实时性。
几种方案比较
我们对比上面讨论的 6 种方案:
先写 redis,再写 mysql
这种方案,我肯定不会用 ,万一 db 挂了,你把数据写到缓存,db 无数据,这个是灾难性的;
我之前也见同学这么用过,如果写 db 失败,对 redis 进行逆操作,那如果逆操作失败呢,是不是还要搞个重试?
先写 mysql,再写 redis
对于并发量、一致性要求不高的项目,很多就是这么用的 ,我之前也经常这么搞,但是不建议这么做;
当 redis 瞬间不可用的情况,需要报警出来,然后线下处理。
先删除 redis,再写 mysql
这种方式,我还真没用过,直接忽略吧。
先删除 redis,再写 mysql,再删除 redis
这种方式虽然可行,但是感觉好复杂 ,还要搞个消息队列去异步删除 redis。
先写 mysql,再删除 redis
比较推荐这种方式 ,删除 redis 如果失败,可以再多重试几次,否则报警出来;
这个方案,是实时性中最好的方案,在一些高并发场景中,推荐这种。
先写 mysql,通过 binlog,异步更新 redis
对于异地容灾、数据汇总等,建议会用这种方式 ,比如 binlog + kafka,数据的一致性也可以达到秒级;
纯粹的高并发场景,不建议用这种方案,比如抢购、秒杀等。
个人结论:
实时一致性方案 :采用“先写 mysql,再删除 redis”的策略,这种情况虽然也会存在两者不一致,但是需要满足的条件有点苛刻,所以是满足实时性条件下,能尽量满足一致性的最优解。
最终一致性方案 :采用“先写 mysql,通过 binlog,异步更新 redis”,可以通过 binlog,结合消息队列异步更新 redis,是最终一致性的最优解。

如何快速读懂一个陌生的PLC梯形图
如何精准的找到社交类APP软件开发服务商
食品安全检测样品前处理一体机的特点是什么
三星s8刚刚发布 s9项目已上线
华为Mate10最新消息,未来精度的全面屏,虹膜指纹双识别,售价4000向上,力怼iphone8!
架构师居然这么设计DB+缓存
七大精品展区闪耀第85届中国电子展
华为nova3评测 一台令人初感惊艳后并且很难让人挑出毛病的一款手机
韩国半导体巨头库存飙升,存储器市场面临巨大挑战
iOS10.2越狱想越吗?iOS10.2安全越狱无隐患,iOS10.2越狱教程奉上
二级缓存的简单配置教程详解 浅谈二级缓存之功效
无线网络传感器在地质灾害监测中的应用
如何驱动分布式网络中的环境传感器
AMD锐龙5 3500X的性能到底怎么样
一文看懂VR在现实生活中的应用场景
海信OLED电视A8的面世 代表着海信彩电新的技术高度
博通推出全新StrataXGS BCM56440交换芯片系列
亿纬锂能发布“麟驹”系列电池
爱特梅尔推出AVR Studio 5嵌入式微控制器
82C250的功能框图