作者 罗小波 · 数据库技术专家
出品 沃趣科技
MySQL Group Replication(MGR)自问世以来,一直是大家技术分享、技术讨论的热点,虽然在MySQL 5.7版本中,MGR 还不尽完善,但其带来的新特性着实让大家眼馋,所以,一些互联网大厂纷纷对其进行了修修补补,然后美美地品尝到了第一口螃蟹的味道。然而,这个时代的变化速度让我有些应接不暇,在MySQL 8.0中,MGR已经具备了非常优秀的功能特性、可控性、稳定性,性能也有大幅提升。
在这大背景下,我们当然也不能稳坐泰山,因此我们完整翻译与整理了MySQL 8.0手册中Group Replication的内容,本着开源精神,从今天开始,我们将以系列文章的形式,定期分享给大家。由于其中相当一部分内容并不是直译的,带入了很多个人的大白话理解,也参考了业界其他一些大牛的文章。加之个人知识的局限性,其中的一些细节理解可能有误,还望业界各位大牛批评指正,我们将会认真修订每一个错误,尽量使得这份文档的内容能够具备更高的传播价值。在系列文章推送结束之后,我们最后会汇总整理为一份完整的文档并以PDF文件之类的形式分享出来。
概述
- 本文基于MySQL 8.0.17版本和Oracle MySQL 官方的8.0参考手册进行整理,详细介绍了MySQL Group Replication(MGR)的安装、配置和监控等。MGR是通过一个MySQL Server插件实现的,不是MySQL原生内置的(注:该插件是Oracle MySQL官方推出的插件,非第三方插件),它可用于创建弹性、高可用性、高容错的复制拓扑。
- MGR可以运行在支持自动选主的单主模式下,在单主模式下,只有一个Server(服务器)节点能够接受更新操作。也可以运行在多主模式下,多主模式下所有Server节点都能接受更新操作,即使多个Server节点同时发起更新请求, MGR的冲突认证机制也能够保证它们正常执行。
- MGR有一个内置的组成员服务,它使得在任何给定的时间点内组的视图能够保持一致,并为组内的所有成员提供服务。给定Server可以根据需要使其脱离、或加入组内,组的视图也会随着组成员的脱离组、加入组进行相应进行更新。有时,Server可能会意外脱离组,在这种情况下,故障检测机制会自动检测到并通知组该视图已更改。这些变更操作都是自动的,无需人工操作。
PS:为避免混淆,我们对一些术语的理解与称呼做了一些统一(后续文章将删除这部分内容)。
- 复制组内的单个MySQL Server(包括组中的存活节点或非存活节点),我们将其称为"组成员"。对于组内的可读写成员,在组内是"primary"角色,因此,我们将其称为"主要节点"(同时也为方便与主从复制拓扑中的"主节点"或"主库"做区分),对于只读成员,在组内是"secondary"角色,我们将其称为"辅助节点"(同时也为方便与主从复制拓扑中的"从节点"或"从库"做区分);复制组内如果不强调单个MySQL Server的,我们将其称为"组"或"复制组"。
- 非复制组内的MySQL Server,为了便于与组成员的概念做区分,我们将其称为"MySQL Server"或"Server"。
- MySQL Group Replication,用于描述可能需要强调MySQL的语境,表示组复制插件,下文中简称为"MGR";Group Replication,用于不强调MySQL的语境,下文中称为"组复制";主从复制拓扑中如果不强调主从角色或者与主从复制无关的数据库Server(未加入主从复制拓扑或者已经脱离了主从复制拓扑的数据库Server),则称为"MySQL Server"或"Server"。
- 用户自定义函数,下文中称为"UDF"。
- 在组中有新的Server加入组时,我们将新加入的Server称为"joiner节点"(也可称为"recipient"节点),将为joiner节点提供状态传输的组成员称为"donor节点"。
- 准备加入组,但是还未执行加入组的过程中的数据库Server或者已经完全脱离组的成员,称为"MySQL Server"或"Server"。
- 怀疑(suspicion):指的是当某个成员可能出现故障(或者网络不可达),组中其他的健康的成员对该故障成员状态的一致性协商判定(多数派成员和少数派成员都可以对其他成员产生怀疑,但需要多数成员达成共识的怀疑方有效,否则无效),即,大家都同意该成员可能出现故障了(只有基于多数派成员达成共识的情况下,才能够正常触发重新配置组成员资格)。
- 失联(unreachable):指的是当本地故障检测器怀疑某个给定的成员不可访问时(例如,由于非自愿与组断开了连接时),就认为该成员失联,并将该成员的状态显示为"UNREACHABLE"。
1-组复制的背景信息
创建容错系统最常见的方法是对组件进行冗余,换句话说,在系统正常运行过程中,删除某个组件之后系统应该按照预期正常继续运行,而不会对系统的可用性造成影响。这就带来了一系列挑战,要实现此类系统,与不具备容错能力的系统相比,这会将系统的复杂性提高到一个完全不同的水平。具体来说,复制拓扑中需要管理和维护多个组成员,而不仅仅是一个组成员,此外,由于组成员之间需要协同工作,因此,还必须要解决分布式系统中的其他几个典型的分布式问题,例如:网络分区或脑裂等场景。
因此,最大的挑战是将数据库和数据复制的逻辑与以一致且简单的方式协调多个组成员的逻辑结合起来。换句话说,让多个组成员就系统的运行状态、系统中每次有数据变更时状态达成一致。这可以概括为让组成员在发生任何数据库状态变化时都达成一致(从而使得它们看上去像是作为一个单一的数据库运行的)或者所有组成员能够收敛到一个最终一致性状态。这意味着它们都需要作为(分布式)状态机运行。
组复制 为分布式状态机复制提供了强大的组成员间的协作能力。当所有Server节点都属于同一个组时,它们通过分布式状态机自动协调自己。组可以运行在单主模式下,也可以运行在多主模式下,在多主模式下,即使在所有组成员中发起并行更新操作。这种分布式协调能力,也可以使得这些并发请求能够正常执行,而不至于产生数据冲突。
在组复制中要提交事务,组内的大多数成员必须就全局事务序列中给定的事务顺序达成一致。每个组成员单独决定是否提交或中止事务(每个组成员各自按照相同的顺序对事务进行冲突认证检测),但是所有组成员都最终都会做出相同的决定。如果存在网络分区,导致出现了组成员无法达成一致意见的分裂,则在解决网络分区问题之前,系统会发生阻塞(不会继续向前执行)。因此,组复制 支持一种内置的自动裂脑保护机制来防止发生网络分区时,可能导致数据不一致的问题。
以上提到的所有这些特性,都是由组通信系统(GCS)协议提供支持的。它们提供了故障检测机制、组成员服务以及安全且完全有序的消息传递。所有这些属性对于创建一个可确保跨组成员且能够一致地复制数据的系统中非常关键。该技术的核心是Paxos算法的实现。它充当着组通信引擎的角色。
1.1. 复制技术
在深入了解组复制的技术细节之前,本节将先对一些背景概念以及工作原理进行简单概述,提供一些上下文信息,以帮助理解与区分组复制和经典的异步复制之间的区别。
1.1.1. 主从复制
传统的MySQL复制提供了一种简单的主从复制方法。其复制拓扑中,由一个主库加上一个或多个从库组成。在主库中执行数据更新的事务,并提交事务。将数据变更记录到主库的binlog文件中,稍后(因此称为异步)将这些binlog发送到从库中,以便在从库中重新执行数据变更语句(基于statement的复制中)或应用数据变更的row格式日志(在基于row格式的复制中)。它是一个shared-nothing的系统,默认情况下,所有的MySQL Server都拥有完整的一份数据副本。异步复制的流程图如下。
另外,基于异步复制的基础上进行改进的半同步复制,它为主从复制协议添加了一个同步的步骤(指的是主库同步binlog到从库的步骤,并不包括重新执行或应用的步骤)。这意味着主库在提交事务时需要等待从库确认它已经收到该事务的binlog(从库收到binlog之后,会返回一个ACK包给主库,主库通过这个ACK包来确认从库是否有收到事务的binlog)。只有这样,主库才会将事务进行提交(这里指的是存储引擎层的提交,而非客户端发起事务提交的语句),半同步复制的流程图如下。
如上所示的两张图中,你可以看到经典的异步MySQL复制协议以及基于异步复制的半同步复制变种的数据复制流程,以及MySQL Server之间、客户端应用程序和MySQL Server之间相互交换消息的过程(留意蓝色箭头所指的方向)。
1.1.2. 组复制
组复制(Group Replication)是一种可用于实现容错系统的技术。复制组是一组MySQL Server,每个Server都有自己的数据完整副本(shared-nothing复制方案),并通过组通讯消息传递进行相互交互。组通信层提供一组保证,例如:保证消息的原子性和消息在所有组成员的整体顺序一致。这些属性非常强大,可以转换为非常有用的抽象概念,可以采用这些抽象概念来构建更高级的数据库复制解决方案。
组复制建立在这些属性和抽象概念的基础上,并实现了基于主从复制协议的多主模式(多主模式下任意组成员都可更新数据)。复制组由多个MySQL Server组成,组中的每个成员可以在任何时候独立执行事务。但是,所有读写事务(不包含只读事务)只有在得到复制组的批准之后才能提交(通过冲突认证检测之后才能提交)。换句话说,对于任何读写事务,都是由组来决定它是否可以提交,而不是由发起事务提交的原始组成员单方面决定。如果是只读事务,则不需要在组内进行协调,可以立即提交(即,发起事务的组成员可以单方面决定)。
当读写事务准备在原始组成员上提交时,该成员将自动广播写入值(数据变更的行)和相应的write set(发生数据变更的行的惟一标识符)。因为事务是通过原子广播发送的,所以组中的所有成员要么都接收事务,要么都不接收事务。如果组中的所有成员都收到了该广播消息(事务),那么它们都会按照与之前发送事务的相同顺序收到该广播消息。因此,所有组成员都以相同的顺序接收事务的写集,并为事务建立全局顺序。
但是,在不同组成员上并发执行的事务之间可能存在冲突。这种冲突是通过检查和比较两个不同并发事务的write set来验证的,这个过程称为认证。在认证期间,冲突检测在行级别执行的:如果在不同组成员上执行的两个并发事务更新了同一行数据,则存在冲突。根据冲突认证检测机制判断,按照顺序,第一次提交到所有组成员上的事务继续执行提交(成功),第二次提交到所有组成员上的事务被中止(失败),因此第二个事务在事务发起的原始组成员上执行回滚,组中的其他成员对该事务执行删除(丢弃接收到的write set,不会写入relay log中)。例如,如果t1和t2在不同的位置同时执行,并且修改了同一行数据,并且t2在t1之前被排序(t2在前,t1在后),那么t2将赢得冲突(t2提交,t1回滚)。这实际上是一个分布式先提交获胜(当选)规则。请注意,如果两个事务经常发生冲突,那么最好将这两个事务放在同一个组成员中执行,这样它们在本地锁管理器的协调下将都有机会提交成功,而不至于因为处在两个不同的组成员中由于冲突认证而导致其中一个事务被频繁回滚。
对于一些事务,如果不破坏数据的一致性和有效性,则组复制允许组成员偏离商定的事务顺序,组复制是一个最终一致性的系统,这意味着只要通过了冲突认证检测的事务,不要求在所有组成员的事务提交顺序都完全一致,只需要保证组复制内的所有成员最终具有相同的数据内容即可。例如:在多主模式下,本地事务可能会在冲突认证通过之后立即提交(即使全局序列中存在着更早的还未应用的远程事务也是如此 ,但是要注意,只允许通过了冲突认证检测的事务先提交,冲突认证不通过的本地事务会被回滚),在单主模式下,在写节点上,并发的且通过了事务冲突认证的事务可能以与组同意的且与全局顺序不相同的顺序提交(但在读节点上,事务总是以全局顺序提交事务)。
下图描述了组复制协议,通过将其与MySQL异步复制(甚至是MySQL半同步复制,可以参考上文中的2张图)进行比较,您可以看到一些差异。为了清晰起见,这幅图中省略了组复制的一些基本的分布式共识(达成一致)信息和Paxos相关的信息。
1.2. 组复制使用场景
通过组复制,可以将系统的状态复制到一组MySQL Server中并创建一个具有冗余的容错系统。当组内有成员出现故障时,只要不是全部或大多数组成员(组内超过半数的成员)出现故障,则系统仍然可用。根据故障组成员数量的不同,该组可能会降低性能或可伸缩性,但它仍然可用。组成员故障是独立的。它们由一个组成员服务进行跟踪监控,该服务依赖于一个分布式故障检测器,该检测器能够在任何组成员脱离组时发出信号(无论是出于自愿还是由于意外停机,都会发出这样的信号)。当故障成员恢复正常或有新的MySQL Server加入组时,由一个分布式恢复程序来控制整个过程,以确保当Server加入组时,它们会自动更新组成员视图等相关信息。而且多主更新的特性可确保即使在单个组成员发生故障时,也不会阻塞更新。总之,组复制能够保证数据库服务是连续可用的。
要注意:在组内有成员发生崩溃的时候(少数成员发生崩溃),尽管数据库服务本身是可用的,但必须自行将连接到故障组成员中的那些应用客户端连接重定向到另一个健康的组成员,或者通过应用程序相关的自动故障转移程序将故障自动转移到到另一个健康的组成员中。这种在组成员发生故障时的客户端访问转移不是组复制需要解决的问题。通过连接器、负载均衡器、路由器或某种形式的中间件更适合处理这个问题。总之,组复制 能够提供一个高可用性、高弹性、高可靠的MySQL服务。
下面是一些组复制的典型用例。
- 弹性复制:需要非常灵活的复制基础设施的环境,其中MySQL Server的数量必须动态增加或减少,并且在增加或减少Server的过程中,对业务的副作用尽可能少。例如,云数据库服务。
- 高可用分片:分片是实现写扩展的一种流行方法。基于 组复制 实现的高可用分片,其中每个分片都会映射到一个复制组上(逻辑上需要一一对应,但在物理上,一个复制组可以承载多个分片)。
- 替代主从复制:在某些情况下,使用一个主库会造成单点争用。在某些情况下,向整个组内的多个成员同时写入数据,对应用来说可能伸缩性更强。
- 自治(自动化)系统:此外,你可以利用组复制内置的自动故障转移、数据在不同组成员之间的原子广播和最终数据一致性的特性来实现一些运维自动化(或者说简化运维成本,这一点上文中已经描述过了)。
1.3. 单主模式和多主模式
组复制可以在单主模式或多主模式下运行。由系统变量group_replication_single_primary_mode指定,该变量在所有组成员中必须设置为相同的值(同一个组中,不能将组的成员部署在不同的模式中,例如:一个成员配置为多主模式,而另一个成员配置为单主模式)。ON表示单主模式,也是默认模式,OFF表示多主模式。
在组复制运行时,不能手动更改系统变量group_replication_single_primary_mode的值(也就是说不能在单主模式和多主模式之间动态切换)。但从MySQL 8.0.13开始,可以使用group_replication_switch_to_single_primary_mode()和group_replication_switch_to_multi_primary_mode() UDF在组复制运行时将组从一种模式切换到另一种模式(使用group_replication_switch_to_single_primary_mode()函数指定切换到单主模式时,可以不指定组成员UUID,会自动选择一个健康的组成员作为新的主要节点,也可以手工在组中挑选一个健康成员的UUID作为参数,指定为主要节点,该函数可以在单主模式下使用,但不会有任何效果也不会报错。只有在多主模式下使用时才有效,表示将多主模式切换为单主模式;group_replication_switch_to_multi_primary_mode() 函数指定切换到多主模式时,不需要指定UUID参数,否则会报错,在多主模式下也可以正常使用该函数,但也不会有任何效果也不会报错。只有在单主模式下使用才有效果,表示将单主模式切换为多主模式)。使用这些UDF函数管理更改组模式的过程,可以确保数据的安全性和一致性。在早期版本中,要更改组的模式,必须先停止组复制并更改所有成员上的group_replication_single_primary_mode系统变量的值。然后将组进行完全的重新引导(由设置了系统变量group_replication_bootstrap_group=ON的Server引导),以使得新操作配置更改生效。注意:不需要重新启动MySQL Server进程,只需要将组复制重新引导即可。
无论部署的模式如何,组复制都不会处理客户端请求的故障转移。该工作必须由中间件框架(如MySQL Router 8.0、代理、连接器或应用程序本身)来处理。
PS:虽然从MySQL 8.0.13版本开始,从多主模式切换为单主模式时,不需要停止组复制,但是,需要将系统变量在所有节点上设置为ON(使用UDF做在线切换可以自动修改,不需要人工参与)
1.3.1. 单主模式
在单主模式下(group_replication_single_primary_mode=ON),组中只有一个成员设置为读写模式(称为"主要节点")。组中的其他所有成员都设置为只读模式(称为"辅助节点")。主要节点通常是引导组启动的第一个MySQL Server。加入组的所有其他MySQL Server将学习主要节点(从主要节点同步数据),并自动设置为只读模式。
在单主模式下,组复制强制约束组内只有一个成员可以执行写操作,因此与多主模式相比,一致性检查可以不那么严格,而且不需要特别小心地处理DDL语句。系统变量group_replication_enforce_update_everywhere_checks用于启用或禁用组的严格一致性检查功能。当以单主模式部署或将组模式更改为单主模式时,必须将该系统变量设置为OFF,以关闭严格一致性检查。
被指定为主要节点的成员在如下一些情况下可能发生角色变换:
- 如果现有的主要节点脱离了该组,不管是自愿还是意外脱离组,都会触发组自动选出一个新的主要节点。
- 您可以使用group_replication_set_as_primary() UDF来指定一个成员(指定一个健康的组成员的UUID)作为新的主要节点。
- 如果使用group_replication_switch_to_single_primary_mode() UDF 将多主模式下运行的组更改为单主模式运行,则会自动选择一个新主要节点,或者可以通过使用该UDF指定一个新主要节点(指定一个健康的组成员的UUID)。
一个组中必须所有组成员都运行在MySQL 8.0.13或更高版本时才能使用UDF。当自动选择或手动指定一个组成员为新的主要节点时,它会被自动设置为读写模式,而其他组成员作为辅助节点,它们会被自动设置为只读模式。关于单主模式下重新选举主要节点的过程如下图:
当一个组成员被选中或指定称为新的主要节点时,可能存在着一些在旧的主要节点中已经提交但在新主要节点上还未来得及应用的数据更改。在这种情况下,在新主要节点追赶上旧主要节点中的最新数据之前,在新主要节点中新发起的读写事务可能会因为与之前的事务冲突而被回滚,而新主要节点中心发起的只读事务也可能会读取到陈旧的数据。组复制的流量控制机制能够减小快成员和慢成员之间的事务差异量,如果激活流控机制并进行适当的调优,则会降低发生这种情况的几率。从MySQL 8.0.14版本开始,还可以使用系统变量group_replication_consistency来配置组的事务一致性级别,以防止这个问题。在新主要节点上设置该系统变量为"BEFORE_ON_PRIMARY_FAILOVER"值(或任何更高的一致性级别值)时,新的事务请求在新主要节点中会被保留(但不应用),直到新主要节点应用完成了落后于旧主要节点的数据为止。有关事务一致性的更多信息,请参见"4.2. 事务一致性保证"。如果一个组没有使用流量控制与事务一致性保证,那么在将客户端应用程序重新路由到新的主要节点之前建议等待新主要节点应用完成与复制相关的中继日志(这里指的是已经通过冲突认证检测,但在relay log中还未来得及回放的日志)。
1.3.1.1. 选主算法
自动选主的过程中每个成员都会参与查看组的新视图、对潜在的新主要节点(备选主要节点)进行排序、并选择最合适的组成员作为主要节点。每个成员都在本地根据MySQL Server发行版中的选主算法各自做出自己的决定。由于要求所有组成员必须最终达成一致决策,所以如果其他组成员运行的MySQL Server版本较低,则成员将调整其选主算法,以便与组中MySQL Server版本最低的成员与组中其他的高版本成员具有相同的行为。
各成员在选举主要节点时考虑的因素,按照顺序,依次如下:
- 第一个要考虑的因素:哪些组成员运行的MySQL Server版本是最低的。如果所有组成员都运行在MySQL 8.0.17或更高版本上,则首先根据其发行版的补丁版本号对成员进行排序。如果任何成员运行在MySQL Server 5.7或MySQL 8.0.16或更低版本上,则首先按其发行版本的主版本号对成员进行排序(忽略补丁版本号)。
- 第二个要考虑的因素:如果组中有多个成员运行在最低版本的MySQL Server上,则要考虑的第二个因素是每个成员的成员权重设置值,该值由每个成员上的系统变量group_replication_member_weight指定(有效值为0~100的数字,默认值为50)。如果组中的任何成员运行在MySQL Server 5.7上,此时对该组成员忽略第二个考虑因素(因为系统变量group_replication_member_weight是8.0版本引入,5.7版本不支持)。如果该组成员支持系统变量group_replication_member_weight,则,可以使用该系统变量对硬件性能较好的组成员增加此权重,以增加被选为主要节点的优先级顺序,或者确保在主要节点的例行维护期间将故障转移到指定的成员(调低权重值可以降低其排序顺序,调高权重值可以增加其排序顺序)。
- 第三个要考虑的因素:如果不止一个成员运行在低版本的MySQL Server上,且其中不止一个成员的权重值无效(不支持系统变量group_replication_member_weight),则就需要考虑第三个因素,即按照每个成员的UUID号进行排序(按照每个组成员的server_uuid系统变量值排序),选择具有最小UUID值的成员作为主要节点。该因素是最后一个可靠的决定因素,因为它可以在第一和第二因素不生效时,使所有的组成员达成最终一致的决策(按照相同的顺序排序UUID并选择最小值,所有组成员能够达成一致,因为组复制是从5.7版本引入的,且UUID是5.6就已经引入,所有组复制成员都支持UUID)。
1.3.1.2. 找出主要节点
要在单主模式的拓扑中找出当前的主要节点,可以使用performance_schema.replication_group_members表中的MEMBER_ROLE列值来判断,MEMBER_ROLE列值为PRIMARY的组成员即为当前组中的主要节点。replication_group_members表的内容查询示例如下:
代码语言:javascript复制mysql> SELECT MEMBER_HOST, MEMBER_ROLE FROM performance_schema.replication_group_members;
------------------------- -------------
| MEMBER_HOST | MEMBER_ROLE |
------------------------- -------------
| remote1.example.com | PRIMARY |
| remote2.example.com | SECONDARY |
| remote3.example.com | SECONDARY |
------------------------- -------------
也可以使用状态变量group_replication_primary_member值进行查看,但是,该变量已经被弃用,计划在将来的版本中删除,所以,不再建议使用该方法来查看组中的主要节点。
代码语言:javascript复制mysql> SHOW STATUS LIKE 'group_replication_primary_member'
1.3.2. 多主模式
在多主模式下(group_replication_single_primary_mode=OFF),没有组成员具有特殊的角色(所有组成员都是主要节点的角色)。任何与其他组成员兼容的成员在新加入组时都会被设置为读写模式,并且允许处理写事务,即使这些写事务是并行请求的。
如果组中的一个成员停止接受写事务,例如:在某个组成员发生崩溃的情况下,连接到它的客户端可以被重定向、或者故障转移到组中健康的且处于读写模式的任何其他成员上继续访问。但组复制本身不处理客户端故障转移,因此需要使用中间件框架(如MySQL Router 8.0、代理、连接器或应用程序本身)来完成客户端的路由切换(故障转移)。下图是在多主模式下,某一个组成员发生crash之后,客户端故障转移到其他组成员的过程示意图。
组复制 是一个最终一致性的系统。这意味着,一旦写入的流量减慢或停止,所有组成员都最终会具有相同(一致)的数据。当组中不断有写入流量时,新写入的事务在某些成员上(一些服务器性能较好的成员)可以先于其他成员进行持久化,特别是在某些成员的写吞吐量低于其他成员的情况下,这就可能会导致写吞吐较低的成员上可能读取到陈旧的数据。在多主模式下,速度较慢的成员还可能导致有大量积压的事务等待验证和应用,从而放大了导致事务发生冲突和认证失败风险。要减缓这些问题的发生,可以激活与调优组复制的流量控制机制,以最小化快成员和慢成员之间的事务差异。有关流量控制的更多信息,请参见"6.2. 流量控制"。
从MySQL 8.0.14版本开始,如果想为组中的每个事务提供一致性保证,可以使用系统变量group_replication_consistency来实现。可以选择适合您的组的工作负载设置以及数据读写优先级,同时考虑到提高一致性所需的数据同步对组性能的影响。还可以在会话级别为各个会话设置该系统变量,以保护对并发非常敏感的事务。有关事务一致性的更多信息,请参见"4.2. 事务一致性保证"。
1.3.2.1. 事务检查
当组运行在多主模式下时,将对事务进行以下严格的一致性检查,以确保它们与该模式兼容:
- 如果事务是在SERIALIZABLE(可序列化,即串行) 隔离级别下执行的,那么当该事务与组执行同步时,该事务将提交失败。
- 如果事务对具有外键约束的表执行操作,那么当该事务与组执行同步时,该事务将提交失败。
是否执行一致性检查,由系统变量group_replication_enforce_update_everywhere_checks控制。在多主模式下,通常应将该系统变量设置为ON(即,启用一致性检查),但是也可以选择将该系统变量设置为OFF(即,停用一致性检查)。但在单主模式下,必须将此系统变量设置为OFF(即,必须停用一致性检查)
1.3.2.2. 数据定义语句(DDL)
在多主模式下的组复制拓扑中,执行DDL语句时需要小心。
- 在MySQL 8.0中引入了对原子DDL(atomic Data Definition Language, DDL)语句的支持,其中完整的DDL语句要么作为单个原子事务提交,要么作为单个原子事务回滚。但是,DDL语句会隐式地终止(提交)当前会话中活跃的任何事务。这意味着DDL语句不能在另一个事务内执行(例如,不能在start transaction…commit等事务控制语句中间执行,也不能与同一事务中的其他语句组合执行)。
- 组复制是基于乐观锁的复制范式,在这种范式中,语句被乐观地执行,并在必要时进行回滚。每个组成员中写入的流量都会先执行,而不是先保护组协议(保护一致性)。因此,在多主模式下复制DDL语句时需要格外小心。如果对同一个对象使用DDL语句修改模型时也需要使用DML语句对执行DDL的对象的数据进行修改,则在DDL语句未被其他组成员执行完成之前,对同一个对象执行的DML语句最好放在与发起DDL语句的同一个组成员上执行(这样可以用每个组成员的本地锁机制来保证两者都可以准确无误地执行完成)。否则,在操作被中断或只部分执行完成时,可能会导致数据不一致。如果以单主模式部署组复制,则不会出现此问题,因为所有的数据和数据模型的修改都是通过同一个组成员(主要节点)执行的。
有关MySQL 8.0中原子DDL支持的详细信息,以及某些语句的最终复制行为,请参见连接:
https://dev.mysql.com/doc/refman/8.0/en/atomic-ddl.html
1.3.2.3. 版本兼容性
为了获得最佳的兼容性和性能,在组复制拓扑中,组中的所有成员都应该运行相同版本的MySQL Server。在多主模式下,这一点尤其重要,因为所有成员通常都以读写模式加入组。如果一个组内包含两个或以上不同MySQL Server版本的成员,那么可能存在一些成员与其他成员不兼容的情况,因为新版本可能支持一些其他旧版本成员不支持的功能特性,或者缺少其他成员拥有的功能特性(在新版本中废弃的特性)。为了防止这种情况,当新成员加入组时(包括升级和重启MySQL Server导致的成员重新加入组),将会对组的其他成员执行兼容性检查。
兼容性检查在多主模式中特别重要。如果joiner成员运行的MySQL Server版本高于组中现有的其他成员的MySQL Server最低版本,则它将成功加入组,但处于只读模式(在单主模式中,joiner成员在任何情况下都默认为只读模式)。运行MySQL 8.0.17或更高版本的成员在检查兼容性时要考虑发行版的补丁版本。运行MySQL 8.0.16或更低、或MySQL 5.7版本的成员在检查兼容性时只考虑主版本号。
在以多主模式运行且具有不同MySQL Server版本的组中,MySQL 8.0.17或更高版本中组复制会自动管理成员的读写和只读状态。如果一个成员离开了组,则组中当前运行在最低版本的成员将自动被设置为读写模式。当使用group_replication_switch_to_multi_primary_mode() UDF将单主模式的组更改为多主模式时,组复制会自动将成员设置为正确的模式(这里指的是只读与读写模式)。如果成员运行的MySQL Server版本高于组中最低版本,则自动将其置于只读模式,而运行最低版本的成员则处于读写模式(为什么需要将最低版本设置为读写模式,高于最低版本的设置为只读模式,这可以类比主从复制拓扑中,从库的版本必须大于等于主库的版本,不能低于主库版本)。
有关组中的版本兼容性及其如何影响组在升级过程中的行为的完整信息,请参见"7.1. 在一个组中兼容不同版本的成员"。
1.4. 组复制服务
1.4.1. 组成员资格
复制组由一组MySQL Server构成。组具有一个唯一的名称,名称形式为UUID字符串。这个组内的成员是动态的,组成员可以随时脱离组(自愿或非自愿),也可以随时加入组。每当有Server加入或脱离组时,组的相关信息都会进行自动调整。
如果一个Server加入了组,则它会从组的现有成员中自动获取自身缺失的数据状态以便和组保持数据同步。如果一个组成员脱离了组,例如:例行维护时关闭了某个组成员,其余的组成员会发现该成员脱离了组,并自动重新配置组。
组复制 具有组成员资格服务,该服务定义组内哪些成员在线且是活跃成员。在线组成员列表被称为组视图。组中的每个成员都具有一个一致的视图,即表示在给定的时刻组中哪些成员是活跃成员。
组成员不仅在事务提交时必须达成一致,对于组视图的变更也必须达成一致。如果现有成员同意新的Server加入组,则组将被重新配置,以便将joiner节点集成到组中,这将触发组视图的变更。如果组成员脱离组(自愿和非自愿),该组将动态地重新配置,并触发组视图的变更。
在成员自愿脱离组的情况下,它首先启动动态组重新配置,在此期间,所有组成员必须(排除自愿脱离组的成员)就新的组视图达成一致。但是,如果某个组成员是非自愿地脱离了组,例如:因为意外宕机了或网络连接中断了,那么脱离组的成员就不能启动动态重新配置。在这种情况下,组复制的故障检测机制会在短时间内识别出该成员已经脱离了组,并建议将已脱离组成员排除在外并进行动态重新配置组。重新配置组需要得到组中大多数组成员的同意(这一点无论脱离组的成员是否是自愿都一样)。但是,如果此时组内不能达成一致,例如:由于组内活跃节点数量少于节点总数量的半数时,系统就不能动态重新配置组,这种情况下组会阻塞写访问以防止出现脑裂的情况。直到管理员人工介入处理。
在故障检测机制检测到其故障之前,或在重新配置组以删除该故障成员之前,允许组成员短暂离线,然后尝试重新加入组。在这种情况下,重新加入的成员可能会丢失它以前的状态(这里的状态指的是事务数据),如果此时其他成员向它发送了包含该成员崩溃前的状态的消息,则这就可能会导致一些问题(例如:可能导致数据不一致。如果这种状态下该成员参与了XCom的协商一致协议,则可能导致XCom在失败前后对同一轮协商做出不同的决策,也就是说对同一轮协商提供了两个不同的协商结果)。
- 为了解决这个问题,从MySQL 5.7.22及其之后的版本中(包括MySQL 8.0),当Server加入一个组时,会被赋予一个唯一的标识符。这使组复制能够察觉到同一Server的新化身(虽然具有相同的地址,但不同的化身具有不同的标识符)试图加入组时,而其旧化身仍然会作为成员列出。在通过重新配置组并删除旧的化身之前,将阻止新的化身加入组。如果通过系统变量group_replication_member_expel_timeout的设置增加了被怀疑(suspicion)出故障的成员在被驱逐出字之前允许返回组的等待时间,则只要被怀疑出故障的成员不是真的失败了,那么被怀疑的成员在这个等待时间内就可以尝试重新加入组。如果在被怀疑出故障的成员上执行了重启组复制,则该成员将成为新的化身,在怀疑超时之前(怀疑期时间内)不能重新加入组。
1.4.2. 故障检测
组复制 支持一种故障检测机制,该机制能够发现和报告哪些组成员是静默(没响应)的,并假定静默的组成员已经死机。总体上讲,故障检测器是一种分布式服务,它提供关于哪些组成员可能死机(怀疑死机)的信息。当组成员静音(不主动发信息,也不回复其他组成员发送的信息)时就会引发怀疑。当组成员A在给定的时间段内没有接收来自组成员B的消息时,将发生消息超时并引发怀疑(组成员A被怀疑)。稍后,如果组内其他所有的成员通过协商之后,都同意对该成员的怀疑可能是真实的(多数节点判定的结果),则组就会判定被怀疑的该成员确实发生了故障。这意味着组中其他成员能够通过采取协调一致的决策来将故障成员驱逐出组(被判定为发生故障的成员)。
如果某个组成员与组中的其他成员发生了网络隔离,那么它会怀疑组中其他所有成员都发生故障了。由于无法与组内的其他成员进行协商(因为仲裁成员数不足),它的怀疑无效。此时,它也无法执行任何本地事务(只读事务可以执行)。
1.4.3. 容错
组复制 建立在Paxos分布式算法的实现之上,以提供组成员之间的分布式协调。因此,它需要大多数组成员处于活动状态才能达到仲裁成员数,才能够做出决策。该要求会直接影响到系统在不影响自身和整体可用性的情况下能够容忍发生故障的成员数量。可以容忍发生故障的成员数量(假设为f个)和要求组内总成员数量(假设为n个)之间的关系为:n = 2 x f 1。
根据上述计算公式,可得知,如果要容忍至少一个组成员发生故障,那么,组内的总成员数量至少需要3个。因此,当一个组成员发生故障时,仍然有2个组成员是活跃成员,即活跃成员占多数(三分之二),此时,组内可通过仲裁机制自动驱逐故障成员并允许系统继续对外提供服务。但是,如果第二个组成员再发生故障(非自愿脱离组),则该组(剩下一个成员)会发生阻塞,因为缺少多数成员来做出合理的决策,所以无法自动恢复到能对外提供服务的状态,此时需要人工介入处理。
下面是对上述公式的一些计算值的小表格(通常,基于性能和维护成本的考虑,组内的成员总数量不建议超过7个,最大只能9个)。
组大小(组成员数量) | 多数(最小活跃节点数量) | 能容忍的故障节点数量 |
---|---|---|
1 | 1 | 0 |
2 | 2 | 0 |
3 | 2 | 1 |
4 | 3 | 1 |
5 | 3 | 2 |
6 | 4 | 2 |
7 | 4 | 3 |
8 | 5 | 3 |
9 | 5 | 4 |
PS:从上表中我们可以看到,如果要能容忍至少一个组成员发生故障而不影响组的可用性,则组大小有3和4可选,在这两个之间,最优方案为3,因为,为4的组合,最多也只能容忍一个组成员失败(但是成员数量却多了一个),性价比不如为3的组大小。能容忍2个成员失败的最优组大小为5,以此类推。
1.4.4. 可观测性
虽然MGR插件内置了很多自动化功能。但,有时可能需要了解幕后发生了什么。如果有需要,可以通过performance_schema下的表查询系统的整个状态(包括视图、冲突统计信息和服务状态等)。复制协议的分布式特性、以及组成员在事务和元数据上的一致性,要求组内的一些元数据和状态信息在组内所有成员间相互同步,这就使得检查组的状态等信息变得更加简单。你只需要连接到组内的任意一个成员中,并通过performance_schema下的相关复制状态信息表执行select语句进行查询,就可以获取到组相关的本地和全局信息。有关更多信息,请参见“3、组复制监控”。
1.5. MGR 插件架构
MGR 是一个MySQL插件,它基于现有的MySQL主从复制基础设施实现,利用了基于行的二进制日志记录和全局事务标识符(GTID)等特性。它与当前MySQL插件式框架集成,如:performance_schema或插件和服务基础设施。MGR的总体架构图如下:
MGR 插件包含一组用于捕获(capture),应用(apply)和生命循环(lifecycle)的API接口,这些API接口控制着MGR插件如何与MySQL Server交互。这些接口是放置在事务执行管道中的一些钩子(它们将MySQL Server的核心与组MGR插件隔离开来),逻辑上将MySQL Server内核与MGR插件隔离开来。其中有一些接口提供把通讯信息从Server发送给MGR插件(例如:Server启动、恢复、接受连接以及Server即将提交事务的事件通知),有一些接口提供把通讯信息从MGR插件发送给MySQL Server(例如:MGR插件命令MySQL Server提交、终止一个正在执行的事务,或者让该事务写入relay log中排队等候处理)。
在这些API接口的下一层,是一组组件(capture、applier、Recovery),组复制中的三个核心模块,当上层API发生调用,将根据调用类型路由到下面这三个模块执行相应的逻辑:
- capture 组件负责跟踪与正在执行的事务相关的上下文信息。
- applier 组件负责在数据库上执行(应用)远程事务,其实就是读取relay log中数据进行回放。
- Recovery 组件管理数据库节点的分布式恢复相关的工作,以及负责在一个新的Server(joiner)加入集群时选择一个捐赠者(donor),协调joiner节点的数据追赶的更新步骤(包括相关的数据回追,失败处理等),以及对选择捐赠者(donor)失败之后做出一些响应。简而言之就是管理组成员的recovery。
继续沿着堆栈向下,复制协议模块包含复制协议中的一些特定逻辑。例如:处理冲突检测,并接收和传播事务到组中。
MGR 插件体系结构的最后两层是组通信系统(GCS) API和基于paxos的组通信引擎(XCom)的实现。GCS API是一个高级API,它抽象了构建复制状态机所需的属性(详见"1、组复制的背景信息")。因此,它将消息层的实现与插件的其余上层组件解耦。组通信引擎处理与复制组成员之间的通信,主要提供基于Paxos协议的变体实现的数据一致性核心功能。
本系列文章参考文献(后续文章将删除这部分内容):
- MySQL 官方手册: https://dev.mysql.com/doc/refman/8.0/en/group-replication.html
- MySQL High Availability Team: https://mysqlhighavailability.com/group-replication-consistent-reads-deep-dive/
- 网易-温正湖-知乎专栏: https://zhuanlan.zhihu.com/p/39541394
- wzy0623-csdn博客-MySQL 8 复制系列文章: https://blog.csdn.net/wzy0623
- 宋利兵: 深入理解MySQL Group Replication
PS:系列文章中的引用链接,我们采用了整体编排章节号的原则(例如:请参见“3、组复制监控”),所以,这些引用链接在我们逐步推送的系列文章中并不能直接跳转,也可能无法直接对应查找这些章节的内容,但,待到最后我们将一份完整的文章整理成册之日,利用这些引用链接中的章节号就可用于直接查找对应章节号的内容了,对于此,还望大家海涵!
| 作者简介
罗小波·数据库技术专家
《千金良方——MySQL性能优化金字塔法则》作者之一。熟悉MySQL体系结构,擅长数据库的整体调优,喜好专研开源技术,并热衷于开源技术的推广,在线上线下做过多次公开的数据库专题分享,发表过近100篇数据库相关的研究文章。
全文完。
Enjoy MySQL 8.0 :)