读写分离的基本结构:

上图的结构是客户端主动做负载均衡,这种模式下一般会把数据库连接信息放在客户端的连接层,由客户端选择后端数据库进行查询。

还有一种架构是在MySQL和客户端间加入中间代理层proxy,客户端只连接proxy,由proxy根据请求类型和上下文决定请求的分发路线:

比较这两种架构:

  • 客户端直连:少了一层,查询性能会好一点,且整体架构简单,排查问题更方便。但在主备切换、库迁移等操作的时候,客户端有感知,需要调整数据库连接信息,一般会用Zookeeper等进行后端管理;

  • proxy架构:客户端不需要关注后端细节,但对维护proxy的团队要求更高,整体架构复杂。

但不论使用哪种架构,由于主从延迟,客户端执行完一个更新事务后马上发起查询,如果查询选择的是从库,都有可能读到刚刚的事务更新之前的状态。

暂时称这种“在从库上读到系统的一个过期状态”的现象为过期读。客户端肯定希望查询从库的数据结果和查主库的数据结果相同,因此接下来就讨论如何处理过期读问题,本文涉及到的处理方案有:

  • 强制走主库方案;

  • sleep方案;

  • 判断主备无延迟方案;

  • 配合semi-sync方案;

  • 等主库位点方案;

  • 等GTID方案。

强制走主库方案

强制走主库其实就是将查询请求做分类,通常查询请求分为两类:

  • 对于必须要拿到最新结果的请求,强制将其发到主库上,比如一个交易平台,卖家发布商品以后要返回主页面查看是否发布成功,该请求必须走主库;

  • 对于可以读到旧数据的请求,将其发到从库上,比如买家逛商铺页面,就算晚几秒看到最新商品也是能接受的。

该方案的最大问题是有时候碰到所有查询都必须拿到最新结果的需求,所有读写压力实际上都在主库,即放弃了读写分离。

Sleep方案

主库更新后,读从库之前先sleep,比如执行一条select sleep(1)命令。该方案就是假设大多数情况下主备延迟在1秒内,sleep有很大概率拿到最新数据。

比如卖家发布商品,商品发布后用Ajax直接把客户端输入的内容作为新的商品显示在页面,这样卖家通过显示就已经确认产品发布成功,等到再刷新页面去查看商品,其实已经过了一段时间,也就达到了sleep的目的。

该方案的问题是不精确:

  • 如果查询请求本来0.5秒就可以在从库上拿到正确请求,也会等1秒;

  • 如果延迟超过一秒,还是会出现过期读。

判断主备无延迟方案

确保备库无延迟,通常有三种做法。

第一种确保主备无延迟的方法是,每次从库执行查询请求前,先判断seconds_behind_master是否已经等于0,如果不等于0就等到这个参数变成0才能执行查询请求。

第二种是对比位点。比如有一个show slave status结果的部分截图:

  • Master_Log_File和Read_Master_Log_Pos:表示读到的主库的最新位点;

  • Relay_Master_Log_File和Exec_Master_Log_Pos:表示备库执行的最新位点。

如果两组值完全相同,就表示接收到的日志已经同步完成。

第三种是对比GTID集合确保主备无延迟:

  • Auto_Position=1,表示这对主备关系使用了GTID协议;

  • Retrieved_Gtid_Set,是备库收到的所有日志的GTID集合;

  • Executed_Gtid_Set,是备库所有已经执行完成的GTID集合。

如果这两个集合相同,表示备库接收到的日志都已经同步完成。

这些方法相比sleep准确度提升了很多,但是还是没达到精确的程度。

接下来看看为什么说没达到精确。先回顾一个事务的binlog在主备库间的状态:

  • 主库执行完成,写入binlog并反馈给客户端;

  • binlog被从主库发给备库,备库接收;

  • 备库执行binlog完成。

上面的方法判断主备无延迟的逻辑是备库收到的日志都执行完成,但是有些日志处于客户端已经收到提交确认,而备库还没收到的状态,如下图:

主库上执行完成三个事务trx1、trx2、trx3,其中:

  • trx1、trx2已经传到从库并执行完成;

  • trx3在主库执行完成并且回复给客户端,但还没传到从库中。

如果此时在从库B上执行查询请求,按照上面方法,从库认为没有同步延迟,但实际上查不到trx3,严格说就是出现了过期读。

配合semi-sync

要解决上面的问题,就要引入半同步复制semi-sync replication。

semi-sync做了设计:

  • 事务提交的时候,主库把binlog发给从库;

  • 从库收到binlog后,发回给主库一个ack,表示收到了;

  • 主库收到这个ack后,才能给客户端返回事务完成的确认。

