文章目录
[隐藏]

最近在准备一个云平台开机自启动的脚本,用以应对服务器重启时业务系统的快速恢复,发现Galera数据库集群的自启动并非简单的启启服务,Galera集群恢复需要考虑多种场景,发现的这篇《How to recover a PXC cluster》博客非常棒,恢复集群的思路非常清晰,本文主要翻译自该博客。

一、概述

1、Galera简介

Galera Cluster是基于MySQL/Innodb二次开发而成的一个支持“多主同步”的数据库主从集群。具备多主、同步复制、高可用等特点。

MariaDB Galera Cluster,由MariaDB 和 MySQL-wsrep补丁实现,同Percona的PXC数据库集群,目前只支持运行在Linux系统上。在 MariaDB 10.1版,MariaDB Server和MariaDB Galera Server安装包已经合并,安装MariaDB时,Galera相关依赖安装包会自动安装,像内置的插件或存储引擎一样,通过简单配置即可启用。

2、Galera集群恢复一般步骤

  • 如果当前Galera集群存在正常运行mariadb的节点(至少一个),启动其他服务关闭的节点上的mariadb服务,进行数据同步,尝试恢复集群;
  • 如果当前Galera集群已经完全停止,需要找到数据最新的节点(最后完成的事务ID号最大),首先从该节点上启动新建集群,启动其余节点上的mariadb服务,数据同步,尝试恢复集群。

二、基本概念

1、Node status

Galera集群中节点在加入集群中具有多种状态,可执行以下查询检查节点状态。

各种状态的说明如下:

ID 名称 说明
1 Open 节点启动成功,尝试连接到集群,如果失败则根据配置退出或创建新的集群
2 Primary 节点已处于集群中,在新节点加入时,选取donor进行数据同步时会产生的状态
3 Joiner 节点处于等待接收/接收同步文件时的状态
4 Joined 节点完成数据同步,但有部分数据没跟上,在尝试保持和集群进度一致的过程状态。例如某个节点故障后,重新加入集群,在追赶集群进度时的状态
5 Synced 节点正常提供服务的状态,表示已经同步完成并和集群进度保持一致
6 Donor 节点处于为新节点提供全量数据数据同步时的状态。此时该节点对客户端不提供服务

2、Primary Component

为避免单点故障,在网络故障发生时,集群可能分成几个部分,在此情形下,只有其中一个数据部分可以继续修改数据库状态用来避免历史数据的不一致,集群的这部分称为Primary Component。

3、GTID

Global Transaction ID,由UUID和sequence number偏移量组成。wsrep api中定义的集群内部全局事务id,一个顺序ID,用于记录集群中发生状态改变的唯一标识以及队列中的偏移量。

4、SST

State Snapshot Transfer 状态快照迁移:集群中数据共享节点通过从一个节点到另外一个节点迁移完整的数据拷贝(全量拷贝)。当一个新的节点加入到集群中,新的节点从集群中已有节点同步数据,开始进行状态快照迁移:

可以在Galera集群中选择两种不同的状态转移方法:

  • 逻辑数据转移:

采用mysqldump命令,在转移之前,需要数据接收方服务器完全启动,并准备好接受数据的连接准备。

这是一个阻塞式方法,数据共享节点Donor在状态转移节点处于只读状态,在数据共享节点Donor上适用FLUSH TABLES WITH READ LOCK命令,mysqldump是速度最慢的SST方法,在负载比较的数据库集群上可能是个问题。

  • 物理数据转移:

该方法采用rsync、rsync_wan、xtrabackup或其他方法从服务器之间直接拷贝数据,数据接受服务器在拷贝完数据后启动服务器。

该方法较mysqldump速度较快,但存在一定的限制,只能在服务器启动时采用,数据接受服务器需要同数据共享服务器Donor配置相同(例如,服务器间innodb_file_per_table配置必须完全一致)。

其中的一些方法例如xtrabackup可以使Donor处于非阻塞状态,可通过可编写脚本的SST接口来支持该方法。

