乐趣区

关于一致性:技术分享-无损半同步复制下主从高可用切换后数据一致吗

作者:芬达

“芬达的数据库学习笔记 ” 公众号作者,国内最沉闷 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 后。

这里是步骤解释:

  1. 用户在 client 处执行 commit 提交了事务,主库写入了 redo 文件,对应 ”redo prepare: write”
  2. binlog: write—— master 节点写入 binlog 文件。
  3. redo prepare: fsync—— 传说中的两阶段提交的第一阶段 prepare 刷盘 (从 OS 层 page cache 长久化刷新到硬盘) 了。
  4. binlog: fsync—— binlog 刷盘。
  5. 拷贝 binlog 到 slave 写入到 relaylog 文件,写完则返回 ACK 给 master 节点,示意收到这个 binlog 了,relaylog 依据参数 sync_relay_log=10000 每 10000 个事务刷一次刷盘(因为咱们只探讨主库 crash 的状况,这个从库 relay 刷盘行为咱们不必管)
  6. 依据参数设置rpl_semi_sync_master_wait_point=AFTER_SYNC,所以在这个地位点开始期待 slave 节点实现 ”relaylog: write”,而后返回个 ACK 给 master。
  7. master 节点收到 ACK 后,晓得从库收到这个 binlog 后就能够提交事务了,发动 ”redo commit:write”,传说中的两阶段提交的第二阶段 commit 阶段,commit 到文件。
  8. 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 慢的办法,能够让你简直每次都能模仿进去。

  1. 造一个慢的 io 设施 (如同只能造读写都慢的状况,没关系咱们仅须要利用他读慢这个个性)
  2. 把它挂载到 binlog 盘
  3. 利用 io 设施的读很慢的特点,使从库拷贝 binlog 的持续时间拉长。如图 2aa 阶段。

模仿网络慢应该也能达到相似成果

为什么差别了三条 gtid 而不是一条?

这个可能和组提交无关,也可能和并发线程无关,这里不不便开展钻研,这可能是另一篇文章。

主从数据不统一,如何修复

办法一 重启优先于切换

很多状况下,主库 crash 后,会被 mysqld_safe 或者 systemd 服务主动拉起来,如果数据库重启速度很快,其实不肯定要切换,RTO 也能失去保障。旧主有多的 gtid 没关系,拉起来后,gtid 会主动同步到从库,数据就不会不统一啦!

办法二 切换后补数据

咱们晓得 MHA 的切换的大略逻辑是这样的:

  1. 卸载旧主库 VIP
  2. 选主,选出有最新 gtid 的从库作为新主(我只有一个从库,就是选他了。。)
  3. 期待 sql thread 补齐数据(if 有复制提早)
  4. 主从角色切换
  5. io thread 级补数,ssh 到旧主拉取 binlog 补数(if 主库 binlog 和从库有 gtid 差别)
  6. 新主(原备库) 挂载 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 了,从库已作为新主提供业务了,这时旧主不能再往新主补数据了,这会造成数据不统一!这个场景下,旧次要加回集群只有两种抉择:

  1. 回退多余 gtid 建设复制,加回集群
  2. 对新主的备份,重做旧主,作为从库建设复制,加回集群。

扩大思考

有没有可能主从高可用切换后,不是主库比从库数据要多 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 讲丁奇(林晓斌)

退出移动版