原标题:记一次 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_info

mysql-bin.000015997 401 cdbc9-e228-ee17-496f-5c53bc36ae5b:1-1123,

c05582e9-dc11-ee14-6b06-c041b8b7ff2d:1-4,

da5e0de8-dc13-ee14-76e6-f074e061cc69:1-2

3、新建文件 grastate.dat,并根据 apply 后生成的文件 xtrabackup_galera_info中的内容填写 grastate.dat 文件信息。

# cat xtrabackup_galera_info

3faa7d16-23ee-11eb-94f9-3fbe474800d2:4

# vim grastate.dat# GALERA saved stateversion: 2.1uuid: 3faa7d16-23ee-11eb-94f9-3fbe474800d2seqno: 4safe_to_bootstrap: 1

4、 以 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_purged='401cdbc9-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_host='10.86.41.xxx',master_port=3306,master_user='replication',master_password='xxxxxxxxxx',master_auto_position=1;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表中id=13的记录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 Interval:GTID 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_start=iv != 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生成{// 返回gno,GTID生成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_time=server_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 GTID

PXC 集群记录事务的 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 运维内参》 作者:周彦伟、王竹峰、强昌金

责任编辑:

mysql gtid 还是pxc_记一次 PXC 集群拆分引发的思考相关推荐

  1. 记一次 k8s 集群单点故障引发的血案

    写在前面 公司使用了 k8s 集群来管理一些比较基础的有状态集群,基于 k8s 进行了简单的二次开发,使之可以支持有状态的集群(并没有使用自带的petset,现在改名为statefulset了好像). ...

  2. 原创 【ReactJs+springBoot项目——租房】第13章:MySQL主从复制+MyCat数据库中间件+HAProxy+PXC集群

    分析目前系统架构中的数据库层存在的问题 分析MySQL数据库的集群方案 学习主从复制(读写分离)架构方案 掌握MyCat数据库中间件的使用 掌握HAProxy复制均衡的使用 掌握PXC集群的使用 多种 ...

  3. MySQL数据库高可用集群搭建-PXC集群部署

    Percona XtraDB Cluster(下文简称PXC集群)提供了MySQL高可用的一种实现方法.集群是有节点组成的,推荐配置至少3个节点,但是也可以运行在2个节点上. PXC原理描述: 分布式 ...

  4. mysql pxc集群介绍_PXC集群的概述及搭建

    PXC集群的概述及搭建 PXC集群的简介 Percona XtraDB Cluster(下文简称PXC集群)提供了MySQL高可用的一种实现方法.PXC集群以节点组成(推荐至少3节点,便于故障恢复,后 ...

  5. 搭建pxc集群时需要先安装mysql么_完美起航-高可用MySQL数据库之PXC集群

    高可用MySQL数据库之PXC集群 前言 在上一篇文章介绍了时下流行的几种数据库产品后(公众号发送"NewSQL"查看),有不少小伙伴表示对自动集群的数据库感兴趣,特别是Cockr ...

  6. mysql中的gcache.page文件_高可用MySQL数据库之PXC集群

    前言 在上一篇文章介绍了时下流行的几种数据库产品后(公众号发送"NewSQL"查看),有不少小伙伴表示对自动集群的数据库感兴趣,特别是CockroachDB数据库,但是现有的业务使 ...

  7. mysql 转pxc_PXC 配置笔记-从MySQL直接转成PXC集群

    英文别人github的配置流程 PXC 能提供高可用,高读,多写支持 最重要的优点就是高可能,在3个及以上节点时,其中一个挂了,完全不影响业务. 最大的缺点是多写问题,最短板性能上限问题. 在我们硬件 ...

  8. mysql 集群_MySQL之PXC集群搭建

    一.PXC 介绍 1.1 PXC 简介 PXC 是一套 MySQL 高可用集群解决方案,与传统的基于主从复制模式的集群架构相比 PXC 最突出特点就是解决了诟病已久的数据复制延迟问题,基本上可以达到实 ...

  9. MySQL Percona PXC集群实现MySQL主从复制强一致性

    简介 不同于MySQL的主从复制只能在主节点写入数据的是,PXC可以在任何一个节点写入数据.基于MySQL Percona版本. 架构 部署安装PXC 需要注意的是:要先启动第一个节点(PXC初始化) ...

最新文章

  1. 9种不同的方法帮助你提高国内访问Github的速度!
  2. 关于爬虫异步请求心得
  3. kafka创建topic命令
  4. 计算机工作对身体有害吗,在电脑前长时间工作会对身体有害处吗?
  5. linux挂载ntfs分区报错,Linux下挂载NTFS分区和挂载分区的方法
  6. H1作业(字符串和字节串)
  7. python入门指南-Python完全小白入门指南
  8. 设置DIV最小高度以及高度自适应随着内容的变化而变化
  9. 游戏策划关于游戏概念和游戏原型设计
  10. 采用JDBC解释java SPI机制和线程上下文类加载器 —————— 开开开山怪
  11. 单芯(多芯)液体不锈钢过滤器技术介绍
  12. KingbaseES V8R6集群管理运维案例之---repmgr standby switchover故障
  13. 自然语言处理(NLP)的一般处理流程!
  14. 自制力的本质是什么?怎样才能变得自律?
  15. 学生使用计算机中怎么关机,学生用计算器咋关机
  16. 程序员笔记软件cherrytree推荐
  17. 低成本,大用处——极弱监督的海量文本知识挖掘 | 加州大学圣地亚哥分校商静波...
  18. mac安装Hadoop3.2.1教程(超详细)
  19. Salesforce自定义搜索结果布局
  20. 游戏开放平台开发历程

热门文章

  1. 集合练习:登录注册功能
  2. MongoDB 4.6.1 c++ driver 编译
  3. appium更新到1.8.2,不能打开运行的解决办法
  4. android 去掉标题栏、状态栏、横屏
  5. 【GPS】GPS的C_GNSS_RF_ELNA_GPIO_NUM_DEFAULT配置,Linux系统中GPIO的设置
  6. 爬虫中的关于字符串的一些理解
  7. 对AutoIt中控件和窗口的理解
  8. Ubuntu下安装配置JDK
  9. 输入一个数,判断他是不是质数
  10. 小程序类似抖音视频整屏切换