浅谈Redis跟MySQL的双写问题解决方案


Posted in Redis onFebruary 24, 2022

项目中有遇到这个问题,跟MySQL中的数据不一致,研究一番发现这里面细节并不简单,特此记录一下。

写在前面

严格意义上任何非原子操作都不可能保证一致性,除非用阻塞读写实现强一致性,所以缓存架构我们追求的目标是最终一致性。
缓存就是通过牺牲强一致性来提高性能的。

这是由CAP理论决定的。缓存系统适用的场景就是非强一致性的场景,它属于CAP中的AP。

以下3 种缓存读写策略各有优劣,不存在最佳。

三种读写缓存策略

Cache-Aside Pattern(旁路缓存模式)

Cache-Aside Pattern,即旁路缓存模式,它的提出是为了尽可能地解决缓存与数据库的数据不一致问题。

浅谈Redis跟MySQL的双写问题解决方案

:从缓存读取数据,读到直接返回。如果读取不到的话,从数据库加载,写入缓存后,再返回响应。
:更新的时候,先更新数据库,然后再删除缓存

浅谈Redis跟MySQL的双写问题解决方案

Read-Through/Write-Through(读写穿透)

Read/Write Through Pattern 中服务端把 cache 视为主要数据存储,从中读取数据并将数据写入其中。cache 服务负责将此数据读取和写入 DB,从而减轻了应用程序的职责。

因为我们经常使用的分布式缓存 Redis 并没有提供 cache 将数据写入DB的功能,所以使用并不多。

:先查 cache,cache 中不存在,直接更新 DB。cache 中存在,则先更新 cache,然后 cache 服务自己更新 DB(同步更新 cache和DB)。

:从 cache 中读取数据,读取到就直接返回 。读取不到的话,先从 DB 加载,写入到 cache 后返回响应。

Write Behind Pattern(异步缓存写入)

Write Behind Pattern 和 Read/Write Through Pattern 很相似,两者都是由 cache 服务来负责 cache 和 DB 的读写。

但是,两个又有很大的不同:Read/Write Through 是同步更新 cache 和 DB,而 Write Behind Caching 则是只更新缓存,不直接更新 DB,而是改为异步批量的方式来更新 DB。

很明显,这种方式对数据一致性带来了更大的挑战,比如cache数据可能还没异步更新DB的话,cache服务可能就挂掉了,反而会带来更大的灾难。

这种策略在我们平时开发过程中也非常非常少见,但是不代表它的应用场景少,比如消息队列中消息的异步写入磁盘、MySQL 的 InnoDB Buffer Pool 机制都用到了这种策略。

Write Behind Pattern 下 DB 的写性能非常高,非常适合一些数据经常变化又对数据一致性要求没那么高的场景,比如浏览量、点赞量。

旁路缓存模式解析

Cache Aside Pattern 的一些疑问

旁路缓存模式是我们平时中使用最多的。下面根据上面介绍的旁路缓存模式,我们可以有以下几个疑问。

为什么写操作是删除缓存,而不是更新缓存

:线程A先发起一个写操作,第一步先更新数据库。线程B再发起一个写操作,第二步更新了数据库,由于网络等原因,线程B先更新了缓存,线程A更新缓存。

这时候,缓存保存的是A的数据(老数据),数据库保存的是B的数据(新数据),数据不一致了,脏数据出现啦。如果是删除缓存取代更新缓存则不会出现这个脏数据问题。

实际上要写操作的时候更新缓存也是可以的,不过我们需要加一个锁/分布式锁来保证更新cache的时候不存在线程安全问题。

在写数据的过程中,为什么要先更新DB在删除缓存

:比如说请求1 是写操作,要是先删除缓存A,请求2是读操作,先读缓存A,发现缓存被删除了(被请求1删除了),然后去读数据库,但是此时请求1还没来得及把数据及时更新,那么请求2读的就是旧数据,并且请求2还会把读到的旧数据放到缓存中,造成了数据的不一致。

其实要先删缓存,再更新数据库也是可以,如采用延时双删策略
休眠1秒,再次淘汰缓存 这么做,可以将1秒内所造成的缓存脏数据,再次删除。不一定是1秒,看你业务决定的,不过不推荐这种做法,因为在这1秒内可能发生因素很多,它的不确定性太大。

在写数据的过程中,先更新DB,后删除cache就没有问题了么?

答: 理论上来说还是可能会出现数据不一致性的问题,不过概率非常小。

假设这会有两个请求,一个请求A做查询操作,一个请求B做更新操作,那么会有如下情形产生

(1)缓存刚好失效
(2)请求A查询数据库,得一个旧值
(3)请求B将新值写入数据库
(4)请求B删除缓存
(5)请求A将查到的旧值写入缓存 ok,如果发生上述情况,确实是会发生脏数据。

然而,发生这种情况的概率并不高

发生上述情况有一个先天性条件,就是步骤(3)的写数据库操作比步骤(2)的读数据库操作耗时更短,才有可能使得步骤(4)先于步骤(5)。

可是,仔细想想,数据库的读操作的速度远快于写操作的(不然做读写分离干嘛,做读写分离的意义就是因为读操作比较快,耗资源少),因此步骤(3)耗时比步骤(2)更短,这一情形很难出现。

还有其他造成不一致的原因么?

