介绍
大家好,我是Leo,目前在常州从事Java后端工程师。上篇文章我们介绍了主库,从库,从库延迟,主库挂了,从库谋权篡位那点事情。从上述中延伸了并行复制策略的发展史,切换策略等。
今天我们介绍一下读写分离那些问题,主要从概念,目的,单到多的演变,安全性演变以及六个解决方案。
思路
根据读者和用户的反馈,画了一个写作思路图。通过此图可以更好的分析出当前文章的写作知识点。可以更快的帮助读者在最短时间内判断是否为有效文章!
案例
首先就是介绍一下我个人全权负责的电商系统。这个是私活,巴西那边的线上订货平台。
这个就是客户端,用户的下单界面。
言归正传,为什么要举这个例子呢? 就是想通过电商系统,来介绍读写分离的问题。以及业务间的考量!根据业务的轻重程序选择相应的解决方案。
读写分离是什么
读写分离就是为了提升性能压力做的一种方案。这种方案的优点与目标就是。主写,从读。专业的库做专业的事情。这样可以提供查询性能,也可以提升写的性能。同时缓解服务器的压力。目的:缓解主库的压力
单到多的裂变
这个大家还是比较熟悉的,以前的单应用时代。因为数据量不大,访问量也不大。往往一台服务器一个数据库就足矣支持日常是使用了。随着大数据时代的到来,用户量,访问量暴增,导致单库无法满足日常需求。所以数据库进行扩展。如下图就是从单库到多库裂变的示例图
安全性的裂变
随着后面科技的发展。数据的安全性能,也是人家关心的一个重点。所以代理层的概念出来了
proxy代理层优缺点
- 有代理层:中间多了一层过滤,查询性能比没有代理层的慢一些,整体结构相对变的复杂一些。对客户端,服务端比较友好,但是对开发团队的能力要求更高。
- 无代理层:没有代理层,中间直连,查询性能比有代理层略好一些。整体架构简单,排查问题也方便。但是在部署的时候比较繁琐,会出现主备切换,库迁移等问题。都需要动数据库连接配置。
抉择: 剩下的抉择就取决于各个公司的科技实力了!
问题
既然考虑到主从库的问题,那么必要会遇到,主库,从库数据一致性的问题。除了数据同步的一致性问题,还有应用时的一致性问题。
数据同步的一致性问题,前几篇文章我们已经讲过了。这里简单回顾一下,就不做详细介绍了。数据同步主要通过binlog完成。深入到细节的话,可以深挖binlog的三种格式,row格式,statement格式,mixed格式。三种格式各有各的优点。
- row:数据比较详细,但是如果数据量非常大的时候比如
delete from history
这个时候要记录,删除的每一条记录,所以比较占内存 - statement:数据不够详细,容易导致,数据不一致 (索引引起的查询方式的不同,limit取值会不一致)。
- mixed:上面的融合版。他中间会有一个判断,判断是否会引起数据不一致这个问题,如果是采用row,如果不会采用statement
应用时的一致性问题,最上面的电商的例子。我在管理端添加一个数据的时候,我们肯定是要第一时间查询是否添加成功的。那么我们在数据库中操作的流程是这样的。
- 写入数据到主库
- 等待主库数据同步到从库
我们在查询的时候是查的从库,所以会有一段时间的不一致问题!我们称之为 过期读 !那么我们如何处理?
解决方案
强制走主库
第一种方式就是强制走主库,强制走主库又分两条路线。不可能全部的请求都打到主库上。所以这里会有一个判断过滤。
这个判断主要会处理看当前的请求是否是及时查看的数据,就比如上述的电商商品一样,添加完商品我们肯定是要查看是否添加成功的。如果是这样的话,那肯定是要强制走主库查询的,
如果是查询一些历史数据,比如说几个月开外的数据,那么肯定不能走主库查询。所以这个就可以不强制走主库。就算晚几秒中看到也是情有可原的。用户还会自动帮我们强刷一下界面呢
sleep方案
这个方案的处理方式就是读从库之前先sleep一下。类似于select sleep(1)
。因为大概的主从库的延迟一般都是1秒,所以我们这里也是给他睡眠1秒。
直接睡一秒严格意义上是影响性能的! 重点来了
我们采用的不是睡完一秒之后,再去从库查询,而且通过前端缓存的方式。以管理端发布商品为例,直接把用户输入的新商品显示在界面上,而不是真的去数据库中查询。等下次刷新的时候也就过了sleep的时间了。主从库的数据也就同步过来了。
自然不是那么简单!
如果主从同步的时间,也就是延迟超过了1秒。那么还会出现过期读的情况
如果查询数据的时间是0.3秒,那么用户还需要等1秒。
判断主从延迟
sleep方案显然是解决不了真实的需求的。但是可以解决大部分场景,对要求比较高的公司还是无法入手。
第一种方式 就是判断主从的延迟情况。前几篇文章我们提到了使用seconds_behind_master
参数,来衡量主从库延迟的长短。
所以,我们在查询的时候可以先判断一下这个参数是否已经等于0。如果不等于0,那就必须等他等于0才可以继续操作。
第二种方式就是判断当前日志的读取位点。这里介绍两个参数。Master_Log_File
和 Read_Master_Log_Pos
表示的是读到主库的最新位点。以及Relay_Master_Log_File
和 Exec_Master_Log_Pos
,表示的是从库执行的最新位点。有了主库的最新位点与从库的最新位点。如果完全相同,就说明已经同步完成了。
第三种方式是对GTID的判断
谈到GTID,我们要聊到它的三个参数,第一个是是否使用协议,第二个是日志的集合,第三个是已经执行完的集合
Auto_Position=1 ,表示这对主从关系使用了 GTID 协议。
Retrieved_Gtid_Set,是从库收到的所有日志的 GTID 集合;
Executed_Gtid_Set,是从库所有已经执行完成的 GTID 集合。
如果集合相同表示同步完成
Semi-sync
seim-sync这个也是MySQL的半同步复制,基于默认的异步复制和完全同步复制之间,它是在master在执行完客户端提交的事务后不是立刻返回给客户端,而是等待至少一个slave收到并写到relay log中才返回给客户端。相对于异步复制,semisync提高了数据的安全性,但是又比完全同步性能好,所以master和slave之间的时间一定要一致,以免造成semisync失败。
这里semi-sync概念来自于 www.linuxidc.com/Linux/2017-…
流程是什么呢
- 事务提交的时候,主库会给从库发一个binlog日志
- 从库接收到binlog之后,会给主库回一下ACK包,表示已经收到了
- 主库收到ACK之后就表示事务完成
也就是说,只要启动了semi-sync。就表示所有给客户端发送过确认的事务,都确保了备库已经收到了这个日志。
semi-synv配置前面关于位点的判断,就能确定在从库上执行的查询请求可以避免过期读。但是无法解决一主多从的问题。
有的小伙伴可能就会问了,为什么?
根据上述流程我们讨论一下。主库给从库发送binlog的时候,从库收到就会给主库发ack,主库收到就完成了。那么谁管你是几个从库? 答案不就出来了嘛!
有哪些问题
- 一主多从的时候,在某些从库执行查询请求会存在过期读的现象;
- 在持续延迟的情况下,可能出现过度等待的问题。(GTID集合的位点与主库的位点永远不一致)
等主库位点方案
理解这种方案,我们要先了解一个指令
select master_pos_wait(file, pos[, timeout]);
这条指令是在从库中执行的。第一个参数 file是文件的名称,第二个参数pos是位置信息,第三个参数是表示这个函数最多可以等待多少秒。
执行完成之后,会返回一个正整数和一些结果。
正整数: 表示从开始应用执行到最后一共执行了多少事务。
结果: 如果执行期间,备库同步线程发生异常,则返回 NULL。如果等待超过 N 秒,就返回 -1。如果刚开始执行的时候,就发现已经执行过这个位置了,则返回 0。
我们可以试想一下这种方法应用之后的流程。
- 事务执行之后,执行
show master status
获取主库上的file文件和pos位置。 - 选定一个从库,在从库上执行
select master_pos_wait(file, pos[, timeout]);
查询返回结果 - 如果返回的结果>0 ,说明从库已经执行过了事务的同步。可以从主库上查询数据
- 否则就从主库,因为从库此时没有更新数据,要从主库上查。
GTID 方案
既然有了GTID模式,那必然也是有GTID方案。
下面我们就来聊一下这个方案。
这个方案比上一个等主库位点方案做了一些优化性的处理。主要优化在show master status
上。
首先介绍一下指令
select wait_for_executed_gtid_set(gtid_set, 1);
- 逻辑就是,等待直到这个库执行的事务中包含传入的gtid_set,返回0
- 否则就是超时返回1
在主库位点方案中,我们要多做一次查询 show master status
,而GTID帮我们省略了这一次查询。
GTID逻辑流程
- 事务A更新完成之后,从这个返回包中取这个事务的GTID,我们先记为gtid1
- 选定一个从库执行查询语句
- 在从库上执行上述的指令
select wait_for_executed_gtid_set(gtid_set, 1);
- 如果返回0表示,表示从从库上查询数据,否则就从主库中(超时了)。
我们再来分析一下流程中的一些问题。返回包中取GTID,那么如何把GTID放进去呢?
只需要将参数 session_track_gtids 设置为 OWN_GTID,然后通过 API 接口 mysql_session_track_get_first 从返回包解析出 GTID 的值即可。
总结
今天介绍了读写分离的一些相关概念,以及发展史,安全问题。最核心的还是读写分离给我们带来的问题,以及这个问题对应的几种方案的比较!根据特定的业务场景选择合适的方案