前 言
本文是由爱可生运维团队出品的「MySQL专栏」系列文章,内容来自于运维团队一线实战经验,涵盖MySQL各种特性的实践,优化案例,数据库架构,HA,监控等,有扫雷功效。
爱可生开源社区持续运营维护的小目标:
欢迎大家持续关注~
MGR作为MySQL原生的高可用方案,它的基于共识协议的同步和决策机制,看起来也更为先进。吸引了一票用户积极尝试,希望通过MGR架构解决RPO=0的高可用切换。在实际使用中经常会遇到因为网络抖动的问题造成集群故障,最近我们某客户就遇到了这类问题,导致数据不一致。
问题现象
这是在生产环境中一组MGR集群,单主架构,我们可以看到在相同的GTID86afb16f-1b8c-11e8-812f-0050568912a4:57305280下,本应执行相同的事务,但binlog日志显示不同事务信息。
SET @@SESSION.GTID_NEXT= '86afb16f-1b8c-11e8-812f-0050568912a4:57305280'/*!*/;
# at 637087357
#190125 15:02:55 server id 3136842491 end_log_pos 637087441 Query
thread_id=19132957 exec_time=0 error_code=0
SET TIMESTAMP=1548399775/*!*/;
BEGIN
/*!*/;
# at 637087441
#190125 15:02:55 server id 3136842491 end_log_pos 637087514 Table_map:
`world`.`IC_WB_RELEASE` mapped to number 398
# at 637087514
#190125 15:02:55 server id 3136842491 end_log_pos 637087597 Write_rows: table id 398
flags: STMT_END_F
BINLOG '
n7RKXBP7avi6SQAAABov+SUAAI4BAAAAAAEAB2ljZW50ZXIAFUlDX1FVRVJZX1VTRVJDQVJEX0xP
'/*!*/;
### INSERT INTO `world`.`IC_WB_RELEASE`
### SET
SET @@SESSION.GTID_NEXT= '86afb16f-1b8c-11e8-812f-0050568912a4:57305280'/*!*/;
# at 543772830
#190125 15:02:52 server id 3136842491 end_log_pos 543772894 Query
thread_id=19162514 exec_time=318 error_code=0
SET TIMESTAMP=1548399772/*!*/;
BEGIN
/*!*/;
# at 543772894
#190125 15:02:52 server id 3136842491 end_log_pos 543772979 Table_map:
`world`.`IC_QUERY_USERCARD_LOG` mapped to number 113
# at 543772979
#190125 15:02:52 server id 3136842491 end_log_pos 543773612 Delete_rows: table id
113 flags: STMT_END_F
BINLOG '
nLRKXBP7avi6VQAAADNRaSAAAHEAAAAAAAEAB2ljZW50ZXIADUlDX1dCX1JFTEVBU0UACw8PEg8
'/*!*/;
### DELETE FROM `world`.`IC_QUERY_USERCARD_LOG`
### WHERE
从以上信息可以推测,primary节点在这个GTID下对world
.IC_WB_RELEASE
表执行了insert操作事件没有同步到secondary节点,secondary节点收到主节点的其他事件,造成了数据是不一致的。当在表IC_WB_RELEASE
发生delete操作时,引发了下面的故障,导致从节点脱离集群。
2019-01-28T11:59:30.919880Z 6 [ERROR] Slave SQL for channel 'group_replication_applier': Could not execute Delete_rows event on table `world`.`IC_WB_RELEASE`; Can't find record in 'IC_WB_RELEASE', Error_code: 1032; handler error HA_ERR_KEY_NOT_FOUND, Error_code: 1032
2019-01-28T11:59:30.919926Z 6 [Warning] Slave: Can't find record in 'IC_WB_RELEASE' Error_code: 1032
2019-01-28T11:59:30.920095Z 6 [ERROR] Plugin group_replication reported: 'The applier thread execution was aborted. Unable to process more transactions, this member will now leave the group.'
2019-01-28T11:59:30.920165Z 6 [ERROR] Error running query, slave SQL thread aborted. Fix the problem, and restart the slave SQL thread with "SLAVE START". We stopped at log 'FIRST' position 271.
2019-01-28T11:59:30.920220Z 3 [ERROR] Plugin group_replication reported: 'Fatal error during execution on the Applier process of Group Replication. The server will now leave the group.'
2019-01-28T11:59:30.920298Z 3 [ERROR] Plugin group_replication reported: 'The server was automatically set into read only mode after an error was detected.'
问题分析
对于该故障的分析,我们要从主从实例GTID相同,但是事务不同的原因入手,该问题猜测与bug(https://bugs.mysql.com/bug.php?id=92690)相关,我们针对MGR同步事务的时序做如下分析。
相关知识背景
MGR全组同步数据的Xcom组件是基于paxos算法的实现;每当提交新生事务时,主实例会将新生事务发送到从实例进行协商,组内协商通过后全组成员一起提交该事务;每一个节点都以同样的顺序,接收到了同样的事务日志,所以每一个节点以同样的顺序回放了这些事务,最终组内保持了一致的状态。
paxos包括两个角色:
paxos达成共识的过程包括两个阶段:
针对本文案例我们需要强调几个关键点:
分析过程
结合paxos时序,我们对案例过程进行推测:
Tips:以下分析过程请结合时序图操作步骤观看
建议点开放大,效果更清晰 ^ ^
1. 【step1】primary节点要执行对表world
.IC_WB_RELEASE
的insert操作,向组内发送假设将ballot设置为(0.0)以及将value值world
.IC_WB_RELEASE
的prepare请求,并收到大多数成员的ack_prepare返回,于是开始发送accept请求。primary节点将ballot(0.0)的提案信息发送至组内,仍收到了大多数成员ack_accept(ballot=0.0value=world
.IC_WB_RELEASE
)返回。然后发送learn_op信息【step3】。
2. 同时其他从节点由于网络原因没有收到主实例的的learn_op信息【step3】,而其中一台从实例开始新的prepare请求【step2】,请求value值为no_op(空操作)ballot=1.1(此编号中节点编号是关键,该secondary节点编号大于primary节点编号,导致了后续的问题,数值编号无论谁大谁小都要被初始化)。
其他的从实例由于收到过主节点的value值;所以将主节点的(ballot=0.0,value=world
.IC_WB_RELEASE
)返回;而收到的ack_prepare的ballot值的数值符号全组内被初始化为0,整个ballot的大小完全由节点编号决定,于是从节点选取了ballot较大的该实例value值作为新的提案,覆盖了主实例的value值并收到大多数成员的ack_accept【step2】。并在组成员之间发送了learn_op信息【step3】,跳过了主实例提议的事务。
从源码中可以看到关于handle_ack_prepare的逻辑。
handle_ack_prepare has the following code:
if (gt_ballot(m->proposal,p->proposer.msg->proposal))
{
replace_pax_msg(&p->proposer.msg, m);
...
}
3. 此时,主节点在accept阶段收到了组内大多数成员的ack_accept并收到了 自己所发送的learn_op信息,于是把自己的提案(也就是binlog中对表的insert操作)提交【step3】,该事务GTID为86afb16f-1b8c-11e8-812f-0050568912a4:57305280。而其他节点的提案为no_op【step3】,所以不会提交任何事务。此时主实例GTID大于其他从实例的。
4. 主节点新生GTID继续上涨;同步到从实例,占用了从实例的86afb16f-1b8c-11e8-812f-0050568912a4:57305280这个GTID,于是产生了主节点与从节点binlog中GTID相同但是事务不同的现象。
5. 当业务执行到对表world
.IC_WB_RELEASE
的delete操作时,主实例能进行操作,而其他实例由于没有insert过数据,不能操作,就脱离了集群。
▽过程总结:
结论
针对此问题我们也向官方提交SR,官方已经反馈社区版MySQL 5.7.26和MySQL 8.0.16 中会修复,如果是企业版客户可以申请最新的hotfix版本。
在未升级 MySQL 版本前,若再发生此类故障,在修复时需要人工检查,检查切换时binlog中 GTID 信息与新主节点对应 GTID 的信息是否一致。如果不一致需要人工修复至一致状态,如果一致才可以将被踢出的原主节点加回集群。
所以正在使用了MGR 5.7.26之前社区版的DBA同学请注意避坑。
使用指南系列
| DBLE系列公开课
DBLE核心研发主讲:MySQL分布式中间件公开课开课啦
技术分享 | MyCat的坑如何在分布式中间件DBLE上改善(内含视频链接)
| 使用指南
深度分析 | MyCat与DBLE的对比性能调优
开源分布式中间件 DBLE 快速入门指南
DBLE 自定义拆分算法
DBLE Server.xml 配置解析
DBLE Schema.xml 配置解析
DBLE rule.xml 配置解析
| 深度分析
DBLE和Mycat跨分片查询结果不一致案例分析
基于Xtrabackup及可传输表空间实现多源数据恢复
[缺陷分析] Table cache 导致 MySQL 崩溃
开源分布式中间件 DBLE
社区官网: https://opensource.actionsky.com/
GitHub主页: https://github.com/actiontech/dble
技术交流群:669663113
开源数据传输中间件DTLE
社区官网: https://opensource.actionsky.com/
GitHub主页: https://github.com/actiontech/dtle
技术交流群:852990221