即所有给客户端发送过确认的事务,都确保了备库已经收到这个日志。

semi-sync加位点判断的方案,能避免过期读。但该方案只对一主一备的场景成立,在一主多从场景中,主库只要等到一个从库的ack就开始给客户端返回确认,此时在从库查询就有两种情况:

  • 如果查询落在这个响应了ack的从库上,能确保读到最新数据;

  • 如果查询落到其他从库,它们可能还没有收到最新的日志,就会产生过期读问题。

判断同步位点方案还有另一个潜在问题:如果在业务更新的高峰期,主库位点或者GTID集合更新很快,那么上面两个位点等值判断就会一直不成立。

实际上,当发起一个查询请求后,要得到准确的结果,其实并不需要等到主备完全同步。比如下面的时序图:

上图中,从状态1到状态4,一直处于延迟一个事务的状态,那么如果按上面必须等到无延迟才能查询的方案,select语句直到状态4都不能被执行。但客户端是在发完trx1更新后发起的select语句,只需要确保trx1执行完就可以select了,即状态3执行查询请求其实就能获得预期结果了。

总结semi-sync配合判断主备无延迟的方案存在的问题:

  • 一主多从,在某些从库执行查询请求会存在过期读的现象;

  • 在持续延迟情况下,可能出现过度等待。

接下来介绍等主库位点方案,可以解决这两个问题。

等主库位点方案

先介绍一条命令:

select master_pos_wait(file, pos[, timeout]);

该命令的逻辑为:

  • 该命令在从库执行;

  • file和pos指主库上的文件名和位置;

  • timeout可选,设置为正整数N表示这个函数最多等待N秒。

该命令正常返回结果是一个正整数M,表示从命令开始执行,到应用完file和pos表示的binlog位置执行了多少事务。此外还有其他结果:

  • 如果执行期间,备库同步线程发生异常,则返回null;

  • 如果等待超过N秒返回-1;

  • 如果刚开始执行时候,发现已经执行过这个位置,返回0。

那么对于之前先执行trx1再执行一个select的逻辑,要保证能查到正确数据,可以使用的逻辑:

  • trx1事务更新完成后,马上执行show master status得到当前主库执行到的file和position;

  • 选定一个从库执行select;

  • 在从库上执行select master_pos_wait(file, position, 1)

  • 如果返回值大于等于0,则在这个从库执行查询语句;

  • 否则,到主库执行查询。

整个流程为:

这里假设这条select最多在从库上等待一秒,那么如果一秒内master_pos_wait返回一个大于等于0的整数,就确保从库上执行的查询结果包含trx1的数据。

最后一步到主库执行查询,是这类方案常用的退化机制,因为不能无限等待从库。

GTID方案

如果数据库开启了GTID模式,对应的也有等待GTID的方案。

MySQL提供了一个类似的命令:

 select wait_for_executed_gtid_set(gtid_set, 1);

其逻辑为:

  • 等待,直到这个库执行的事务中包含传入的gtid_set,返回0;

  • 超时返回1。

在前面等位点的方案中,执行完事务后,还要主动去主库执行show master status。而MySQL 5.7.6版本开始,允许在执行完更新类事务后,把这个事务的GTID返回给客户端,这样等GTID的方案就可以减少一次查询。

此时执行流程变为:

  • trx1事务更新完成后,从返回包直接获取这个事务的GTID,记为gtid1;

  • 选定一个从库执行查询语句;

  • 在从库上执行select wait_for_executed_gtid_set(gtid1, 1);

  • 如果返回值是0,则在这个从库执行查询语句;

  • 否则,到主库执行查询语句。

流程图:

为了让第一步MySQL执行完事务在返回包带上GTID,需要将参数session_track_gtids设置为OWN_GTID,然后通过API接口mysql_session_track_get_first从返回包解析出GTID的值。