答: 如果删除缓存过程中失败了就会造成不一致问题

如何解决?
使用Canal去订阅数据库的binlog,获得需要操作的数据。另起一个程序,获得这个订阅程序传来的信息,进行删除缓存操作。

Cache Aside Pattern 的缺陷

缺陷1:首次请求数据一定不在 cache 的问题

解决办法:可以将热点数据提前放入cache 中。

缺陷2:写操作比较频繁的话导致cache中的数据会被频繁被删除,这样会影响缓存命中率 。

数据库和缓存数据强一致场景 :更新DB的时候同样更新cache,不过我们需要加一个锁/分布式锁来保证更新cache的时候不存在线程安全问题。可以短暂地允许数据库和缓存数据不一致的场景 :更新DB的时候同样更新cache,但是给缓存加一个比较短的过期时间,这样的话就可以保证即使数据不一致的话影响也比较小。

到此这篇关于浅谈Redis跟MySQL的双写问题解决方案的文章就介绍到这了,更多相关Redis MySQL双写内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Redis 相关文章推荐
Redis实现订单自动过期功能的示例代码
May 08 Redis
详解redis分布式锁的这些坑
May 19 Redis
深入理解redis中multi与pipeline
Jun 02 Redis
Redis主从配置和底层实现原理解析(实战记录)
Jun 30 Redis
在redisCluster中模糊获取key方式
Jul 09 Redis
基于Redis结合SpringBoot的秒杀案例详解
Oct 05 Redis
Redis 持久化 RDB 与 AOF的执行过程
Nov 07 Redis
redis缓存存储Session原理机制
Nov 20 Redis
Redis安装使用RedisJSON模块的方法
Mar 23 Redis
sentinel支持的redis高可用集群配置详解
Apr 01 Redis
Redis基本数据类型List常用操作命令
Jun 01 Redis
浅谈Redis变慢的原因及排查方法
Jun 21 Redis
解决linux下redis数据库overcommit_memory问题
Feb 24 #Redis
解决Redis启动警告问题
分布式Redis Cluster集群搭建与Redis基本用法
Redis命令处理过程源码解析
Redis+Lua脚本实现计数器接口防刷功能(升级版)
Spring Boot实战解决高并发数据入库之 Redis 缓存+MySQL 批量入库问题
基于Redis zSet实现滑动窗口对短信进行防刷限流的问题
Feb 12 #Redis
You might like
PHP使用redis实现统计缓存mysql压力的方法
2015/11/14 PHP
PHPMailer发送邮件
2016/12/28 PHP
js类中获取外部函数名的方法与代码
2007/09/12 Javascript
jquery分页插件AmSetPager(自写)
2013/04/15 Javascript
JQuery中dataGrid设置行的高度示例代码
2014/01/03 Javascript
js确认删除对话框效果的示例代码
2014/02/20 Javascript
自己动手制作基于jQuery的Web页面加载进度条插件
2016/06/03 Javascript
js实现界面向原生界面发消息并跳转功能
2016/11/22 Javascript
js仿QQ邮箱收件人选择与搜索功能
2017/02/10 Javascript
有关JS中的0,null,undefined,[],{},'''''''',false之间的关系
2017/02/14 Javascript
解析Angular 2+ 样式绑定方式
2018/01/15 Javascript
Vue组件中prop属性使用说明实例代码详解
2018/05/31 Javascript
vue二级菜单导航点击选中事件的方法
2018/09/12 Javascript
在vue项目中优雅的使用SVG的方法实例详解
2018/12/03 Javascript
vue实现购物车抛物线小球动画效果的方法详解
2019/02/13 Javascript
[02:33]2018 DOTA2亚洲邀请赛回顾视频 再次拾起那些美妙的时刻
2018/04/10 DOTA
基于scrapy实现的简单蜘蛛采集程序
2015/04/17 Python
在Python中使用mechanize模块模拟浏览器功能
2015/05/05 Python
Python操作MySQL数据库9个实用实例
2015/12/11 Python
python操作excel的包(openpyxl、xlsxwriter)
2018/06/11 Python
Python设计模式之状态模式原理与用法详解
2019/01/15 Python
selenium+python自动化测试之页面元素定位
2019/01/23 Python
python Selenium实现付费音乐批量下载的实现方法
2019/01/24 Python
Python中类的创建和实例化操作示例
2019/02/27 Python
PyQt5 在label显示的图片中绘制矩形的方法
2019/06/17 Python
Django项目中使用JWT的实现代码
2019/11/04 Python
python ftplib模块使用代码实例
2019/12/31 Python
解决Tensorflow 内存泄露问题
2020/02/05 Python
CSS3 animation实现简易幻灯片轮播特效
2016/09/27 HTML / CSS
日本动漫周边服饰销售网站:Atsuko
2019/12/16 全球购物
人事专员职责
2014/02/22 职场文书
中级会计大学生职业生涯规划书
2014/09/16 职场文书
Python还能这么玩之只用30行代码从excel提取个人值班表
2021/06/05 Python
HTML5页面音频自动播放的实现方式
2021/06/21 HTML / CSS
Python中生成随机数据安全性、多功能性、用途和速度方面进行比较
2022/04/14 Python
Nginx 配置 HTTPS的详细过程
2022/05/30 Servers