对于通过配置文件指定状态快照迁移的方法,例如:

5、 IST

Incremental State Transfer,增量状态迁移,集群一个节点通过识别新加入节点缺失的事务操作,将该操作发送,而并不像SST那样的全量数据拷贝。该方法只在特定条件下可用:

  • 新加入节点的状态UUID与集群组中节点一致;
  • 新加入节点所缺失的写数据集write-sets可以在Donor的写数据集write-sets存在。

当以上条件满足时,Donor节点只迁移确实的事务,并在新加入节点按序执行,直到同集群数据一致。

例如:Galera集群中有一个节点宕机,该节点状态如下:

同时,正在运行的节点状态如下:

Donor节点收到新加入节点的状态转移请求,Donor检查写数据集缓存write-set cache序列号为197223,如果该序号在写数据集write-set缓存不存在,就会执行SST全量数据拷贝,如果存在,Donor节点会将197223到201913事务提交发送给新加入节点,从而取代SST操作。

增量状态迁移优势是大幅提高节点重新加入集群的速度。另外,这个过程在Donor上是非阻塞的。

需要注意的是:Donor节点上的gcache.size参数对IST非常重要,该参数决定分配给写数据集缓存的内存大小,分配的内存空间越大,可缓存的写数据集越多。缓存写数据集越大,涵盖的IST事务操作越多。但如果该参数远大于数据库状态的大小,IST的效率会比SST低。

6、 Gcache

Galera集群将保存写数据集的一个特殊缓存称为Write-set Cacheh或Gcache,Gcache缓存为Gcache的内存分配程序。其目的主要是减少写数据集在内存上的占用空间。Galera集群对以上的改进是通过将内存中的写数据集存储卸载到硬盘上。

Gcache使用三种类型的存储:

  • 内存中永久存储

通过操作系统默认的内存分配器对写数据集进行分配。对尚存在多余内存的系统十分有用,该存储具有硬性的大小限制。默认是禁用的。

  • 永久的环形缓存文件:在缓存初始化时写数据集是从磁盘上提前分配的。这主要是作为主的写数据集缓存的。
  • 按需页存储:如果有必要,在运行时,写数据集分配给内存映射页文件。默认大小是128Mb,但可以按序增加,页存储大小受空余磁盘大小的限制。默认,Galera集群会删除不使用的页存储,但可以设置保留页文件总大小的限制。在以上的存储中,如果所有其他存储都被禁用,至少有一个页文件在磁盘上存在。

Galera集群使用使用分配算法安装以上顺序尝试存储写数据集。首先会尝试使用永久内存存储,如果没有足够内存,将尝试永久的环形缓存文件,页存储通常都会有效,除非写数据集大于磁盘空限量。

默认的,写数据集缓存分配文件在该进程的工作目录中,可以通过gcache.dir参数指定写数据集缓存的确切位置。

7、 Split-brain

脑裂是数据库集群出现分区后每一部分都独立运行的一种情形。这种情形发生后,quorum算法难以发现Primary Component,然后导致无法执行任何查询操作。这是情形应尽力避免。

8、grastate.dat

识别最新节点ID是通过对比不同节点上GTID的值来进行,grastate.dat文件中保留了这些信息:在此文件中可以发现当前节点最新状态ID的值,当节点正常退出Galera集群时,会将GTID的值更新到该文件中。

依次停止Galera不同节点上数据服务,会发现后来服务停止服务节点上最后提交事务序号seqno越大,这对于查找数据最新的节点非常有帮助。

如果该节点正在运行服务,grastate.dat文件中最后事务提交序号seqno为-1,文件如下:

最后提交事务ID不停再变化,可以通过以下命令查询:

9、 gvwstate.dat

当集群形成或改变Primary Component时,节点会创建或更新gvwstate.dat文件,确保节点保留最新Primary Component的状态,如果节点无法连接,可以利用该文件进行参照,如果节点正常关闭,该文件会被删除。文件样例如下:

