莱芜亓家网站,门户网站英文,宁波网站建站公司,杭州推广系统原标题#xff1a;记一次 PXC 集群拆分引发的思考作者简介冷正磊2018年2月加入去哪儿网 DBA 团队#xff0c;主要负责机票业务的 MySQL 和 Redis 数据库的运维管理工作#xff0c;以及数据库自动化运维平台部分功能的开发工作#xff0c;对数据库技术具有浓厚兴趣#xff…原标题记一次 PXC 集群拆分引发的思考作者简介冷正磊2018年2月加入去哪儿网 DBA 团队主要负责机票业务的 MySQL 和 Redis 数据库的运维管理工作以及数据库自动化运维平台部分功能的开发工作对数据库技术具有浓厚兴趣具有多年 MySQL 和 Redis 运维管理和性能优化经验。1. 内容摘要众所周知MySQL 基于 GTID 复制功能的出现极大地简化了 MySQL 复制拓扑初始化配置和变更以及高可用的切换。在去哪儿网我们大量使用 PXC(Percona XtraDB Cluster)集群然而 PXC 中用于记录事务的 Galera GTID 与普通的 MySQL GTID 还是有一点差异运维过程中如果不加注意可能会引发一些问题。本文通过记录一次 PXC 集群拆分的过程中由于未深刻理解这两者的差别而导致的问题与原因分析总结了 Galera GTID 与 MySQL GTID 的异同点以及运维过程中应该注意的事项。2. 背景Qunar 机票核心业务某个 PXC 集群 C1 由于运行时间比较久随着业务的持续发展集群单个节点的实例数据大小已达到 5T 以上对数据量如此大的 MySQL 集群进行日常维护(备份、集群节点水平扩容、实例迁移等)以及实例故障恢复都是一项比较耗时费力的工作。经过与研发讨论后决定将集群 C1 中比较大的两个库 DB1 和 DB2 拆分出来组成一个新的 PXC 集群 C2。集群拆分前后示意图如下(正常每个集群有三个节点为简单起见每个集群只画了一个节点)3. 方案简要说明PXC 集群进行库的拆分大致流程是使用当前集群 C1 的任意一个节点做一个全量副本利用这个副本再做2个节点的数据组建一个三节点的新集群 C2。同时为了保持数据的一致性新集群 C2 的写节点作为原有集群 C1 某个节点的从库不断同步集群 C1 的数据更新。原计划是第一步先迁移 DB1主要流程为业务方下线集群 C1 上与 DB1 相关的应用服务停止对该库中所有表的写入。为了防止遗漏的应用服务对 DB1 进行写入DBA 将该库里面所有的表进行改名即加一个统一的后缀(需提前准备好脚本)。DBA 确认两个集群直接主从同步无延迟后在新集群 C2 上恢复 DB1 所有表的名称即去掉第2步中添加的后缀(需提前准备好脚本)。业务方发布新的应用服务(业务方已提前修改好代码中的数据源配置)开始访问集群 C2 中的 DB1各系统验证业务是否正常。第二步是在完成第一步之后仍然保持两个集群之间主从同步关系等使用 C2 中 DB1 相关的业务确认无问题后以同样的方式迁移 DB2。全部迁移完后观察一段时间确认各业务流程正常最后删除两个集群中不需要的 DB。其中第一步操作过程如下不过在顺利完成第一步后出现了意外原本应该正常同步数据的两个集群出现了复制中断根据报错信息发现大量的数据(除 DB1 之外的库)在集群 C2 上找不到对应的记录由于两个集群中 DB2 的数据没法保证一致性导致不得不中止后续的迁移计划以至于集群 C2 上只完成了库 DB1 的迁移。4. 问题分析与复现4.1 问题分析正常来说集群 C1 已经彻底停止(表名已改)了对 DB1 中表的写入而集群 C2 上只会对 DB1 中表进行写入其他库的写入不受影响应该正常复制才对。既然复制出现了问题那么原有的“理所当然”的想法肯定存在不合理的地方。经过排查我们发现了一个令人匪夷所思的问题两个集群用作复制的两个节点从库和主库的 GTID 的 部分竟然是一样的导致从库在对 DB1 进行写入后生成的 GTID 的 值比主库上大当接收主库推送过来的 binlog 数据时发现主库事务的 GTID 的 值比自己的小于是从库直接选择了跳过该事务并没有重放这部分 binlog从而出现了主从数据不一样的情况。4.2 复现过程为什么新建的从库生成的 GTID 的 会和主库一样呢为了找到问题的原因我们在测试环境用同样的流程对出现的问题进行复现。首先我们复盘了下搭建主从复制的过程大致如下1、使用 Xtrabackup 备份集群 C1 某个节点的全量数据并将数据传送到目的服务器用于新建集群 C2 的第一个节点。2、在目的服务器 apply 备份日志后根据生成的文件 xtrabackup_ binlog_info 中的内容找到复制信息。# catxtrabackup_binlog_infomysql-bin.000015997 401 cdbc9-e228-ee17-496f-5c53bc36ae5b:1-1123,c05582e9-dc11-ee14-6b06-c041b8b7ff2d:1-4,da5e0de8-dc13-ee14-76e6-f074e061cc69:1-23、新建文件 grastate.dat并根据 apply 后生成的文件 xtrabackup_galera_info中的内容填写 grastate.dat 文件信息。# cat xtrabackup_galera_info3faa7d16-23ee-11eb-94f9-3fbe474800d2:4# vim grastate.dat# GALERA saved stateversion: 2.1uuid: 3faa7d16-23ee-11eb-94f9-3fbe474800d2seqno: 4safe_to_bootstrap: 14、 以 bootstrap-pxc 方式启动该实例作为集群 C2 的第一个节点并与老集群 C1 建立复制关系。# 启动实例/etc/init.d/mysql.server -P 3311 bootstrap-pxcmysql reset slave all;Query OK, 0 rows affected (0.00 sec)# 建立新的复制mysql set wsrep_on 0;Query OK, 0 rows affected (0.00 sec)mysql reset master;Query OK, 0 rows affected (0.00 sec)mysql set wsrep_on 1;Query OK, 0 rows affected (0.00 sec)mysql SET GLOBAL gtid_purged401cdbc9-e228-ee17-496f-5c53bc36ae5b:1-1123,c05582e9-dc11-ee14-6b06-c041b8b7ff2d:1-4,da5e0de8-dc13-ee14-76e6-f074e061cc69:1-2;Query OK, 0 rows affected (0.01 sec)mysql change master to master_host10.86.41.xxx,master_port3306,master_userreplication,master_passwordxxxxxxxxxx,master_auto_position1;Query OK, 0 rows affected, 2 warnings (0.02 sec)mysql start slave;Query OK, 0 rows affected (0.00 sec)# 集群C2此时的master信息mysql show master statusG*************************** 1. row ***************************File: mysql-bin.000002Position: 271Binlog_Do_DB:Binlog_Ignore_DB:Executed_Gtid_Set: 401cdbc9-e228-ee17-496f-5c53bc36ae5b:1-1123,c05582e9-dc11-ee14-6b06-c041b8b7ff2d:1-4,da5e0de8-dc13-ee14-76e6-f074e061cc69:1-21 row in set (0.00 sec)5、 向集群 C1 中未迁移的库 test2 中正常写入数据观察主从 master 信息。# 写入前集群C1的master状态mysql show master statusG*************************** 1. row ***************************File: mysql-bin.000015Position: 997Binlog_Do_DB:Binlog_Ignore_DB:Executed_Gtid_Set: 401cdbc9-e228-ee17-496f-5c53bc36ae5b:1-1123,c05582e9-dc11-ee14-6b06-c041b8b7ff2d:1-4,da5e0de8-dc13-ee14-76e6-f074e061cc69:1-21 row in set (0.00 sec# 向集群C1的库test2中写入两个事务的数据后master状态mysql use test2;mysql insert into t values(13);Query OK, 1 row affected (0.00 sec)mysql insert into t values(14);Query OK, 1 row affected (0.00 sec)mysql show master statusG*************************** 1. row ***************************File: mysql-bin.000015Position: 1481Binlog_Do_DB:Binlog_Ignore_DB:Executed_Gtid_Set: 401cdbc9-e228-ee17-496f-5c53bc36ae5b:1-1123,c05582e9-dc11-ee14-6b06-c041b8b7ff2d:1-6, # 发生变化的GTIDda5e0de8-dc13-ee14-76e6-f074e061cc69:1-21 row in set (0.00 sec)# 此时集群C2的master信息mysql show master statusG*************************** 1. row ***************************File: mysql-bin.000002Position: 735Binlog_Do_DB:Binlog_Ignore_DB:Executed_Gtid_Set: 401cdbc9-e228-ee17-496f-5c53bc36ae5b:1-1123,c05582e9-dc11-ee14-6b06-c041b8b7ff2d:1-6, # 发生变化的GTID同步正常da5e0de8-dc13-ee14-76e6-f074e061cc69:1-21 row in set (0.00 sec)此时复制没有问题数据也是正常的。6、向集群 C2 中已迁移的库 test1 中写入两个事务的数据观察主从 master 信 息。mysql insert into t values(7);Query OK, 1 row affected (0.00 sec)mysql insert into t values(8);Query OK, 1 row affected (0.00 sec)mysql show master statusG*************************** 1. row ***************************File: mysql-bin.000002Position: 1209Binlog_Do_DB:Binlog_Ignore_DB:Executed_Gtid_Set: 401cdbc9-e228-ee17-496f-5c53bc36ae5b:1-1123,c05582e9-dc11-ee14-6b06-c041b8b7ff2d:1-8, # 写入后从节点发生变化的GTIDda5e0de8-dc13-ee14-76e6-f074e061cc69:1-21 row in set (0.00 sec)7、 此后如果集群 C1 上继续写入一个事务。mysql delete from t where id 13; # 删除test2库t表中id13的记录Query OK, 1 row affected (0.00 sec)# 集群C1的master信息mysql show master statusG*************************** 1. row ***************************File: mysql-bin.000015Position: 1723Binlog_Do_DB:Binlog_Ignore_DB:Executed_Gtid_Set: 401cdbc9-e228-ee17-496f-5c53bc36ae5b:1-1123,c05582e9-dc11-ee14-6b06-c041b8b7ff2d:1-7, # GTID的gno增加1da5e0de8-dc13-ee14-76e6-f074e061cc69:1-21 row in set (0.00 sec)# 集群C2的master信息mysql show master statusG*************************** 1. row ***************************File: mysql-bin.000002Position: 1209Binlog_Do_DB:Binlog_Ignore_DB:Executed_Gtid_Set: 401cdbc9-e228-ee17-496f-5c53bc36ae5b:1-1123,c05582e9-dc11-ee14-6b06-c041b8b7ff2d:1-8, # GTID的gno没有发生变化因为同步过来的事务的GTID的gno值比自己小选择跳过da5e0de8-dc13-ee14-76e6-f074e061cc69:1-21 row in set (0.00 sec)# 此时C1集群已经被删掉的记录在C2集群仍然存在mysql use test2;Database changedmysql select * from t where id 13;----| id |----| 13 |----1 row in set (0.00 sec)从测试过程中发现确实通过以上的方式搭建主从节点后存在主从节点写入后 GTID 值的部分相同的情况此时如果主从节点同时发生写入(针对不同的库)就会导致主从节点数据不一致。4.3 问题原因对问题复现过程进行分析后发现整个过程有一步是多余的即步骤3(新建 grastate.dat 文件)因为我们的目的是通过搭建从库的方式组建一个新的集群而不是对原有集群扩增节点所以在该 slave 节点(针对原集群而言)以 bootstrap 方式启动时不需要指定原来的集群信息。当以 bootstrap 方式启动 PXC 实例时如果 grastate.dat 文件存在那么该实例会从该文件中获取 uuid 参数的值赋值给参数 wsrep_ cluster_state_uuid同时该参数的值也决定了事务的 Galera GTID 中的 部分所以导致这个实例的与原集群中节点的 相同当作为主从的两个节点都有写入时从库在应用 binlog 时就会出现冲突或者忽略的情况导致主从数据不一致。5. 如何改进通过此次集群拆分过程中出现的问题总结下原因以及改进措施避免后续工作中出现类似情况5.1 原因延用了前期类似经验的惯性思维。因为在此之前有过两次类似的迁移操作只不过当时只需迁移一个库在原集群停止该库的写入后等从库复制无延迟后就马上断开了复制所以没有出现后续复制的问题。操作流程不够精细。操作过程中没有严格区分 PXC 集群新增节点和拆分集群的流程差异细节之处欠缺考虑也反映出个人在对 PXC 的使用和原理方面研究不够深入。5.2 改进措施分别制定 PXC 集群新增节点和拆分集群的操作规范在运维操作时严格按照规范执行。优化集群拆分方案。比如可建立一个中间节点对要迁移的数据库进行过滤复制然后再同步到新集群中可节省组建新集群的时间同时可避免后续在新集群上删除多余的库。3. 在标准规范的基础上实现运维操作自动化避免人为主观因素造成影响。6. 关于 Galera GTID 与 MySQL GTID 的比较6.1 GTID 的概念GTID 特性是 MySQL5.6加入的一个强大的特性全称是 Global Transaction Identifier。MySQL 会为每一个 DML/DDL 操作增加一个唯一标记叫做 GTID这个标记在整个复制环境中都是唯一的格式为 。GTID 相关的几个常见术语server_ uuid单个 GTID 的前半部分即 部分是一个32字节1字节(/0)的字符串。gno单个 GTID 的后半部分即 部分表示事务的序号gno 的值从全局计数器 next_ free_ gno 中获取的。GTID SET表示一个 GTID 的集合可以包含多个 server_ uuid如 executed_ gtid、gtid_ purged。GTID SET IntervalGTID SET 中某个 server_uuid 可能包含多个区间比如 GTID 为“23d45aa2-3d1f-11e6-a16b-c81f66e1165d:1-99:110-200”的字符串中GTID SET Interval 分别是“1-99”和“110-200”。6.2 GTID 的生成GTID 是在 SQL 的 commit 命令发起后order commit 执行到 flush 阶段需要生成 GTID Event 的时候才会获取。MySQL 内部维护了一个全局的 GTID 的计数器 next_ free_gno 用于生成 gno。可参考函数 Gtid_ state∶getautomatic_gno部分代码如下∶// 定义∶Gtid next_candidate{ sidno,sidno get_server_sidno? next_free_gno: 1};// 赋值∶while( true){constGtid_set::Interval *iv ivit.getO;// 定义IntervaL指针指向这个链表指针开头如果在进行下次循环会获得NULLrpl_gno next_interval_startiv ! NULL? iv-start: MAX_GNO;// 正常情况下不会为NULL因此 next_interval_start 等于第一个interval的start当然如果初始化会为NULL如果Interval-next NULL 则标示设有区间了。while(next_candidate.gno next_interval_start DBUG_EVALUATE_IF( simulate_gno_exhausted, false, true))// 如果next_candidate.gno正常不会小于next_intervalL_start// 如果Interval-next NULL或者初始化next_interval_start会被置为MAX_GNO那么条件成立DBUG_RETURN(next_candidate.gno);// 返回了这个gno 则GTID生成{// 返回gnoGTID生成if(owned_gtids.get_ownernext_candidate)O)DBUG_RETURN(next_candidate.gno)// 如果本GTID已经被其他线程占用则next_candidate.gno 继续判断next_candidate.gno;}......}6.3 server_uuid 的生成MySQL 在启动的时候会调用 init_ server_auto_ options 来读取 auto.cnf 文件。如果 auto.cnf 文件不存在则会调用函数 generate_server_ uuid 来生成一个新的 server_uuid这时 GTID 会发生改变。当 auto.cnf 文件不存在时调用函数 generate_ serve_ruid 生成 server_ uuid 的过程中可以看出server_uuid 的生成至少和下面部分有关∶数据库的启动时间。线程的 LWP ID。LWP 是轻量级进程(light-weight process)的简称。一个随机的内存地址。下面是部分代码供参考∶// 获取MySqL启动时间consttime_t save_server_start_timeserver_start_time;// 加入LWP号运算server_start_time((ulonglong)current_pid 48)current_pid;// 一个内存指针即线程结构体的内存地址thd-status_var.bytes_sent(ulonglong)thd;// 具体的运算过程lex_start(thd);func_uuid new(thd-mem_root)Item_func_uuid;func_uuid-fixed 1;func_uid-vaL_str(uuid);6.4 Galera GTIDPXC 集群记录事务的 Galera GTID 中 的生成逻辑与 MySQL GTID 的不太一样而且也不是 PXC 集群的 wsrep_ cluster_state_uuid还是以上面的 PXC 集群 C2 为例看下这个几个参数的值mysql select server_uuid;--------------------------------------| server_uuid |--------------------------------------| 4fd32e4d-249f-11eb-8fd9-fa163e05f092 |--------------------------------------1row inset ( 0. 00sec)mysql select global.gtid_executed;---------------------------------------------------------------------------------------------------------------------------------| global.gtid_executed |---------------------------------------------------------------------------------------------------------------------------------| 401cdbc9-e228-ee17-496f-5c53bc36ae5b:1-1123,c05582e9-dc11-ee14-6b06-c041b8b7ff2d:1-8,da5e0de8-dc13-ee14-76e6-f074e061cc69:1-2 |---------------------------------------------------------------------------------------------------------------------------------1row inset ( 0. 00sec)mysql show status like wsrep_%_uuid;----------------------------------------------------------------| Variable_name |Value |----------------------------------------------------------------|wsrep_local_state_uuid | 3faa7d16-23ee-11eb-94f9-3fbe474800d2 || wsrep_gcomm_uuid |4f807d05- 249f- 11eb-a679-ea70d2c3575a ||wsrep_cluster_state_uuid | 3faa7d16-23ee-11eb-94f9-3fbe474800d2 |----------------------------------------------------------------3rows inset ( 0. 00sec)可以看到PXC 集群中实例的 server_ uuid 并不在它的 GTID SET 中当 PXC 集群写入数据时生成的 MySQL GTID 的也不是服务器的 server_uuid。实际上在 PXC 集群中这么设计合理的因为 PXC 是一个分布式可多写的集群架构所有节点共享相同的 当在不同的节点写入数据时将产生同样的 GTID SET看起来不同的事务像是在同一个服务器上执行的。6.5 Galera GTID vs MySQL GTID两种 GTID 使用的格式相同即 。对于 Galera 来说在集群以 bootstrap 启动时会生成 且集群中的所有节点共享此 。所以说 PXC 集群中各节点之间用作同步的 Galera GTID 和 MySQL GTID 之间并没有直接关系在运维过程中切记不要搞混淆。参考资料1、简书专栏《深入理解主从原理32讲》 作者重庆八怪2、《MySQL 运维内参》 作者周彦伟、王竹峰、强昌金责任编辑