作者:芬达
“芬达的数据库学习笔记 ” 公众号作者,国内最沉闷 mysql ocp 考试探讨群群主,群号:120242978。
本文起源:原创投稿
* 爱可生开源社区出品,原创内容未经受权不得随便应用,转载请分割小编并注明起源。
高能正告,因作者能力无限,文章的剖析过程没有基于 MySQL 源码,而是依据一些业界大佬的文章和官网文档整顿和思考得出的论断,如果感觉说得有情理,请点个赞,如果感觉胡言乱语请留言。本文的受众是至多曾经入门 MySQL 的 DBA。
前言
1. 咱们简略地温习一下半同步的原理:
MySQL5.7 默认参数下咱们开启了半同步,在一个事务提交(commit)的过程时,在 MySQL 层的 write binlog 步骤后,Master 节点须要收到至多一个 Slave 节点回复的 ACK(示意收到了 binlog)后,能力持续下一个事务;
如果在肯定工夫内(Timeout)内没有收到 ACK,则切换为异步复制模式。
这能保证数据不失落的高可用需要,因为他能保障从库确认到这个事务后,再告诉主库提交事务。这种模式下,至多一个从库的日志数据和主库保障同步,从而保障主库挂了后,数据不失落,因为最新数据的是从库。
2. 我先说一下我的半同步保证数据一致性的相干设置
sync_binlog=1
innodb_flush_log_at_trx_commit=1
...(等等)
好了,不列出来了,我保障我半同步设置了最靠谱设置,不会因为参数设置谬误而失落数据。
3. 我再解释一下术语:
lossless semi-sync replication、无损半同步、加强半同步,都是一回事,说的是 MySQL5.7 参数 rpl_semi_sync_master_wait_point=AFTER_SYNC
设置下的半同步,他是默认值,解决高可用切换后的幻读问题。MySQL5.6 没有这个参数设置,这个版本在高可用切换后有概率呈现幻读,产生数据失落,那就是数据不统一。如没有特地指出,本文的 ” 半同步 ” 均指的是无损半同步。
4. 而后还有一点要留神的,kill -9 主库 mysqld 和 被动 shutdown 主库 mysqld 的行为引发高可用切换是不一样,后者行为有概率会导致幻读,也就是数据失落,具体起因看这两篇文章具体理解。
《如何正确地敞开 MySQL 数据库?99% 的 DBA 都是错的!》
《技术分享 | 聊聊 MySQL 关机的故事》》
5. 我确保我测试时,半同步复制没有降级为异步复制。
我上述说的,我假如大家都懂了,所以实践上,无损半同步复制下的主从高可用切换后数据肯定统一吗?
客户角度
我先说答案,数据是统一的!但我这里说的数据统一,指的是,原主库上原先看到的数据库,在新主库 (原备库) 也是只能看到这些数据,不多也不少,也就是我指的是不会产生幻读。
主从角度
But!可能会有另外一种数据不统一,就是切换后,新主和旧主的数据不统一,旧主数据多了!
这篇文章,次要探讨,为什么在主从角度下,数据有可能不统一。
为什么旧主数据多了?
这两张网上流传很广的图,大家如果感觉似曾类似,那必须是资深 DBA 了。我整合了一下,再联合八怪的高清大图(《深刻了解 MySQL 主从原理 32 讲》专栏第 15 节),画了以下这张原理图:
- 图中只关注 binlog、redo、relaylog 三个文件写入和刷盘行为。
- 图中 write 代表写入文件,对于 Linux 零碎个别就是写入到 OS 的 page cache 了。这个时候服务器产生 crash,日志数据将失落。
- 图中 fsync 代表 MySQL 调用 Linux 的 fsync 把 page cache 长久化到磁盘,咱们称之为 ” 刷盘 ”。
- 依据《深刻了解 MySQL 主从原理 32 讲》专栏第 15 节,sync_binlog 设置不一样,MySQL 的 dump binlog 线程工作的起始点会不一样,咱们本文用的是传说中最平安的双一设置,所以
sync_binlog=1
,从库到主库拉去 binlog 的工夫点产生在binlog: fsync
后。
这里是步骤解释:
- 用户在 client 处执行 commit 提交了事务,主库写入了 redo 文件,对应 ”redo prepare: write”
- binlog: write—— master 节点写入 binlog 文件。
- redo prepare: fsync—— 传说中的两阶段提交的第一阶段 prepare 刷盘 (从 OS 层 page cache 长久化刷新到硬盘) 了。
- binlog: fsync—— binlog 刷盘。
- 拷贝 binlog 到 slave 写入到 relaylog 文件,写完则返回 ACK 给 master 节点,示意收到这个 binlog 了,relaylog 依据参数
sync_relay_log=10000
每 10000 个事务刷一次刷盘(因为咱们只探讨主库 crash 的状况,这个从库 relay 刷盘行为咱们不必管) - 依据参数设置
rpl_semi_sync_master_wait_point=AFTER_SYNC
,所以在这个地位点开始期待 slave 节点实现 ”relaylog: write”,而后返回个 ACK 给 master。 - master 节点收到 ACK 后,晓得从库收到这个 binlog 后就能够提交事务了,发动 ”redo commit:write”,传说中的两阶段提交的第二阶段 commit 阶段,commit 到文件。
- master 回复客户端 ACK: commit OK。
其中,我设置了双一参数:
sync_binlog=1
,示意事务提交之前,MySQL 都须要先把 BINLOG 刷新到磁盘
innodb_flush_log_at_trx_commit=1
,示意在每次事务提交的时候,都把 log buffer (内存)刷到文件系统中 (os buffer) 去,并且调用文件系统的“flush”操作将缓存刷新到磁盘下来。
我认为这里指的是刷 redo prepare 状态,redo commit 状态是不须要刷盘的,这是平安的,并且缩小一次 io。懂源码的大佬欢送打脸。
主库解体后主从复制的行为,我间接援用丁奇的《MySQL 45 讲》第十五章的文字,有趣味的同学能够间接去浏览原文。
A 阶段对应的是我图的这个地位
如果主库在 A 阶段产生了 crash,丁奇大佬这么说:
所以 A 阶段主库产生了 crash,主从数据会统一的。
B 阶段对应的是我图的这些地位,按丁奇的说法细分为三种可能,我独自标记了。
如果主库在 B 阶段产生了 crash,丁奇大佬这么说:
2(a) 这个状况下,主库抵赖 binlog 的数据,但在拷贝到从库时,我对丁奇大佬的说法又细分为两个阶段,别离是 2aa 和 2ab 阶段,如下图:
- 2aa 阶段: 这个阶段从库 relaylog 没有拷贝完,主库就挂了,那么从库是不认这些 relaylog 的,所以主库会多了事务。
- 2ab 阶段: 这个阶段从库 relaylog 曾经拷贝完,ack 没有返回胜利给主库,这个时候主库挂了,大家都基于雷同的 binlog 抵赖雷同的事务,数据是统一的。
数据不统一的最直接证明就是,高可用切换后,旧主库起来后 gtid 比新主库 (原备库) 要多。
老主库的 uuid 下的 gtid 编号要多。
测试
原理上,咱们剖析明确了,咱们来理论测试一下,是不是真的会这样。
实验设计:
- 搭建一主一从半同步复制数据库
- sysbench 压测主库几十秒后
- kill -9 主库 mysqld
- 批改 mysqld_safe 或者 systemd 设置,确保避免主库主动被拉起来。
- 查看主库 binlog,查看从库 show slave status\G,比照看两者 gtid 是否统一。
一开始,因为虚拟机,我的 sysbench 压测只能达到 20 tps 时,测了好几次,都无奈测进去问题。而后我调整了虚拟机的配置从 1 vcpu 降级到 8 vcpu,压测能压到 60 tps 了上不去了,测了好几次,还是无奈测进去问题。而后,我发现了瓶颈在于网络,因为我的 sysbench 连贯数据库走的无线网卡了,我更换为走有线网卡后,压测性能达到了 200 tps。我调整压测的 lua 脚本,从 oltp_read_write 调整为 oltp_write_only,减少写 binlog 的机会,那更容易模仿到 gtid 不统一问题,并且此调整后 tps 涨到了 800。
终于,我模仿到了景象。
主库解体后,我查看从库状况,发现 gtid 跑到 283030 这个编号。
[root@192-168-199-132 3306]# mysql -uadmin -pGta@2019 -S /database/mysql/data/3306/mysqld.sock -e "show slave status\G" |grep "ffc43852-1d82-11ed-a65f-000c29375703"
mysql: [Warning] Using a password on the command line interface can be insecure.
Master_UUID: ffc43852-1d82-11ed-a65f-000c29375703
Retrieved_Gtid_Set: ffc43852-1d82-11ed-a65f-000c29375703:210837-283030
Executed_Gtid_Set: ffc43852-1d82-11ed-a65f-000c29375703:1-283030
接着,我解析主库的 binlog,发现多了 红色的三条 gtid 编号。
为什么那么难模仿
说难模仿也不难,只有 tps 高是很容易模仿进去的,这么难模仿进去,还有一个起因是,2aa 阶段其实持续时间很短。
有一个技巧,应用《第 02 问:怎么模拟磁盘 IO 慢的状况?》》大佬黄炎提及的模仿 io 慢的办法,能够让你简直每次都能模仿进去。
- 造一个慢的 io 设施 (如同只能造读写都慢的状况,没关系咱们仅须要利用他读慢这个个性)
- 把它挂载到 binlog 盘
- 利用 io 设施的读很慢的特点,使从库拷贝 binlog 的持续时间拉长。如图 2aa 阶段。
模仿网络慢应该也能达到相似成果
为什么差别了三条 gtid 而不是一条?
这个可能和组提交无关,也可能和并发线程无关,这里不不便开展钻研,这可能是另一篇文章。
主从数据不统一,如何修复
办法一 重启优先于切换
很多状况下,主库 crash 后,会被 mysqld_safe 或者 systemd 服务主动拉起来,如果数据库重启速度很快,其实不肯定要切换,RTO 也能失去保障。旧主有多的 gtid 没关系,拉起来后,gtid 会主动同步到从库,数据就不会不统一啦!
办法二 切换后补数据
咱们晓得 MHA 的切换的大略逻辑是这样的:
- 卸载旧主库 VIP
- 选主,选出有最新 gtid 的从库作为新主(我只有一个从库,就是选他了。。)
- 期待 sql thread 补齐数据(if 有复制提早)
- 主从角色切换
- io thread 级补数,ssh 到旧主拉取 binlog 补数(if 主库 binlog 和从库有 gtid 差别)
- 新主(原备库) 挂载 VIP,去掉 read_only,提供服务
也就是如果你用 MHA 这个高可用,基于 binlog pos 的传统复制模式下,产生了主从切换,并且主库的主机没死,能 ssh 过来的状况下,从库会拉去主库 binlog 来补数,数据不会不统一。
But! 很多 DBA 都晓得了,MHA 在 gtid 模式下有 bug,他 5 这个流程是不走的。咱们大多数人用的都是 gtid 复制模式下的 MySQL,所以这种状况下,主从数据是有概率不统一的。
办法三 切换后旧主 flashback 数据
我看沃趣和万里开源的技术人写的文章发现,他们玩法竟然是不补数据,如果发现旧主多了 gtid 则 flashback 回退这些 gtid!这样旧主才能够作为新从,加回集群。
修复办法,从库补数,还是主库回滚,谁对谁错?
对于主从 gtid 差别的修复办法,竟然分了两个派别:
- 从库补数据
- 主库回退数据
那么谁是对的谁是错的?答案是—— 都行!
在主库 crash 或 kill -9
这种状况下,mysql 客户端会返回【ERROR 2013 (HY000) at line 1: Lost connection to MySQL server during query】产生了这个报错,其实服务器是通知你“在执行查问的时候,你与服务器失去连贯了”当你从新连上服务器后,你应该发动一次重连确认数据的。我举个例子假如你本来在主库上执行以下 SQL,一个叫 fander 的人给他老婆转账 10 万元。
begin;
update accounts set amount = amount - 100000 where account_id=1 and name='fander';
update accounts set amount = amount + 100000 where account_id=2 and name='fander's wife';
commit;
这个时候如果解决这笔业务的 MySQL 服务器主库 crash 了,数据库给应用程序抛出的异样就相似于我方才说的【ERROR 2013 (HY000) at line 1: Lost connection to MySQL server during query】,应用程序重连,连上了高可用切换后的新主库(原备库),自身就应该去查一下,转账胜利没,转账胜利就完事了,没胜利那就再执行一次这条事务。
所以这个转账胜利与否,是 MySQL 通知他。你从库补数,还是不补数,是数据库层决定,而后通知客户即可。
如果是主库 mysqld crash 了,在新主提供服务之前,能够抉择基于近程拷贝 binlog,把旧主日志补到新主。但 server crash 了,从库已作为新主提供业务了,这时旧主不能再往新主补数据了,这会造成数据不统一!这个场景下,旧次要加回集群只有两种抉择:
- 回退多余 gtid 建设复制,加回集群
- 对新主的备份,重做旧主,作为从库建设复制,加回集群。
扩大思考
有没有可能主从高可用切换后,不是主库比从库数据要多 gtid,而是从库要比主库要多 gtid?在 sync_binlog=1
的状况下不会。
因为主库 binlog 长久化后才会同步给从库,不可能从库的 binlog 会比主库要新。
但 sync_binlog !=1
的状况下,有两个起因会导致主库的 binlog 没有从库新。
- binlog 不是每次事务都刷盘,主库挂了后,失落一些事务。
- 即便每次刷盘也没用,因为 dump binlog 起始地位不一样,如果主库挂载在写入 binlog 后,网速很快实现传输到从库,但 binlog 还没有来得及 fsync 这种极限状况下产生了 crash,还是会导致主库失落了这些 binlog,而从库抵赖了这些 binlog。
总结
无损半同步复制下,主从高可用切换后对于业务层面来说数据统一,对于运维层面来说,底层主从可能数据不统一,须要晓得如何修复主从数据到统一。
参考
https://upload-images.jianshu…
https://mp.weixin.qq.com/s/uZ…
https://mp.weixin.qq.com/s/1B…
https://mp.weixin.qq.com/s/8J…
MySQL 实战 45 讲丁奇(林晓斌)