1、需求起因

在高并发的业务场景下,数据库大多数情况都是用户并发访问最薄弱的环节。所以,就需要使用redis做一个缓冲操作,让请求先访问到redis,而不是直接访问MySQL等数据库。
这个业务场景,主要是解决读数据从Redis缓存,一般都是按照下图的流程来进行业务操作。

读取缓存步骤一般没有什么问题,但是一旦涉及到数据更新:数据库和缓存更新,就容易出现缓存(Redis)和数据库(MySQL)间的数据一致性问题。
不管是先写MySQL数据库,再删除Redis缓存;还是先删除缓存,再写库,都有可能出现数据不一致的情况。举一个例子:
如果删除了缓存Redis,还没有来得及写库MySQL,另一个线程就来读取,发现缓存为空,则去数据库中读取数据写入缓存,此时缓存中为脏数据。
如果先写了库,在删除缓存前,写库的线程宕机了,没有删除掉缓存,则也会出现数据不一致情况。

2、缓存和数据库一致性解决方案

第一种方案:采用延时双删策略

在写库前后都进行redis.del(key)操作,并且设定合理的超时时间。
伪代码如下:

public void write(String key,Object data){redis.delKey(key);
db.updateData(data);
Thread.sleep(500);
redis.delKey(key);
}

具体的步骤就是:
1)先删除缓存
2)再写数据库
3)休眠500毫秒
4)再次删除缓存
那么,这个500毫秒怎么确定的,具体该休眠多久呢?
需要评估自己的项目的读数据业务逻辑的耗时。这么做的目的,就是确保读请求结束,写请求可以删除读请求造成的缓存脏数据。
当然这种策略还要考虑redis和数据库主从同步的耗时。最后的的写数据的休眠时间:则在读数据业务逻辑的耗时基础上,加几百ms即可。比如:休眠1秒。

设置缓存过期时间
从理论上来说,给缓存设置过期时间,是保证最终一致性的解决方案。所有的写操作以数据库为准,只要到达缓存过期时间,则后面的读请求自然会从数据库中读取新值然后回填缓存。

该方案的弊端
结合双删策略+缓存超时设置,这样最差的情况就是在超时时间内数据存在不一致,而且又增加了写请求的耗时。

第二种方案:异步更新缓存(基于订阅binlog的同步机制)

1.技术整体思路
MySQL binlog增量订阅消费+消息队列+增量数据更新到redis
1)读Redis:热数据基本都在Redis
2)写MySQL:增删改都是操作MySQL
3)更新Redis数据:MySQ的数据操作binlog,来更新到Redis

2.Redis更新
1)数据操作主要分为两大块:
。一个是全量(将全部数据一次写入到redis)
。一个是增量(实时更新)

这里说的是增量,指的是mysql的update、insert、delate变更数据。

2)读取binlog后分析 ,利用消息队列,推送更新各台的redis缓存数据。

这样一旦MySQL中产生了新的写入、更新、删除等操作,就可以把binlog相关的消息推送至Redis,Redis再根据binlog中的记录,对Redis进行更新。

其实这种机制,很类似MySQL的主从备份机制,因为MySQL的主备也是通过binlog来实现的数据一致性。

这里可以结合使用canal(阿里的一款开源框架),通过该框架可以对MySQL的binlog进行订阅,而canal正是模仿了mysql的slave数据库的备份请求,使得Redis的数据更新达到了相同的效果。

当然,这里的消息推送工具你也可以采用别的第三方:kafka、rabbitMQ等来实现推送更新R

第三种方案 串行化

先一起细看一下,在一个服务中,并发的多个读写SQL一般是怎么执行的

上图是一个service服务的上下游及服务内部详细展开,细节如下:
(1)service的上游是多个业务应用,上游发起请求对同一个数据并发的进行读写操作,上例中并发进行了一个uid=1的余额修改(写)操作与uid=1的余额查询(读)操作
(2)service的下游是数据库DB,假设只读写一个DB
(3)中间是服务层service,它又分为了这么几个部分
(3.1)最上层是任务队列
(3.2)中间是工作线程,每个工作线程完成实际的工作任务,典型的工作任务是通过数据库连接池读写数据库
(3.3)最下层是数据库连接池,所有的SQL语句都是通过数据库连接池发往数据库去执行的

工作线程的典型工作流是这样的:

void work_thread_routine(){Task t = TaskQueue.pop(); // 获取任务// 任务逻辑处理,生成sql语句DBConnection c = CPool.GetDBConnection(); // 从DB连接池获取一个DB连接c.execSQL(sql); // 通过DB连接执行sql语句CPool.PutDBConnection(c); // 将DB连接放回DB连接池
}

提问:任务队列其实已经做了任务串行化的工作,能否保证任务不并发执行?
答:不行,因为
(1)1个服务有多个工作线程,串行弹出的任务会被并行执行
(2)1个服务有多个数据库连接,每个工作线程获取不同的数据库连接会在DB层面并发执行

