组复制简介

组复制MySQL Group Replication(简称MGR)是MySQL官方在已有的Binlog复制框架之上,基于Paxos协议实现的一种分布式复制形态。RDS MySQL集群系列实例支持组复制。本文介绍组复制的优势、技术实现原理、AliSQL对组复制稳定性的优化。

组复制的优势

组复制、半同步复制、异步复制的数据可靠性、数据一致性、全局事务一致性情况如下表所示。

特性

组复制

半同步复制

异步复制

数据可靠性

★★★★★

★★★

数据一致性

保证主备数据一致性

不保证

不保证

全局事务一致性

支持

不支持

不支持

数据可靠性

组复制的数据强可靠性来源于Paxos协议的多数派原则,即当多数派收到事务的Binlog后,事务才能在各节点提交。这保证了在多数派可用的情况下,任何节点故障都不会导致数据丢失。

例如,5个节点的集群,3个节点收到Binlog,2个节点未收到Binlog,此时有2个节点故障:

  • 如果故障的2个节点是收到Binlog的节点,那至少还有1个节点上有数据。

  • 如果故障的2个节点是没收到Binlog的节点,那至少还有3个节点上有数据。

说明
  • 多数派指集群中超过半数的节点。

  • 少数派指集群中少于半数的节点。

数据一致性

在组复制中,事务总是先传输到集群中其他节点,然后写入Binlog文件,这保证无论主节点在什么时刻发生故障,重新启动后数据都不会比集群选出的新主节点多。旧主节点故障重启后,能够自动加回集群,拉取它缺失的Binlog,就能够获得最新的数据,不会导致主备节点数据不一致。

而在传统主备复制模式中,事务是先写入Binlog文件,然后传输到备节点。这样,如果主节点在写入Binlog后,传输数据到备节点之前发生故障,重新启动后数据就会多于备节点。如果此时备节点已经被切换成新主节点,就会出现主节点数据少于备节点,主备节点数据不一致的情况。

全局事务强一致性

组复制具备集群的节点间的数据全局强一致读和全局强一致写能力,并且可根据业务需要修改group_replication_consistency参数来调整读写的一致性等级:

  • 设置备节点的强一致读能力:可以在备节点上设置Session级别的group_replication_consistency参数为BEFORE,然后执行查询语句。此时,备节点会等到主节点上所有先于此查询语句的事务应用完成后,再执行这条查询语句。这样在备节点和主节点上就会读到一致的数据。

  • 设置主节点的强同步写能力:可以在主节点上设置Session级别的group_replication_consistency参数为AFTER,然后执行写事务,这个事务会等到所有节点应用成功后,再在主节点上返回提交成功的消息到客户端。

组复制的部署模式

image

如上图所示,组复制支持单主和多主两种部署模式:

  • 多主模式(Multiple Leader):

    集群中所有节点均可读可写。多主模式的组复制主要用于扩展实例的写能力。它依赖Paxos协议的多点写入能力,辅以行级别的冲突检测,能够保证所有节点收到数据的顺序一致,实现多写。

    但它的严重缺陷是,在多数派可用的情况下,任何节点的故障都会导致集群的抖动(短时间不可用)。

  • 单主模式(Single Leader):

    集群中只有一个节点可以写入数据,其他节点只能读不能写。单主模式的组复制依赖Paxos Single Leader实现,在扩展读能力、提高数据可靠性的同时,保持了高可用性:

    • 当备节点故障时,只要多数派可用,就不会影响集群的可用性。

    • 当主节点故障时,集群能够根据Paxos协议,在保证数据强一致性的情况下自主进行主备切换。

    RDS MySQL提供了单主模式的组复制实例,并对只读节点做了优化,在保证数据的高可靠和强一致的同时,提供了更平滑的性能。

组复制的架构

image

如上图所示,在MySQL的Server层和Replica层之下,组复制的架构分为三层:

  • 组复制层(Group Replication Logic Layer):在单机MySQL的Server层之下,组复制增加了组复制层,该层通过钩子(HOOK)与Server层相连,负责向组通讯层发送、接收并回放事务。

  • 组通讯层(Group Communication System Layer):与XCom层共同实现组复制层与集群的通讯。该层除了负责消息的传递,还负责故障检测和集群成员的管理。

  • XCom层(Paxos Layer):基于Paxos协议实现,与组通讯层共同实现组复制层与集群的通讯,以及消息传递的全局有序性和集群成员的角色切换。它能够保证所有节点收到数据的顺序一致,同时保证只要多数派可用,数据就不会丢失。

XCom层

Paxos协议

Paxos协议在组复制中的作用主要有以下两点:

  • 确保集群中各节点收到数据的顺序一致,这是多主模式实现的基础。

  • 确保多数派收到数据后,事务才能提交。这对数据可靠性很重要,可以保证只要多数派可用,数据就不会丢失。

在Paxos协议中,使用锁的方式来实现节点间顺序一致性,这种方式存在一定的效率问题,并且还存在着负载不均衡的问题。在工程实现过程中,MySQL的XCom层基于Paxos的变种协议,Mencius协议。这是一种使用轮询的方法实现的Leaderless Paxos协议,能够有效提升节点间的负载均衡。

多主模式实现原理

image

多主模式基于Mencius协议。如上图所示,在Mencius协议中,集群中每个节点都会主动和其他节点建立连接,领导一个单主的Paxos组。对于n个节点的集群,会形成n个互不干预的Paxos组。每个组中只允许Leader节点发送数据,当多数派收到数据后,视为数据发送成功。当一个节点的客户端收到数据时,会使用自己领导的Paxos组发出数据,这个发送动作是串行的,因此单个组内的数据顺序是一致的。