文件有两部分组成:

  • 本节点信息:my_uuid统一标识符。
  • Primary Component的相关信息:包含在#vwbeg和#vwend之间。
    • view_id 由view_type、view_uuid和view_seq三部分组成的标识符:
      • view_type 规定用3表示Primary信息。
      • view_uuid 和view_seq形成惟一的标识符。
    • bootstrap 显示该节点是否启动,但这不影响Primary Component的恢复流程。
    • member 显示primary component中节点的UUID。

三、 七种故障场景的恢复

1、场景一

A节点正常停止,如果其他节点收到“A节点停止”消息,集群数量会减少,quorum calculation 或auto increment自动改变。重启A后,由于my.cnf指定了wsrep_cluster_address的原因,该节点会自动加入该集群,这个过程与normal replication不同,直到该节点完成数据同步前,不会为相应任何请求。故仅仅能够连接上该节点还不够,必须等到该节点的状态成功转移才能正常访问。如果B或C节点上存在A节点宕机期间所有事务的写数据集缓存(gcache.size),A重新加入集群可能通过IST(通常非常轻快),不然,就需要SST全量数据拷贝,实际为全部二进制数据的快照拷贝。因此,选择最优的Donor非常重要。如果Donor存在gcache的事务丢失,那么Donor会自动执行SST操作。

2、场景二

同前一个场景,集群数量变为1,因此单节点C变为primary component,并相应客户端请求,恢复集群时,需要启动A、B两节点。由于节点C必须为至少第一个加入的节点提供状态转移,它切换至Donor/Desynced状态,在此过程中,它仍有可能进行读写操作,但它可能比较慢,这取决于它发送状态转移数据的大小,另外,一些负载均衡器认为该donor node可能不再运行,可能将从调度池中移除,当只有一个节点运行是最好避免这种情形。

需要注意的是,如果按顺序重启A,然后重启B,如果不想让A充当B的donor,因为此时A的gcache中还没有所有B所需的写数据集,如果指定C作为B的donor的话,可以采用如下方式:

3、场景三

所有的节点都正常停止,在这种情况下,问题是如何重新启动,需要知道的是,在完全关闭时,PXC节点将最后的执行顺序写到了grastate.dat文件中,比较三个节点上该文件中seqno序号,你会发现那个节点是数据最新的节点(即为最后一个停止的节点),Cluster必须从该节点上启动,不然,具有更多最新数据的节点(后停止的节点)会从具有少量最新数据的节点(先停止的节点)执行全量SST,启动第一个节点的脚本如下:

需要注意的是:如果你启动最新的节点,具有比较小的序号其他节点会通过full-SST的方式加入集群,因为Galera Cache在重启时还不可用,基于这个原因,因此建议在停止Galera集群前,建议停止对集群的写操作。确保所有节点在统一时刻停止,但在Galera 3.19版本之后有了gcache-recover选项后发生了变化。

4、场景四

因电力故障、硬件故障、内核错误、mysqld crash, kill -9 on mysqld pid, OOMkiller等原因,节点A从集群中消失,剩下的两个节点发现连接A失败,这时会重连A,多次超时后,B、C两节点都认为A确实已经宕机,将正式将A从集群中移除,Quorum成立(2/3节点仍在运行),服务不会中断,重启A之后,A会自动加入集群。

5、场景五

AB两节点消失,节点C自己无法形成quorum,集群切换到无主模式non-primary,这时MySQL拒绝接受任何SQL查询,这时,Mysql进程将在C上继续运行,可以连接到C,但和数据相关的statement将会失败。

实际上,在C已经决定它不能连接A和B前的一段时间里,读操作还可以进行,但由于Galera中的certification based replication 的限制,将不运行任何的写操作。这是我们将会看到以下的日志信息:

单点C等待其他对等的A、B节点进行重启,如果发生网络中断情况,并且A、B节点仍在运行,网络正常时集群会自动恢复,如果A与B仅仅是与C进行网络切断,但A、B之间可以相互访问,由于他们可以形成quorum,A、B可以继续工作,如果A、B由于数据不一致、Bug或电力故障等原因崩溃,在A、B恢复之前,需要在C上确定primary component,这好比告诉C:“你可以自己成为一个单独的集群啦,不要管A、B啦”,命令如下:

特别注意的是,在执行上面操作之前,你反复检查,非常确定A、B已经Down了,不然,集群的数据可能造成不一致。

6、场景六

所有节点全部Down掉,并且没有进行正常的关闭流程,可能发生于数据中心电力故障、MySQL 或Galera Bug导致所有节点crash,另外导致了数据不一致,集群监测到每个节点都有不同的数据,这时,grastate.dat文件将不会被更新,并且其中不存在一个有效的seqno序号,类似如下:

这时,我们不确定节点间数据是否一致,因此,找到保留最新数据节点,并在该节点上重启集群成为关键,在开始Mysql守护进程之前,必须通过检查事务的状态来抽取最后的序列号,可以利用如下方法:

最后事务提交顺序号是在节点2,现在就可以从节点2上启动,并在其他节点上启动服务。

但上述流程在自从PXC 5.6.19后最近Galera versions (3.6+?)版本不可用,它增加了pc.recovery,它将集群状态保存到每个节点上的gvwstate.dat中,如其中的变量(pc – primary component)所示,它保存了集群成为PRIMARY的状态,文件内容如下:

我们看到三个成员节点都已经启动,由于这个新的特征,在数据中心发生电力故障时,在电力恢复后,节点在启动时会读取最后的状态,一旦所有成员节点可以相互连接,将尝试恢复primary component。这使得PXC集群能够从电力故障发生时无需任何人工干预就能自动恢复,查看日志可以看到:

7、场景七

集群因脑裂而丢失primary状态,我们假设集群成员节点变成了偶数个,例如在六个节点中,有三个在一个地方(数据中心),并且另外三个在另外的一个地方(数据中心),此时他们之间发生了网络中断,当然实际中应避免发生这种网络拓扑,实际的节点应该是奇数个,至少可用增加一个 arbitrator (garbd) 或着为其中的一些节点设置不同权重 pc.weight,但实际中会以多种方式发生脑裂,所以两个分开的两组节点都不能保持quorum状态,所有的节点必须停止服务请求,并且集群的每部分都不断的尝试重新连接。如果想恢复故障即使网络故尚未恢复,都可以利用场景五的方法指定其中一部分为primary,

之后,可以着手手动恢复一部分集群的工作,一旦网络恢复,另外一部分可以通过增量状态转移(IST)重新加入集群。

但要小心的是:如果在两个分开的部分上设置了bootstrap启动选项,最后会得到两个集群实例,他们之间的数据可能会发生分流。恢复网络连接不会使两部门节点重组,直到节点重启,并尝试重连配置文件中的指定的节点。那么,Galera副本模型对数据一致性要求苛刻,一旦监测到不一致的状态,节点因数据不同将不能执行行修改的语句,这将出现紧急关闭的情况,并且恢复集群的惟一途径是全量SST。

四、参考文档

Galera集群启动脚本请参考:

MariaDB Galera集群自动恢复脚本

Galera恢复场景主要翻译至Przemysław Malkowski的博文,英文原文请参考:

Galera replication – how to recover a PXC cluster

 

其他参考文献:

http://www.ywnds.com/?p=6084

http://nirbhay.in/blog/2015/02/split-brain/

http://jschu.blog.51cto.com/5594807/1887339

http://galeracluster.com/documentation-webpages/pcrecovery.html

http://galeracluster.com/documentation-webpages/restartingcluster.html

http://itindex.net/detail/54074-galera-%E5%90%8C%E6%AD%A5-mysql

http://galeracluster.com/documentation-webpages/statetransfer.html#state-snapshot-transfer-sst