提问:假设服务只部署一份,能否保证任务不并发执行?
答:不行,原因同上

提问:假设1个服务只有1条数据库连接,能否保证任务不并发执行?
答:不行,因为
(1)1个服务只有1条数据库连接,只能保证在一个服务器上的请求在数据库层面是串行执行的
(2)因为服务是分布式部署的,多个服务上的请求在数据库层面仍可能是并发执行的

提问:假设服务只部署一份,且1个服务只有1条连接,能否保证任务不并发执行?
答:可以,全局来看请求是串行执行的,吞吐量很低,并且服务无法保证可用性

完了,看似无望了,
1)任务队列不能保证串行化
2)单服务多数据库连接不能保证串行化
3)多服务单数据库连接不能保证串行化
4)单服务单数据库连接可能保证串行化,但吞吐量级低,且不能保证服务的可用性,几乎不可行,那是否还有解?

退一步想,其实不需要让全局的请求串行化,而只需要“让同一个数据的访问能串行化”就行
在一个服务内,如何做到“让同一个数据的访问串行化”,只需要“让同一个数据的访问通过同一条DB连接执行”就行。
如何做到“让同一个数据的访问通过同一条DB连接执行”,只需要“在DB连接池层面稍微修改,按数据取连接即可”
获取DB连接的CPool.GetDBConnection()【返回任何一个可用DB连接】改为
CPool.GetDBConnection(longid)【返回id取模相关联的DB连接】
这个修改的好处是:
(1)简单,只需要修改DB连接池实现,以及DB连接获取处
(2)连接池的修改不需要关注业务,传入的id是什么含义连接池不关注,直接按照id取模返回DB连接即可
(3)可以适用多种业务场景,取用户数据业务传入user-id取连接,取订单数据业务传入order-id取连接即可
这样的话,就能够保证同一个数据例如uid在数据库层面的执行一定是串行的

稍等稍等,服务可是部署了很多份的,上述方案只能保证同一个数据在一个服务上的访问,在DB层面的执行是串行化的,实际上服务是分布式部署的,在全局范围内的访问仍是并行的,怎么解决呢?能不能做到同一个数据的访问一定落到同一个服务呢?
上面分析了服务层service的上下游及内部结构,再一起看一下应用层上下游及内部结构

上图是一个业务应用的上下游及服务内部详细展开,细节如下:
(1)业务应用的上游不确定是啥,可能是直接是http请求,可能也是一个服务的上游调用
(2)业务应用的下游是多个服务service
(3)中间是业务应用,它又分为了这么几个部分
(3.1)最上层是任务队列【或许web-server例如tomcat帮你干了这个事情了】
(3.2)中间是工作线程【或许web-server的工作线程或者cgi工作线程帮你干了线程分派这个事情了】,每个工作线程完成实际的业务任务,典型的工作任务是通过服务连接池进行RPC调用
(3.3)最下层是服务连接池,所有的RPC调用都是通过服务连接池往下游服务去发包执行的

工作线程的典型工作流是这样的:

voidwork_thread_routine(){Task t = TaskQueue.pop(); // 获取任务// 任务逻辑处理,组成一个网络包packet,调用下游RPC接口ServiceConnection c = CPool.GetServiceConnection(); // 从Service连接池获取一个Service连接c.Send(packet); // 通过Service连接发送报文执行RPC请求CPool.PutServiceConnection(c); // 将Service连接放回Service连接池
}

似曾相识吧?没错,只要对服务连接池进行少量改动:
获取Service连接的CPool.GetServiceConnection()【返回任何一个可用Service连接】改为
CPool.GetServiceConnection(longid)【返回id取模相关联的Service连接】
这样的话,就能够保证同一个数据例如uid的请求落到同一个服务Service上。

总结
由于数据库层面的读写并发,引发的数据库与缓存数据不一致的问题(本质是后发生的读请求先返回了),可能通过两个小的改动解决:
(1)修改服务Service连接池,id取模选取服务连接,能够保证同一个数据的读写都落在同一个后端服务上
(2)修改数据库DB连接池,id取模选取DB连接,能够保证同一个数据的读写在数据库层面是串行的

遗留问题
提问:取模访问服务是否会影响服务的可用性?
答:不会,当有下游服务挂掉的时候,服务连接池能够检测到连接的可用性,取模时要把不可用的服务连接排除掉。

提问:取模访问服务与取模访问DB,是否会影响各连接上请求的负载均衡?
答:不会,只要数据访问id是均衡的,从全局来看,由id取模获取各连接的概率也是均等的,即负载是均衡的。

该如何选择呢

个人比较偏好方案二,因为不需要我们在应用层面做很多事情,对一般的开发人员是透明的。方便业务开发速度。