多个组在发出数据时,为了保证顺序性,使用轮询处理机制。也就是说,XCom层在接收到多个Paxos组的数据后,必须按一个预先约定好的顺序传给组复制层。如上图所示,数据必须按照 (1,1)、(1,2)、(1,3) 这样的顺序发送给组复制层。

当一个节点发现比自己的顺序号靠后的数据已经完成了Paxos过程,并且自己的这个位置上没有数据要发送时,就会广播一个Noop,通知其他节点自己的这个顺序号可以跳过。每一个节点必须等顺序在自己前面的一个节点发出数据或发出Noop后才能发出数据。当某个节点故障或抖动时,它既不能发出数据又不能发出Noop,在故障恢复或节点抖动期间,就会导致后续节点的数据无法发出,集群将完全不可用。这是多主模式的一个严重缺陷。

说明

图中(m,n)表示第n个组发出的第m条数据。例如,(2,1)表示第1个组发出的第2条数据。

单主模式实现原理

上文中提到的多主模式的缺陷可以优化,但无法从根源上避免。为此MySQL推出了组复制的单主模式,来解决少数派故障对集群可用性的影响。

image

上图是单主模式的XCom架构,由于只有一个节点可写,只需激活一个Paxos组。接收方的XCom在轮询数据时,会自动忽略其他Paxos组。这样,只要多数派可用,Paxos就能正常发送数据,集群的可用性不受影响。

image

在单主模式下,备节点不会发送事务数据,但有时需要发送一些集群管理信息。备节点在发送数据时,必须向主节点请求一个发送信息的位置,如上图中的 <3, 1>,并使用这个位置向全集群发送自己的数据。这种发送方式效率低、时延高,但由于集群管理信息发送的频率很低,并不会对性能造成影响。

组复制层

image

组复制层的主要工作是向集群发送、接收并回放事务,其在主节点和备节点上的工作原理如下:

  • 在主节点上:当一个事务在主节点进入提交阶段时,事务的Binlog会先被传到XCom层中,发送给其他节点。当确认多数派收到事务后,会对事务进行冲突检测,如果成功则写入Binlog文件并提交,如果失败则回滚事务。

  • 在备节点上:当一个事务被多数派接收后,会由XCom层传给组复制层,进行冲突检测。如果成功则事务会被写入Relay Log,随后被Applier线程应用,如果失败则此事务的数据会被直接丢弃。

冲突检测

  • 场景

    组复制在以下两种场景中,都需要将不同节点的事务在同一个节点上执行,因此需要对不同节点间的事务进行冲突检测,以确保它们的修改没有冲突:

    • 多主模式下,所有写操作都需要冲突检测。

    • 单主模式下,如果发生切主,并且新主节点在应用完旧主的Relay log前,就执行写事务,也需要冲突检测。

  • 原理

    在组复制中,使用数据行的主键哈希值来进行行级别的冲突检测。每个节点都会维护一个事务认证信息数组,这是一个哈希数组,它的key是数据行的哈希值,它的value是最近修改这一行的事务的GTID与此事务在源节点提交时的gtid_executed(源节点上所有提交过的事务的gtid集合)的并集。

    一个事务在源节点准备提交时,除了发送自己修改的数据到其他节点外,还会发送一个gtid 集合,这个集合是该事务提交时,源节点上的gtid executed(源节点上所有提交过的事务的gtid集合)。这个集合标识了当前事务提交前,源节点上哪些事务已经完成提交,这里将其称为提交集合。

    同时,集群内的所有节点(包括源节点),都会使用当前事务修改的所有数据行(下文简称为相关行)的哈希值作为key,到认证信息数组中读取value,并将读到的这些value合并为一个gtid集合。这个集合标识了当前事务提交前,哪些事务必须完成提交,这里将其称为依赖集合。

    在当前事务提交或写入Relay Log前,需要将上述两个集合进行比较:

    • 如果提交集合包含了依赖集合,则说明所有之前修改过相关行的事务均已提交。此时冲突检测成功,源节点上需要将当前事务写入Binlog 并提交,其他节点上需要将当前事务写入Relay Log。

    • 如果提交集合没有包含依赖集合,则说明有之前修改过相关行的事务未提交。此时冲突检测失败,源节点上需要回滚当前事务,其他节点上需要丢弃Relay Log。

    认证信息数组还必须及时清理无用数据,以节省内存空间。当一个事务在所有节点上都被执行之后,任何其他事务都不会跟它冲突了,此事务修改的所有行都可以从认证信息数组中清理掉。在组复制中每60秒会清理一次已执行的事务的数据。

AliSQL对组复制稳定性的优化

组复制的稳定性在引入单主模式后有了较大的改善,但是在一些场景下,仍存在稳定性问题。当备节点的延迟较大时,会有大量的事务无法及时应用,这会导致认证信息大量积累,会有以下两方面影响:

  • 大量的认证信息会占用很多内存,导致实例内存溢出(OOM)。

  • 认证信息堆积可能导致清理的代价变高,影响实例的稳定性。

AliSQL对认证信息的清理做了两点优化:

  • 在主节点上:无论何种情况,认证信息数组都不会被使用,因此主节点上不再保留认证信息数组,可以消除认证信息对主节点的资源和稳定性的影响。

  • 在备节点上:只有当group_replication_consistency参数设置为EVENTUAL时需要保留认证信息。因为在这个配置下,备节点在切为主节点后会立刻对外提供服务,不会等待Relay Log回放完成,有可能产生数据操作的冲突。这种不等新主节点应用完成,就对外提供服务的方式在日常生产中并不常用。在禁止这种行为后,可以减少备节点的认证信息保留量,节省备节点的内存开销,提升集群的稳定性。