MySQL 28 读写分离有哪些坑?的更多相关文章

  1. MySQL ProxySQL读写分离实践

    目的 在上一篇文章MySQL ProxySQL读写分离使用初探里初步介绍了ProxySQL的使用,本文继续介绍它的一些特点和DBProxy的性能差异.深入一些去了解ProxySQL,通过测试来说明Pr ...

  2. MySQL的读写分离的几种选择

    MySQL的读写分离的几种选择 MySQL主从复制(Master-Slave)与读写分离(MySQL-Proxy)实践 原址如下: http://heylinux.com/archives/1004. ...

  3. MySQL的读写分离与主从同步数据一致性

    有没有做MySQL读写分离?如何实现mysql的读写分离?MySQL主从复制原理的是啥?如何解决mysql主从同步的延时问题? 高并发这个阶段,那肯定是需要做读写分离的,啥意思?因为实际上大部分的互联 ...

  4. MyCAT实现MySQL的读写分离

    在MySQL中间件出现之前,对于MySQL主从集群,如果要实现其读写分离,一般是在程序端实现,这样就带来一个问题,即数据库和程序的耦合度太高,如果我数据库的地址发生改变了,那么我程序端也要进行相应的修 ...

  5. Amoeba搞定mysql主从读写分离

    前言:一直想找一个工具,能很好的实现mysql主从的读写分离架构,曾经试用过mysql-proxy发现lua用起来很不爽,尤其是不懂lua脚本,突然发现了Amoeba这个项目,试用了下,感觉还不错,写 ...

  6. Centos7源码安装mysql及读写分离,互为主从

       Linux服务器 -源码安装mysql 及读写分离,互为主从   一.环境介绍: Linux版本: CentOS 7 64位 mysq版本: mysql-5.6.26 这是我安装时所使用的版本, ...

  7. Amoeba实现mysql主从读写分离

    Amoeba实现mysql主从读写分离 这段在网上看了下关于amoeba的文章,总体感觉好像要比mysql-proxy好的多,也参考了不少的资料,此文章可能与其他文章作者会有雷同的地方,请谅解,但是此 ...

  8. Atlas mysql的读写分离和负载均衡<转>

    mysql的读写分离和负载均衡 http://my.oschina.net/superbigfu/blog/178134

  9. python实现mysql的读写分离及负载均衡

    Oracle数据库有其公司开发的配套rac来实现负载均衡,目前已知的最大节点数能到128个,但是其带来的维护成本无疑是很高的,并且rac的稳定性也并不是特别理想,尤其是节点很多的时候. 但是,相对my ...

  10. 应用集成mycat,实现mycat的高可用与mysql的读写分离

    前言 开心一刻 一个女人自朋友圈写道:我家老公昨天和别人家的老婆出去旅游,迄今未归,我则被别人家的老公折腾了一天,好累哦! 圈子下面,评论无数,老公在下面评论到:能不能好好说话,我只不过陪女儿去毕业旅 ...

随机推荐

  1. (萌新向)对于nodejs原型链污染中merge函数的作用的个人理解

    merge函数 function merge(target,source){ for (let key in source){ if (key in source && key in ...

  2. 使用 SpringBoot 集成 WebService [不需要身份验证]

    一.使用 JDK 自带的工具生成实体类 # 格式 wsimport -s 保存路径 -p 包路径 -encoding utf-8 wsdl文件地址 # 实例 wsimport -s d:\wsdl - ...

  3. PVE折腾笔记 (3) 在原QNAP使用的硬盘上创建ZFS

    前言 在经过一番研究后,我决定使用ZFS作为俩机械硬盘的文件系统,本来也可以和QNAP一样直接ext4的,但ZFS比较安全,有自愈功能,可以处理比特位翻转的问题,总之就是好用. 如果追求灵活性可以使用 ...

  4. games101作业环境一站式配置(2022vs版)

    在尝试配置Eigen和opencv多次失败后,找到一位大佬做的一键配置的GAMES101的作业框架以及运行环境,按照步骤安装后,真的是帮助非常大,特别感谢大佬! 这是大佬的链接: https://gi ...

  5. HyperWorks的模型简化

    2.2 模型简化 本节将介绍如何改变结构的外形以实现模型的简化.模型中对分析不需要的小特征,如小孔.导角等,将被去除.模型简化后将大大提高整体模型的网格质量.建模效率以及后续模型求解效率. 本节将学习 ...

  6. jenkins部署到另一台服务器

    安装插件 搜索安装插件:publish over ssh 配置插件 系统管>SSH Servers 前端部署到另一台服务器 其实前端就是将编译后的代码传送至目标服务器的nginx的html目录下 ...

  7. springBoot项目打包并部署centos7

    打包 部署centos7首准备好jdk环境将jar复制到指定的地方启动服务: nohup java -jar inner-analysis-center-1.0.0.jar --spring.prof ...

  8. Delaunay Triangle 学习 2

    Delaunay Triangle 学习 2 参考 Delaunay Triangulation and Meshing Application to Finite Elements [book] h ...

  9. linux 上的格式放入windows 上报错

    简介 vs 上一直说使用unicode编码格式 解决方案 在makefile中添加这两项 format_incode : find . -regex '.*\.\(cpp\|hpp\|cc\|cxx\ ...

  10. eslint vscode 配置

    简介 以作备份 step1 vscode 安装插件 eslint step2 file->preferences->setting code Actions On Save "e ...