Redis缓存和MySQL数据一致性相关推荐

  1. 高并发架构系列:Redis缓存和MySQL数据一致性方案详解

    需求起因 在高并发的业务场景下,数据库大多数情况都是用户并发访问最薄弱的环节.所以,就需要使用redis做一个缓冲操作,让请求先访问到redis,而不是直接访问MySQL等数据库. 这个业务场景,主要 ...

  2. 面试官:谈谈Redis缓存和MySQL数据一致性问题

    前言 对于Web来说,用户量和访问量增一定程度上推动项目技术和架构的更迭和进步.可能会有以下的一些状况: 页面并发量和访问量并不多,MySQL足以支撑自己逻辑业务的发展.那么其实可以不加缓存.最多对静 ...

  3. Redis系列教程(六):Redis缓存和MySQL数据一致性方案详解

    需求起因 在高并发的业务场景下,数据库大多数情况都是用户并发访问最薄弱的环节.所以,就需要使用redis做一个缓冲操作,让请求先访问到redis,而不是直接访问MySQL等数据库. 这个业务场景,主要 ...

  4. 高并发用redis还是mysql_高并发架构系列:Redis缓存和MySQL数据一致性方案详解

    需求起因 在高并发的业务场景下,数据库大多数情况都是用户并发访问最薄弱的环节.所以,就需要使用redis做一个缓冲操作,让请求先访问到redis,而不是直接访问MySQL等数据库. 这个业务场景,主要 ...

  5. easyexcel写入数据为空_如何解决Redis缓存和MySQL数据一致性的问题?

    在高并发的业务场景下,数据库的性能瓶颈往往都是用户并发访问过大.所以,一般都使用redis做一个缓冲操作,让请求先访问到redis,而不是直接去访问MySQL等数据库.从而减少网络请求的延迟响应. 数 ...

  6. 分布式缓存redis 方案_Redis缓存和MySQL数据一致性方案详解

    在高并发的业务场景下,数据库大多数情况都是用户并发访问最薄弱的环节.所以,就需要使用redis做一个缓冲操作,让请求先访问到Redis,而不是直接访问MySQL等数据库. 这个业务场景,主要是解决读数 ...

  7. redis一般缓存什么样数据_Redis缓存和MySQL数据一致性方案详解

    关注我,可以获取最新知识.经典面试题以及技术分享 一.需求起因 在高并发的业务场景下,数据库大多数情况都是用户并发访问最薄弱的环节.所以,就需要使用redis做一个缓冲操作,让请求先访问到redis, ...

  8. Redis 缓存和 MySQL 如何实现数据一致性

    需求起因 在高并发的业务场景下,数据库大多数情况都是用户并发访问最薄弱的环节.所以,就需要使用redis做一个缓冲操作,让请求先访问到redis,而不是直接访问MySQL等数据库. 这个业务场景,主要 ...

  9. Redis 缓存和 MySQL 数据如何实现一致性?

    需求起因 在高并发的业务场景下,数据库大多数情况都是用户并发访问最薄弱的环节.所以,就需要使用redis做一个缓冲操作,让请求先访问到redis,而不是直接访问MySQL等数据库. 这个业务场景,主要 ...

最新文章

  1. Flutter基础知识链接汇总(持续更新中)
  2. Mysql基础运维及复制架构——实验文档
  3. Akka STM –与STM Ref和Agent进行乒乓球比赛
  4. sql azure 语法_如何在SQL 2016中使用Azure Key Vault使用AlwaysOn配置TDE数据库
  5. 论文阅读:Uncertainty-aware Joint Salient Object and Camouflaged Object Detection
  6. docker获取数据库时间相差8小时_Docker部署服务时间相差8小时解决方法
  7. 一名优秀项目经理需具备的五种基本素质及八大管理技能
  8. Origin绘图使用自定义的RGB配色
  9. pilz pnoz s4说明书_pilz安全继电器PNOZ端子及接线功能描述(中英对照版)
  10. 导航窗口用html语言怎么写,html通用导航条制作详解
  11. 工作中vue项目common方法
  12. 博士申请 | 美国明尼苏达大学葛畅教授招收隐私数据管理方向全奖博士/硕士/博后/访问学者...
  13. AI 之路、回顾 2021,展望 2022 :当代青年的小烦恼
  14. Microsoft Teams显示连接不上网
  15. ORACLE中的KEEP()使用方法
  16. 全球与中国胶原蛋白敷料市场深度研究分析报告
  17. 使用Rust开发操作系统(UEFI基本介绍)
  18. 游戏技巧-《七日杀》存档位置
  19. Java poi api插入文字水印到docx文件
  20. 改进我的人人农场收菜外挂

热门文章

  1. 通信原理第九章 多路复用和多址技术
  2. MT4 managerAPI 接口(头文件)
  3. Excel如何批量制作分数条
  4. Linq学习——查询表达式
  5. 【领域泛化】论文介绍《Learning to balance specificity and invariance for in and out of domain generalization》
  6. Andorid 11 数据和文件存储相关API
  7. input赋值时的空格问题
  8. HOOK之APIhook(转)
  9. UMD(Universal Module Define)一种兼容各大模块方式的写法
  10. 2021-06-08 imshow函数不显示图像解决办法