MYSQL MGR 从入门到精通 02

12 MGR性能

本节介绍如何使用可用的系统变量对组复制进行性能优化,以便获得最佳性能。
6.1. 微调组通信线程
当加载并启动MGR插件时,组通信线程(GCT)就会不断循环运行。GCT接收来自组和MGR插件的消息,处理与仲裁和故障检测相关的任务,发送一些保活的通讯消息,还处理MySQL Server与组之间传入和传出的事务。GCT会等待队列中的传入消息。当队列中没有消息时,GCT将会进行等待。在某些情况下,通过将这个等待配置得稍微长一些(进行主动等待),可以减少操作系统执行上下文切换时从处理器中换出GCT线程的次数。
要强制GCT执行主动等待,请使用系统变量group_replication_poll_spin_loops进行设置,这使得 GCT 在对下一个消息进行实际轮询队列之前,在已配置的循环次数内进行循环时不做任何相关操作(该系统变量设置的值表示需要等待通信引擎互斥锁(mutex)被释放的次数,不是时间单位)。


mysql> SET GLOBAL group_replication_poll_spin_loops= 10000;

6.2. 流量控制
组复制可确保事务仅在组中的大多数成员接收到它,且并发发送的所有事务在所有接收到事务的成员之间的相对顺序达成一致后,就可以执行事务的提交操作。如果对组的写入并发事务总数不超过组中任何成员的写入容量(提供写服务的能力),则此方法可以获得很好的性能。但如果组中所有成员能够提供的最大写服务能力不相同,那么组中服务能力低的成员可能出现数据延迟(例如:一些成员能提供3000TPS的写能力,但是有一些成员只能提供2000TPS的写能力),那么,当对能够提供3000TPS的成员写入并发3000的事务时,只能提供2000TPS的成员就会出现延迟(数据落后于能够提供3000TPS的成员)。
组中如果有成员出现数据延迟,将有可能导致应用程序对这些成员执行读操作时,读取到非常陈旧的数据,另外,组中的其他不存在数据延迟的成员或多或少需要保存一些复制上下文(binlog日志记录),以满足来自存在数据延迟的慢速成员潜在的数据传输请求。但是,复制协议中有一种机制可以避免在快成员和慢成员之间存在过大的事务差距。这就是所谓的流量控制机制。流量控制机制图解决如下几个问题:

  • 保持成员之间的数据足够接近(慢速成员没有太大数据延迟)。
  • 快速适应组中不断变化的环境。如,适应不同的工作负载或更多写操作。
  • 让组中的每个成员能够提供的写服务能力得到一个平衡。
  • 在非严格必要的情况下,不降低吞吐量,以避免浪费资源。
    考虑到组复制的设计,在决定是否需要启用流量控制时,可能要考虑两个工作队列:认证队列和二进制日志应用队列。当其中一个队列的大小超过用户定义的阈值时,就会触发流量控制机制。对于流量控制配置:首先,需要选择对谁配置流量控制,是对认证队列、还是针对应用队列、还是两者都需要配置流量控制。然后,对需要配置流量控制的对象(认证队列和应用队列)设置流量控制阈值。
    流量控制依赖于两个基本机制:
  • 对组成员进行监控,并收集所有组成员的吞吐量和队列大小的一些统计信息,从而对每个组成员能够承受的最大写压力进行有根据的猜测(评估);
  • 对组中的所有成员的并发写能力时刻保持监控,一旦某成员的并发压力超过了组中所有成员的平均写能力,就会对其执行流量控制。
    6.2.1. 探测和统计
    监控机制通过在每个成员上部署一组探测器来收集关于其工作队列和吞吐量的信息。然后,将收集到的信息定期发送给组,以便与其他成员共享这些探测数据。
    这些探测器被分散在MGR插件的堆栈中,允许它们建立相应的度量指标,如下(这些指标值在performance_schema.replication_group_member_stats表中能够查询到):
  • 认证队列大小
  • 复制应用队列大小
  • 认证完成的事务总数
  • 组成员中应用的远程事务的总数
  • 本地事务的总数
    一旦一个成员收到来自另一个成员的带有统计信息的消息,它就会计算关于在最后一个监控探测期间,认证、应用和本地执行的事务数量等相关的度量指标。
    监控数据定期与组内其他成员共享。监控周期(频率)必须足够高,以便其他成员能够根据这些监控信息来确定当前的写请求量,但也必须足够低,以便对组带宽的影响最小。监控信息每秒钟都会被共享,一秒的时间间隔通常情况下能够解决这两个问题且能够很好地在这两个问题之间取得一个平衡。
    6.2.2. 组复制节流
    组复制节流,指的是基于从组中所有成员中收集的度量指标,来决定是否启用限制成员"执行/提交'新事务的速度的一种节流机制。因此,从所有组成员获取的度量指标是计算每个组成员容量的基础:如果一个成员有一个大的队列(用于认证或复制应用线程),那么执行新事务的能力应该接近于上一阶段(最后一次探测时间段)认证或复制应用事务的能力。
    组中所有成员的最低容量(写能力)决定了组的实际容量,而本地事务的数量决定了有多少成员向其写入数据,因此也决定了应该与多少成员共享可用的容量。这意味着每个成员都有一个基于可用容量的写限额,换句话说,它就是下一个时间段内可以安全(不受节流机制影响)地执行的事务数量。如果认证队列或二进制日志应用队列大小超过用户定义的阈值,则将通过节流机制强制执行写限额。
    限额将根据上一阶段延迟的事务数量逐步减少10%,以让触发节流机制的队列大小减少到触发阈值以内。待到恢复后,为避免在队列大小超过阈值时出现吞吐量的陡增,在此之后,每个时间段的吞吐量只允许增长相同的10%。
    当前的节流机制不会对限额内的事务造成影响,但是会延迟完成那些超过限额的事务,直到监控周期结束。因此,如果写限额设置的非常小,一些事务的延迟可能会接近一个监控周期。
    6.3. 消息压缩
    当网络带宽成为瓶颈时,消息压缩可以在组通信级别上将吞吐量提高达30-40%。这对于负载较大的大型组尤其重要。下表列出了在不同的二进制日志格式下的LZ4压缩率。
    MYSQL MGR 从入门到精通 02
    组中N个参与者之间的相互连接的TCP具有对等性质,使得发送方需要发送N次相同数量的数据(例如:组中有3个成员,那么发送方就要发送3次相同的数据)。此外,二进制日志可能具有较高的压缩比(见上表)。这使得压缩对于包含大事务的工作负载来说是一个引人注目的特性。在MGR插件中,支持压缩的是组通讯API组件,如下图 。
    MYSQL MGR 从入门到精通 02

MGR 插件架构如上图所示,压缩功能在插件的五层(位于MySQL Server和复制组之间,刨去MySQL Server层,它是MGR插件的第四层,也就是组通讯引擎层)。压缩和解压缩的工作任务由组通信系统API处理。压缩发生在数据被传递给组通信线程之前的组通讯引擎,所以它发生在MySQL 用户会话线程的上下文中。事务有效负载可能在发送到组之前进行压缩,在接收之后进行解压缩。压缩是有条件的,并且依赖于一个配置的阈值。压缩功能默认启用。
此外,并不要求组中的所有成员都启用压缩来协同工作。在收到消息后,成员会检查消息信封以验证它是否已被压缩。如果需要,则该成员在将事务交付给上层组件之前会对其进行解压。
使用的压缩算法是LZ4。默认情况下启用压缩,阈值为1000000字节(1M)。压缩阈值(以字节为单位)可以根据需要设置为比默认值更大的值。当压缩阈值不为0时,只有有效负载大于阈值的事务才会被压缩。设置压缩阈值示例如下:

# 这将压缩阈值设置为2MB。如果事务生成的复制消息的有效负载大于2MB,例如:二进制日志事务条目大于2MB,则对其进行压缩。若要禁用压缩,请将阈值设置为0。
STOP GROUP_REPLICATION;
SET GLOBAL group_replication_compression_threshold= 2097152;
START GROUP_REPLICATION;

6.4. 消息分段
当在组复制组成员之间发送异常大的消息时,可能导致某些组成员发生失败并被驱逐出组。这是因为组复制的组通信引擎(XCom, Paxos变体)使用的单个线程占用的处理消息的时间太长,因此某些组成员可能会报告消息接收失败。默认情况下,从MySQL 8.0.16开始,大的消息被自动分割成片段,分别发送,然后由接收者重新组合这些消息片段。
系统变量group_replication_communication_max_message_size指定组复制通信的最大消息大小,超过该大小的消息将被分段。默认的最大消息大小是10485760字节(10 MiB)。该系统变量的最大允许值与系统变量slave_max_allowed_packet的最大值相同(后者是1073741824字节,即 1GB),系统变量group_replication_communication_max_message_size的设置值必须小于系统变量slave_max_allowed_packet的设置值,因为应用线程不能处理大于系统变量slave_max_allowed_packet的消息片段。要关闭消息分段功能,请将系统变量
group_replication_communication_max_message_size设置为零值。
与大多数其他组复制系统变量一样,修改系统变量group_replication_communication_max_message_size必须重新启动组复制才能使更改生效。例如:


STOP GROUP_REPLICATION;
SET GLOBAL group_replication_communication_max_message_size= 5242880;
START GROUP_REPLICATION;

当所有组成员都接收并重新组合了消息的所有片段时,就认为分段消息的消息传递已经完成了。分段消息的头部包含了一些信息,这些信息使成员能够在消息传输期间加入组,并恢复加入组之前发送的早期消息片段。如果joiner节点无法恢复消息片段,则会将自己从组中驱逐出去。
为了让一个复制组正常使用消息分段功能,所有组成员必须运行MySQL 8.0.16或以上版本,并且组使用的组复制通信协议版本必须支持消息分段。可以使用group_replication_get_communication_protocol() UDF检查组使用的通信协议版本是多少,UDF 返回版本号字符串代表了组支持的最老的MySQL Server版本。MySQL 5.7.14的版本支持压缩消息,MySQL 8.0.16的版本支持消息分段。如果所有组成员都运行在MySQL 8.0.16以上版本,并且组中不需要运行更低版本的组成员,则可以使用group_replication_set_communication_protocol UDF()来设置通信协议版本为MySQL 8.0.16及其以上,这样就能够确保消息分段功能在组中所有成员上正常运行。有关更多信息,请参见"4.1.4. 设置组的通信协议版本”。
如果复制组由于某些成员不支持消息分段导致组不能使用消息分段,则可以使用系统变量group_replication_transaction_size_limit来限制该组所接受的最大事务大小。在MySQL 8.0中,默认设置大约为143 MB。超过这个大小的事务将被回滚。还可以使用系统变量group_replication_member_expel_timeout来设置一个在成员被驱逐出组之前,它被怀疑失败的额外时间(默认为0,从8.0.14版本开始,最大值为一个小时)。即,在该系统变量设置的时间内,被怀疑的成员不会被驱逐出组。
6.5. XCom 缓存管理
用于组复制的组通信引擎(XCom, Paxos变体)包含了一个消息(及其元数据)缓存,该消息是作为组成员之间交换协商一致性协议的一部分。在其他用途中,消息缓存可用于在一段时间内无法与其他组成员通信的成员在重新返回到组使进行恢复。
从MySQL 8.0.16开始,可以使用系统变量
group_replication_message_cache_size为XCom的消息缓存设置缓存大小限制。这个系统变量的默认值和最小值为1 GB,即MySQL Server 8.0.16 版本之前的消息缓存大小设置。如果达到了缓存大小限制设置,XCom将删除已经确定和交付的最老的条目。考虑到MySQL Server的其他缓存和对象池的大小,请确保在系统上有足够的内存来满足所设置的缓存大小限制。
如果一个不可达成员尝试重新恢复连接时,需要一条恢复消息,但该消息已从消息缓存中删除,则该成员无法重新连接。如果使用了系统变量group_replication_member_expel_timeout(该系统变量在MySQL 8.0.13中引入)指定一个额外的延迟时间,则更有可能出现这种情况。当不可达的成员恢复时可能需要使用到的消息在消息缓存中已经被删除时,组复制的组通信系统(GCS)通过一条警告消息来发出警告。此警告消息记录在所有活跃的组成员上(对于每个不可到达的成员仅记录一次)。尽管组成员不能确定不可到达的成员最后看到的消息是什么消息,但是警告消息表明缓存大小可能不足以支撑通过系统变量group_replication_member_expel_timeout设置的在驱逐成员之前的等待时间内总的消息大小。在这种情况下,可以增加缓存大小限制,以便消息缓存能够存放组成员重新加入组所需的所有遗漏消息。
如果考虑减少缓存大小限制,可以使用以下语句先查询
performance_schema.memory_summary_global_by_event_name表中记录的相关内存分配情况:

# 查询语句返回消息缓存的内存使用统计信息,包括当前缓存条目的数量和当前缓存的大小。如果降低了缓存大小限制,XCom将删除已经确定并交付的最老的条目,直到当前大小低于限制值为止。在删除最老的条目过程进行期间,XCom可能会暂时超过缓存大小限制。
mysql> SELECT * FROM performance_schema.memory_summary_global_by_event_name
  WHERE EVENT_NAME LIKE 'memory/group_rpl/GCS_XCom::xcom_cache';

6.6. 对故障检测和网络分区的响应
组复制的故障检测机制旨在识别不能与组正常通信的组成员,并在他们可能发生故障时将他们从组中驱逐出去。当组中有成员发生故障时,如果组中存在多数成员存活,则故障检测机制能够使得组正确恢复可用性,以便能够及时恢复并正确处理客户端的请求。
通常,所有组成员会定期与所有其他组成员交换消息。如果一个组成员在5秒内没有收到来自某个特定成员的任何消息,当这个检测周期结束时,就会产生对该成员的怀疑。当一个可疑成员超时时(在最大允许的怀疑时间范围内仍然没有任何消息),该可疑成员就被认定为失败了,并被驱逐出组。被驱逐的成员会被组中所有活跃成员从组成员资格列表中删除,但被驱逐的成员自己可能不知道已经被驱逐出组(例如:它自己还在线,只是无法联系其他成员)。如果被驱逐的成员实际上没有失败(例如,因为临时网络问题而断开连接),并且后续能够恢复与其他成员的正常通信,则在网络恢复之后它会收到一个包含了该成员已被驱逐出该组的新视图信息。
组成员(包括失败的成员本身)对这些情况的响应可以在流程中的许多地方进行配置。默认情况下,如果怀疑某个成员失败,则会发生以下行为:

  • 当创建怀疑对象时,它将立即超时(其生存期设置为0),因此一旦怀疑对象发生超时,嫌疑成员就会被驱逐。成员可能在超时后继续存活几秒钟,因为对怀疑对象的检查是周期性的。
  • 如果被驱逐的成员恢复了正常通讯,并意识到自己被驱逐了,它就不会试图重新加入到该组,而会接受驱逐结果。
  • 当一个被驱逐的成员接受它的驱逐结果时,它就会切换到超级只读模式(设置super_read_only=1。注意:设置super_read_only=ON时,read_only会自动设置为ON,但在将super_read_only=OFF时,不会自动将read_only设置为OFF),直到人工介入处理(注意:在MySQL 8.0.12到8.0.15的版本中,组成员被驱逐时默认的行为是关闭自身数据库进程。从MySQL 8.0.16开始,默认行为被更改为匹配MySQL 5.7中的行为,即,设置超级只读模式)。
    设置这些默认值是为了优先考虑组的正确操作和对请求的正确处理。但是,在较慢的网络或瞬时故障率较高的网络中,这些默认值可能会带来不便,因为在这些情况下,经常需要人工介入修复被驱逐的成员。它们也不允许在预期的网络故障或Server速度变慢的情况下对组计划执行一些操作。但,为了满足系统的一些优先级处理需求,可以使用本节中介绍的一些组复制配置系统变量来修改这些默认行为。
    活跃成员可能由于网络分区而与复制组的一部分(不是全部)失去联系。例如,在一个由5个成员组成的组(S1、S2、S3、S4、S5)中,如果(S1、S2)和(S3、S4、S5)之间断开连接,则这就表示存在一个网络分区。第一组(S1,S2)成为了少数成员的一部分,因为它并不拥有多数成员(不超过总成员数的一半),这时,少数成员的组部分处理的任何事务都将被阻塞,因为它们无法访问该组的大多数成员,因此该组部分无法执行仲裁。有关此场景的详细描述,请参见"4.4. 网络分区”。在这种情况下,默认的行为是少数成员部分和多数成员部分都能够继续接受事务(尽管在少数成员的组部分的请求会被阻塞,但是,请求并不会被拒绝),此时,必须人工介入处理。但此默认行为是可配置的。
    请注意,如果组成员运行在不支持相关设置的旧MySQL Server版本下,或者具有不同的默认设置的版本中,那么他们将根据上述默认行为对自己和其他组成员采取行动。例如,不支持系统变量
    group_replication_member_expel_timeout的组成员将在检测到怀疑对象超时时立即驱逐其他成员,并且驱逐结果将被其他成员接受,即使其他成员支持该系统变量并设置了更长的超时时间也不影响该驱逐结果。
    6.6.1. 超时驱逐
    可以使用系统变量group_replication_member_expel_timeout(该变量在MySQL 8.0.13中引入)来设置在创建可疑成员和驱逐可疑成员之间允许的超时时间(也可以看做是驱逐等待期或者生存期)。
    当组中的其他成员对它的怀疑(或它对自己的怀疑)超时时,该组成员将被驱逐出组。默认情况下,系统变量group_replication_member_expel_timeout设置为0,表示没有等待期,在5秒的检测周期结束后,可疑成员可能立即被驱逐出组。在驱逐机制检测到并实施驱逐之前,可能需要一段较短的时间。如果一个组成员使用的是不支持此系统变量设置的旧MySQL Server版本,那么其行为就类似于系统变量group_replication_member_expel_timeout设置为0。
    为了避免在较慢的网络上发生不必要的驱逐,或者在可预期的瞬时网络故障或服务器突然变慢的情况下,您可以指定一个大于零的超时值,最长3600秒(1小时)。此状态下的可疑成员被列为不可访问,但不会从组的成员资格列表中删除。如果可疑成员在怀疑超时之前再次变为活跃状态,它将重新加入该组,并应用组中其他成员的中缓存的所有消息,应用完成之后就会进入在线状态。
    如果超过了怀疑超时时间,可疑成员将在怀疑超时后立即被驱逐出组。如果该成员后续能够恢复通信并接收到被驱逐的视图,它会发现自己已经被驱逐了,并接受驱逐结果。默认情况下,此时被驱逐的成员会遵循系统变量group_replication_exit_state_action指定的退出操作。或者,可以使用系统变量group_replication_autorejoin_tries(MySQL 8.0.16中引入)使成员自动尝试重新加入组。
    在驱逐一个成员之前的等待期只适用于先前在该组中活跃的成员。在组中从未成为活跃成员的Server并没有这个等待期待遇,如果在检测周期内发现了,就会被立即从成员资格列表中删除,因为他们花费了太长时间来加入组,且加入组还失败了。
    如果组中的任何成员受到怀疑,则就无法重新配置该组成员的身份(通过添加或删除成员或选举新主要节点)。如果在一个或多个组成员受到怀疑时需要执行组成员资格变更,并且希望可疑成员继续留在组中,那么可以采取任何可行的方法来使成员再次变为活跃状态(如果可能的话)。如果不能让组成员再次变为活跃状态且希望将其驱逐出组,则可以强制被怀疑的组成员立即超时。通过将系统变量group_replication_member_expel_timeout的值更改为一个比怀疑时间更短的值即可,这样,可疑成员就会立即被驱逐出组。
    有关在系统变量group_replication_member_expel_timeout不可用时,如何避免不必要的驱逐,详情请参阅"9.2. 组复制限制”。
    6.6.2. 与多数成员失联超时
    默认情况下,由于网络分区而处于少数成员的组部分不会自动脱离组。可以使用系统变量group_replication_unreachable_majority_timeout设置一个成员在与大多数组成员失去联系后等待的秒数,超过这个设置时间之后,就会自动退出组。设置失联超时意味着不需要在发生网络分区之后主动监控少数组成员的组部分(它们会自行退出组),这样可以避免由于不适当的人为干预造成脑裂(具有两个不同组视图版本的组成员资格)的情况。
    当系统变量group_replication_unreachable_majority_timeout指定的失联超时过期时,由该成员和该成员所处的少数成员的组部分中的其他所有成员正在处理的所有挂起事务都将被回滚,该组部分中的成员状态将变更为ERROR状态。默认情况下,少数派成员随后将执行系统变量group_replication_exit_state_action指定的退出操作。或者,可以使用系统变量group_replication_autorejoin_tries(MySQL 8.0.16中引入)使成员自动尝试重新加入组。
    在决定是否设置与多数成员失联超时的系统变量时,需要考虑以下几点:
  • 在一个对称的组中,例如有两个或四个成员的组,如果两个网络分区都包含了相同数量的成员,则两个组部分都会认为自己属于少数派,并都会进入ERROR状态。在这种情况下,组没有任何一个网络分区可用(即,没有任何一个被网络拆分的组部分可用正常对外提供服务)。
  • 当存在少数派的组部分时,少数派组部分处理的任何事务都将被接受(不会被拒绝),但会被阻塞,因为少数派组部分的成员无法达到法定人数(即,不满足达成共识所需的多数成员要求),直到在这些成员上执行STOP GROUP_REPLICATION语句,或处于这种状态达到超时之后自动处理脱离组操作为止。
  • 如果没有设置与多数成员失联超时,则少数派组部分中的成员将永远不会自动进入ERROR状态,此时,你必须手动停止它们。
    如果在检测到与多数成员失联超时之后,再到少数派成员上设置与多数成员失联超时的设置,则此时设置无效,需要提前设置才会生效。
  • 如果不使用系统变量group_replication_unreachable_majority_timeout,则在发生网络分区时。
    6.6.3. 自动重新加入组
    系统变量group_replication_autorejoin_tries是在MySQL 8.0.16引入的,它可以使一个被驱逐出组的成员(此时,被驱逐的成员还未接受驱逐结果)或与多数成员失联超时的成员自动重新加入组。可以使用该系统变量来指定该成员重新加入组的尝试次数,让其自动重新加入组,而不是在其恢复与组的通信后立即接受驱逐结果。如果能够容忍读取陈旧数据且希望尽量减少手动干预,特别是在瞬时网络问题频发导致成员被驱逐的情况下,可以考虑启用自动重新加入组的功能。
    当成员被驱逐或者与多数成员失联超时,如果系统变量group_replication_autorejoin_tries设置了一个非0值,则它将自动尝试重新加入组,直到耗尽指定的尝试次数为止。在一次尝试自动重新加入组失败之后,会等待5分钟之后再次尝试重新加入组。如果重试次数被耗尽之后,该成员仍然没有成功加入组或者未被执行停止MGR插件的操作,则该成员将继续执行系统变量group_replication_exit_state_action指定的退出操作。如果系统变量group_replication_autorejoin_tries设置为0值,则等待系统变量group_replication_unreachable_majority_timeout设置的超时时间之后,直接执行系统变量group_replication_exit_state_action指定的退出操作。
    在自动重新加入组的过程中,被驱逐的成员保持在超级只读模式,并在其复制组视图上显示为ERROR状态。但要注意,虽然成员处于超级只读模式,不能对成员执行写操作,但是可以执行读操作,并且随着时间的推移,读取到陈旧数据的可能性会越来越大(因为此时并不能同步组中的最新数据)。如果此时希望成员脱离组,则可以随时使用STOP GROUP_REPLICATION语句或关闭数据库Server进程来手动关闭该成员。
    可以使用performance_schema下的表来监控自动重新加入组的过程。当执行自动重新加入组时,performance_schema.events_stages_current表中会记录“Undergoing auto-rejoin procedure”的事件,以及到目前为止(查询该表的时间点)该事件执行的次数(在WORK_COMPLETED字段中)。performance_schema.events_stages_summary_global_by_event_name表中记录了Server自动重新加入组的过程的次数(在COUNT_STAR字段中)。performance_schema.events_stages_history_long表中记录了每次自动重新加入组的过程完成时间(在TIMER_END字段中)。
    6.6.4. 退出操作
    系统变量group_replication_exit_state_action在MySQL 8.0.12及其之后的版本、MySQL 5.7.24及其之后的版本中引入,它指定当组成员由于发生错误或未知问题而意外脱离组、或者无法自动重新加入组、或者尝试自动加入组次数耗尽时,组复制会执行什么操作(这里指的是组成员退出组时需要做的操作)。请注意,对于被驱逐出组的成员,在其重新加入到组之前,该成员并不知道自己已被驱逐出组,因此,只有当该成员设法重新与组建立连接、或该成员对自己产生怀疑并将自己驱逐出组时,才会采取指定的操作(即,系统变量group_replication_exit_state_action指定的操作)。
    关于退出操作,按影响顺序依次如下:
  • 如果退出操作配置为READ_ONLY,则会通过将系统变量super_read_only设置为ON,以便将MySQL切换到超级只读模式。当成员处于超级只读模式时,客户端不能执行任何更新操作,即使他们拥有super权限。但是,客户端仍然可以读取数据,由于数据不再更新,所以,随着时间的推移,读取到陈旧的数据的概率会大大增加。因此,使用此设置时,需要主动监控Server的故障状态。该退出操作也是MySQL 8.0.16及其之后版本的默认操作。执行此退出操作后,成员的状态将在组视图中显示为ERROR状态。
  • 如果退出操作配置为OFFLINE_MODE,则会通过将系统变量offline_mode设置为ON,以便将MySQL切换到离线模式。当成员处于离线模式时,连接的客户端用户执行下一个请求时连接会被断开,且不再接受其新的连接请求,只接受具有CONNECTION_ADMIN或super权限的用户建立新的客户端连接。组复制还会将系统变量super_read_only设置为ON,以阻止普通用户与超级用户执行更新操作。此退出操作可以防止更新操作,也可以防止读取到陈旧的数据(因为普通用户无法再次建立连接,但具有指定权限的客户端用户如果要在这个时候执行查询,则仍然可能读取到陈旧的数据)。同时允许MySQL Server继续运行,以便管理员可以尝试解决问题而不关闭MySQL Server进程。此退出操作在MySQL 8.0.18中可用。在执行此退出操作之后,成员的状态将在组视图中显示为ERROR状态(不是OFFLINE,这意味着MGR插件可用,但该成员当前它不属于组)。
  • 如果退出操作配置为ABORT_SERVER,则MySQL Server将被关闭。虽然这可以防止所有的更新操作和读取陈旧的数据,但也意味着MySQL Server不可用,必须重新启动Server进程。该退出操作在MySQL 8.0.12版本中引入,且在大于等于8.0.12与小于等于8.0.15之间的版本中为默认的退出操作。执行此退出操作后,退出成员将被从组视图列表中删除。
    注意:
  • 无论设置什么退出操作,当执行完退出操作之后,都需要人为介入对其进行恢复,此时,不人为执行重启组复制的情况下不允许(也无法)重新加入组。
  • 如果Server在成功加入组之前发生失败(从未成功加入组的Server),则不会执行group_replication_exit_state_action系统变量指定的退出操作。例如,在本地配置检查期间出现故障、或者joiner节点的配置与组的配置不匹配,就会出现这种情况。在这些情况下,系统变量super_read_only将保持原始值(不会进行调整),MySQL Server也不会执行关闭动作(当然,此时由于该Server未成功加入组,所以也就无法接受组中的更新数据,此时如果在该Server中发生新的数据写入,将导致后续难以将这些新数据同步到组中)。所以,为了避免这种情况的发生,建议在启动时在配置文件中设置系统变量super_read_only=ON,当配置好组复制之后,会自动将系统变量super_read_only设置为OFF。
    如果Server在成功加入组之后发生失败,则会执行如下退出操作。下面是一些导致成员失败的一些场景:
  • 应用线程错误:复制应用线程中存在错误。此问题不可自动恢复。
  • 无法执行分布式恢复:这意味着无法完成组复制的分布式恢复过程(即,无法使用基于远程克隆操作和基于二进制日志的状态传输)。组复制在这种情况下会自动重试分布式恢复,但如果没有其他更多选择来完成该过程,则组复制将停止。具体参见"4.3.3. 分布式恢复的容错能力"。
  • 组配置变更时发生错误:在使用UDF进行组范围的配置变更期间发生错误
  • 选主期间发生错误:在以单主模式运行的组中,执行选主时发生错误
  • 与多数成员失联超时:该成员已与大多数组成员失去联系,因此它属于少数成员的组部分,且系统变量group_replication_unreachable_majority_timeout设置的超时时间已过期(与多数成员失联时间已超过该系统变量设置的时间)。
  • 被驱逐出组的成员:组中的其他成员对该成员产生了怀疑,且系统变量group_replication_member_expel_timeout设置的超时已过期(怀疑期超时时间),当该成员恢复与组的通信之后会发现自己已被驱逐出组。
  • 尝试自动重新加入组的次数耗尽:当某个成员与组中的其他大多数成员失联或被驱逐出组之后,会根据系统变量group_replication_autorejoin_tries设置的次数不断尝试自动重新加入组,当该成员耗尽了尝试次数,也仍然能未能成功加入组。
    下表总结了组复制的每种故障场景及其对应的退出操作:
    MYSQL MGR 从入门到精通 02
    成员上的应用线程错误

分布式恢复不可用

组配置进行了错误的修改

选主出错

多数成员不可达超时

被驱逐出组的成员

超过自动重新加入组尝试次数仍未成功加入组的成员

系统变量super_read_only设置为ON

系统变量offline_mode和super_read_only设置为ON

MySQL Server关闭

同左

13 MGR安全

本节从组复制的IP白名单以及安全套接字(SSL)支持两个方面来介绍如何保护组合组成员之间连接的安全性。

5.1. 组复制的IP地址白名单
MGR插件有一个系统变量group_replication_ip_whitelist,用于确定可以接受从哪个主机传入的组通信系统连接。假设Server S1中设置了该系统变量,然后使用S1引导组启动,之后,将Server S2作为joiner节点,当S2尝试与S1建立组通讯连接时,S1在接受S2的组通讯连接请求之前,先检查系统变量group_replication_ip_whitelist的设置是否允许S2访问,如果允许,则接受S2的组通讯连接请求,否则拒绝S2的组通讯连接请求。
如果没有显式地指定白名单,则S1的组通信引擎(XCom)将自动扫描S1所在主机上的活跃网卡接口,并根据这些活跃网卡接口上配置的IP地址生成相应的子网地址(包括IPV4和IPV6地址)。根据这些生成的子网地址来自动创建一个组复制的白名单设置。自动生成的IP白名单地址可能包含如下范围:

IPv4 (在RFC 1918中定义的IPV4地址的划分)
# A类地址
10/8 (IP范围:10.0.0.0-10.255.255.255) 
# B类地址
172.16/12 (IP范围:172.16.0.0-172.31.255.255) 
# C类地址
192.168/16 (IP范围:192.168.0.0-192.168.255.255) -
IPv6 ( 在RFC 4193 和 RFC 5156中定义的IPV6地址的划分)
# 唯一的本地地址前缀(范围)
fc00:/7
# 本地链路的单播地址前缀(范围)
fe80::/10
# 本地(localhost)IPv4地址
127.0.0.1 - localhost for IPv4
# 本地(localhost)IPv6地址
::1 - localhost for IPv6

在MySQL的错误日志中会记录自动为主机添加的白名单地址信息。
从上面代码段中所述的IP范围我们可以看到,自动生成的白名单地址都是私有网络地址(即便主机上配置有公网IP地址,也不会生成公网地址网络的白名单),而私有地址只允许在私有网络内访问,不允许在公网*问。因为,如果要使用公网地址作为白名单,则,你需要使用系统变量group_replication_ip_whitelist来显式指定你希望允许开放访问的公网地址范围,另外,一旦为系统变量group_replication_ip_whitelist指定值之后,自动生成白名单的功能就失效了,未在系统变量group_replication_ip_whitelist中指定的任何地址都不允许访问,因此,任何希望允许访问的IP地址范围,你都需要显式指定。
如果某个Server是组中的活跃成员,则不允许动态修改白名单地址,必须先执行STOP GROUP_REPLICATION语句停止组复制,让其主动脱离组,配置好白名单之后,再执行START GROUP_REPLICATION语句让其重新申请加入组。否则会报错:ERROR 3093 (HY000): The IP whitelist cannot be set while Group Replication is running。
如果要显式指定白名单地址,则,可以使用如下一些有效的字符串形式指定:

  • 单个IPV4地址:例如,198.51.100.44
  • 带有CIDR符号的IPv4地址:例如,192.0.2.21/24,可以理解为带有子网掩码的地址。
  • IPv6地址(MySQL 8.0.14中引入IPV6地址支持):例如,2001:db8:85a3:8d3:1319:8a2e:370:7348
  • 带有CIDR符号的IPv6地址(MySQL 8.0.14中引入IPV6地址支持):例如,2001:db8:85a3:8d3::/64
  • 主机名:例如,example.org
  • 带有CIDR符号的主机名:例如,www.example.com/24
    在MySQL 8.0.14之前,主机名只能解析为IPv4地址。从MySQL 8.0.14开始,主机名可以解析为IPv4地址或IPv6地址或两者同时解析。如果主机名同时解析为IPv4和IPv6地址,则IPv4地址总是优先用于组复制连接。您可以将CIDR表示法与主机名或IP地址结合使用,将带有特定网络前缀的IP地址块列入白名单(即,带有子网掩码的IP地址范围的白名单),但是要确保指定的子网中包含了你希望允许访问的所有IP地址。
    要修改白名单设置,需要重启组复制,如果有多个白名单地址,使用逗号分隔,如下:
# 先停止组复制
mysql> STOP GROUP_REPLICATION;
# 修改白名单地址
mysql> SET GLOBAL group_replication_ip_whitelist="192.0.2.21/24,198.51.100.44,203.0.113.0/24,2001:db8:85a3:8d3:1319:8a2e:370:7348,example.org,www.example.com/24";
# 重新启动组复制
mysql> START GROUP_REPLICATION;

白名单必须包含每个成员的系统变量group_replication_local_address中指定的IP地址或主机名。这个地址与MySQL Server的SQL协议主机和端口不一样(系统变量group_replication_local_address指定的地址和端口是用于组成员之间的组通讯的,而不是对外提供业务访问的)。
要成功加入复制组,则给定的待加入组的Server的IP地址需要在其请求加入组的种子成员的白名单中需要允许其发起组通讯请求。通常,是根据种子成员(即组的引导成员)的系统变量group_replication_group_seeds设置的IP进行适配,但是,也可以根据组中的任意成员的系统变量group_replication_group_seeds指定的值进行设定,例如:组中的成员混合使用了IPV4和IPV6地址,那么,建议将所有成员可能会用于组通讯的网络的IPV4和IPV6协议地址一并配置到白名单中,以避免出现有Server申请加入组时被拒绝连接的情况发生。有关管理混合IPv4和IPv6复制组的更多信息,请参见"4.5. 配置支持IPv6和混合IPv6与IPv4地址的组"。
当复制组被重新配置时(例如,当选举一个新的主要节点或者一个成员脱离组时),组成员会重新建立它们之间的连接。如果组中的所有成员的白名单地址配置不一致,在重新配置组之后,可能导致某个成员在重新配置组之前允许加入组的而在重新配置组之后无法重新加入组(例如:组中有3个成员S1、S2、S3,当S3脱离组并重新配置组时,因为白名单不一致的原因,S2不允许S1访问,S3允许S1访问,但是现在S3已经不在组中了,这就会导致S3脱离组之后,S1和S2也无法组成新的组)。要完全避免这种情况,建议在组中的所有成员中设置一致的白名单。
对于主机名的白名单设置,仅当有另外一个Server发起连接请求时才会进行名称解析。无法解析的主机名不会用于白名单验证,且会将警告信息写入MySQL错误日志中。
注意:主机名用作白名单,安全性不如IP地址,除非必须,否则不建议使用外部组件来实现名称解析(除非外部组件的安全性你能够把控),如果有临时需要,可以考虑使用本地主机中的/etc/hosts解析记录实现
5.2. 组复制安全套接字层(SSL)支持
我们可以使用SSL来保护组成员之间的通信连接和分布式恢复的连接。本节将介绍如何配置SSL的连接。
5.2.1. 为组通信配置SSL
安全套接字可用于组成员之间的组通信连接。MGR插件的系统变量group_replication_ssl_mode控制组通讯连接是否启用SSL,并为组通讯连接指定安全模式。默认设置为DISABLED,表示不使用SSL。该系统变量有如下有效值:

MYSQL MGR 从入门到精通 02
复制的组通信连接的其余SSL相关的配置通过MySQL Server的SSL系统变量进行配置。这些SSL系统变量如下:

![](http://www.icode9.com/i/li/?n=4&i=images/blog/202103/01/69f86cfadbc900c19d4e18ff204fd6e8.png?,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=)

重要事项:

  • 从MySQL 8.0.16开始,MySQL Server支持TLSv1.3协议,前提是MySQL使用了OpenSSL 1.1.1或更高版本编译。但是,对于组复制来说,却是从MySQL 8.0.18开始支持TLSv1.3。在MySQL 8.0.16和MySQL 8.0.17中,只是MySQL Server支持TLSv1.3,但组通信引擎不支持TLSv1.3协议,所以在这两个版本中组复制不能使用TLSv1.3协议。
  • 请确保在系统变量tls_version中指定的TLS协议列表的连续性(例如:TLSv1,TLSv1.1,TLSv1.2)。如果协议列表中有任何空白(例如,如果您指定了TLSv1,TLSv1.2、省略了TLS 1.1),则组复制可能无法建立组通信连接。
  • 如果在组复制中使用TLSv1.3协议进行分布式恢复,则组复制组成员中至少需要一个默认启用TLSv1.3版本的加密算法套件(或称为"密码套件"),否则分布式恢复会失败。
    在复制组中,使用OpenSSL来在所有成员之间协商大家都支持的最高TLS协议版本。如果某个新加入组的Server配置为仅支持TLSv1.3 (tls_version=TLSv1.3),则如果组中的现有成员不支持TLSv1.3时(例如:设置 tls_version=TLSv1,TLSv1.1,TLSv1.2 )的情况下,则新的Server无法加入组(因为此时组中的所有成员使用的TLS协议版本低于新Server的TLS版本)。要将新申请加入组的Server连接到组,必须修改新申请加入组的Server的系统变量tls_version以支持低版本(例如:tls_version=TLSv1,TLSv1.1,TLSv1.2,TLSv1.3),修改完成之后,再次尝试重新申请加入组。在这种情况下,OpenSSL使用较低的TLS协议版本来在申请加入组的Server与组中现有成员之间建立连接。而组中的现有成员之间仍然继续使用支持的最高可用TLS协议版本。
  • 如果只是修改组中现有成员的TLS版本,但不重启组复制,则组中的现有成员之间已建立连接的TLS版本不会改变(不影响现有成员之间的连接)。
    如果想要使用TLSv1.3版本,则可能需要先升级组中现有成员的MySQL Server版本到MySQL 8.0.18及其以上的版本,然后,将TLS版本都修改为支持TLSv1.3(例如:tls_version=TLSv1,TLSv1.1,TLSv1.2,TLSv1.3)。
    从MySQL 8.0.16开始,tls_version系统变量可动态修改(即,可在线修改TLS协议版本列表)。注意,对于组复制,ALTER INSTANCE RELOAD TLS语句会根据系统变量tls_version的当前值来重新配置MySQL Server的SSL上下文,但在组复制运行时不会更改组通信连接的SSL上下文。要使新的系统变量tls_version配置对组复制生效,必须使用STOP GROUP_REPLICATION和START GROUP_REPLICATION语句重启组复制。
    要对MySQL Server启用SSL支持,可按照如下示例进行配置:

[mysqld]
# 为MySQL Server配置好SSL相关的系统变量
ssl_ca = "cacert.pem"
ssl_capath = "/.../ca_directory"
ssl_cert = "server-cert.pem"
ssl_cipher = "DHE-RSA-AEs256-SHA"
ssl_crl = "crl-server-revoked.crl"
ssl_crlpath = "/.../crl_directory"
ssl_key = "server-key.pem"
# 为组复制激活SSL,设置为REQUIRED时,如果组成员之间支持安全连接,则建立安全连接
group_replication_ssl_mode= REQUIRED

5.2.2. 为分布式恢复配置SSL
当一个Server申请加入组时,会组合使用远程克隆操作(如果可用)和异步复制连接来执行分布式恢复。这两种状态传输的方法都需要为分布式恢复设置复制用户,如"2.1.3. 用户凭证"中所述。申请加入组时使用的复制用户需要在执行申请加入组之前提前在组的现有成员中创建好(如果需要使用SSL也需要提前配置好SSL),可以使用如下语句来创建复制用户并启用SSL。


donor> SET SQL_LOG_BIN=0;
donor> CREATE USER 'rec_ssl_user'@'%' REQUIRE SSL;
donor> GRANT replication slave ON *.* TO 'rec_ssl_user'@'%';
donor> GRANT BACKUP_ADMIN ON *.* TO 'rec_ssl_user'@'%';
donor> SET SQL_LOG_BIN=1;

假设在组中的所有成员中都已经配置好了一个启用SSL的复制用户,则,可以通难过如下语句来为组复制的恢复通道配置使用该用户,当启动组复制时,复制恢复通道将使用这些凭据来连接其他组成员,如下所示:


new_member> CHANGE MASTER TO MASTER_USER="rec_ssl_user" FOR CHANNEL "group_replication_recovery";

要配置安全的分布式恢复连接,请使用组复制专用的分布式恢复SSL系统变量。这些变量对应用于组通信连接的Server SSL系统变量值,但它们仅适用于分布式恢复的连接。默认情况下,分布式恢复连接不使用SSL,即使为组通信连接激活了SSL,这些Server SSL系统变量也不会应用于分布式恢复连接。必须单独配置组复制专用的SSL系统变量才会生效。
如果将远程克隆操作用作分布式恢复的一部分,则组复制将自动配置克隆插件的SSL系统变量,以匹配对分布式恢复SSL系统变量的设置。
分布式恢复的专用SSL系统变量如下:

  • group_replication_recovery_use_ssl:设置为ON时,组复制会对分布式恢复的连接使用SSL,包括基于远程克隆和基于二进制日志的状态传输都会启用SSL。
  • group_replication_recovery_ssl_ca:用于分布式恢复连接的证书颁发机构(CA)文件的路径名。组复制会根据此系统变量的值自动配置为克隆插件的SSL系统变量clone_ssl_ca的值。
    group_replication_recovery_ssl_capath:包含受信任的SSL证书颁发机构(CA)证书文件的目录的路径名。
  • group_replication_recovery_ssl_cert:用于分布式恢复连接的SSL公钥证书文件的路径名。组复制会根据此系统变量的值自动配置为克隆插件的SSL系统变量clone_ssl_cert的值。
  • group_replication_recovery_ssl_key:用于分布式恢复连接的SSL私有密钥文件的路径名。组复制会根据此系统变量的值自动配置为克隆插件的SSL系统变量clone_ssl_key的值。
  • group_replication_recovery_ssl_verify_server_cert:使分布式恢复连接检查donor节点发送的证书中Server的公共名称值。将此系统变量设置为ON与系统变量group_replication_ssl_mode设置为VERIFY_IDENTITY值等效。
  • group_replication_recovery_ssl_crl:包含证书撤销列表的文件的路径名。
  • group_replication_recovery_ssl_crlpath:包含证书撤销列表的目录的路径名。
  • group_replication_recovery_ssl_cipher:用于分布式恢复连接的允许的加密算法列表。可以指定单个,也可以指定多个(多个算法之间用冒号分隔)。
    为组复制的分布式恢复连接配置SSL的示例如下:
new_member> SET GLOBAL group_replication_recovery_use_ssl=1;
new_member> SET GLOBAL group_replication_recovery_ssl_ca= '.../cacert.pem';
new_member> SET GLOBAL group_replication_recovery_ssl_cert= '.../client-cert.pem';
new_member> SET GLOBAL group_replication_recovery_ssl_key= '.../client-key.pem';

14 MGR在线配置

组复制处于运行状态时,可以使用一组依赖于组操作协调器的UDF自定义函数来对组做一些在线变更操作。这些UDF 由8.0.13或更高版本的MGR插件提供。本节描述如何使用这些UDF自定义函数来对组进行在线进行一些变更操作。

  • 注意:要使协调器能够在运行中的组中有效执行组范围内的配置变更操作,所有成员必须运行在MySQL 8.0.13或更高版本中,且配置安装好了UDF(加载好了MGR插件)。

要使用UDF,使用客户端程序连接到组中的任意成员,并使用SELECT语句执行UDF调用。MGR插件会处理该操作及其相关的参数调整,协调器会将此操作发送给组中所有成员(执行此操作能够看到的所有组成员)。如果该操作被接受,所有成员都将执行该操作。一旦所有成员都声明操作已完成时,调用UDF的成员会将执行结果返回给客户端。

在配置整个组时,操作的分布式特性意味着它们与MGR插件有许多进程交互,因此需要注意以下几点:

  • 您可以在任何地方执行配置操作:假设您想让组成员A成为新的主要节点,在组成员A上执行UDF的调用操作不是必须的。因为,操作会以协调的方式在所有组成员上发送和执行,因此,在任意一个组成员中执行调用都可以。此外,因为操作是分布式的,所以执行的状态在某一时刻可能在每个组成员中不相同(有不同的状态分支),例如:如果执行调用UDF的组成员宕机,则任何已经执行的配置操作将继续在其他组成员上执行。因此,即使在执行UDF调用的组成员宕机的情况下,仍然可以使用相关的监控功能来查看并确保其他组成员成功完成调用操作。

  • 所有组成员必须在线:为了简化迁移或选举的过程,并确保它们尽可能快地执行完成,组不能包含正处于分布恢复过程中的任何成员,否则组成员将直接拒绝执行UDF来更改组的配置。

  • 在配置变更期间,不能有任何Server正在申请加入组:在协调配置变更期间尝试申请加入组的任何Server都会被拒绝,并终止其加入组的过程。

  • 同一时间内只允许执行一个配置变更操作:正在执行配置变更的组不能接受任何其他的组配置变更操作,因为并发配置操作可能导致组中的成员出现分裂。

  • 组中的所有成员必须运行MySQL 8.0.13或更高版本:由于配置操作的分布式特性,组中的所有成员必须识别(支持)配置操作。因此,如果组中存在运行MySQL 8.0.12或更低版本的成员时,则会拒绝执行配置操作。

4.1.1. 修改组的主要节点
本节介绍在单主模式的组中如何使用UDF自定义函数来更改主要节点。用于更改组中主要节点的函数可以在任何成员上运行。
通过使用group_replication_set_as_primary() UDF来更改单主模式的组中的主要节点(如果组运行在多主模式下,则执行该函数不会有任何影响)。单主模式的组中,只有主要节点才允许数据写入(其他成员为辅助节点,只能接受只读请求),因此,如果在该成员(主要节点)上正在运行异步通道(这里指的是主要节点还同时作为主从复制拓扑中的从库),则在停止异步通道之前不允许切换主要节点。
如果在MySQL 8.0.17或以上版本的组成员上执行UDF调用,且组中所有成员都运行在MySQL 8.0.17或以上版本,那么只能基于补丁版本(次要版本,例如:MySQL 8.0.17中,8.0是主要版本,17是次要版本)指定一个组中版本号最低的MySQL服务器作为新的主要节点。此保护措施用于确保组中新功能特性的兼容性。但,如果任何成员运行在MySQL 8.0.13和MySQL 8.0.16之间的版本,则不会对组强制执行此保护措施,可以指定任何成员为主要节点,但为了避免出现一些不兼容的意外发生,建议选择组中版本号最低的成员作为主要节点。
修改组中的主要节点时,使用group_replication_set_as_primary函数并指定将要切换为主要节点的成员server_uuid,如下:

# 查询组成员的状态信息,可以看到当前node1为主要节点(MEMBER_ROLE列值为PRIMARY)

root@localhost : performance_schema:55: > select * from replication_group_members;

+---------------------------+--------------------------------------+-------------+-------------+--------------+-------------+----------------+

| CHANNEL_NAME | MEMBER_ID | MEMBER_HOST | MEMBER_PORT | MEMBER_STATE | MEMBER_ROLE | MEMBER_VERSION |

+---------------------------+--------------------------------------+-------------+-------------+--------------+-------------+----------------+

| group_replication_applier | 2d283e92-de7b-11e9-a14d-525400c33752 | node2 | 3306 | ONLINE | SECONDARY | 8.0.17 |

| group_replication_applier | 2e33b2a7-de7b-11e9-9a21-525400bdd1f2 | node3 | 3306 | ONLINE | SECONDARY | 8.0.17 |

| group_replication_applier | 320675e6-de7b-11e9-b3a9-5254002a54f2 | node1 | 3306 | ONLINE | PRIMARY | 8.0.17 |

+---------------------------+--------------------------------------+-------------+-------------+--------------+-------------+----------------+

3 rows in set (0.01 sec)

# 指定node2为新的主要节点

root@localhost : performance_schema:55: > SELECT group_replication_set_as_primary('2d283e92-de7b-11e9-a14d-525400c33752');

+--------------------------------------------------------------------------+

| group_replication_set_as_primary('2d283e92-de7b-11e9-a14d-525400c33752') |

+--------------------------------------------------------------------------+

| Primary server switched to: 2d283e92-de7b-11e9-a14d-525400c33752 |

+--------------------------------------------------------------------------+

1 row in set (0.03 sec)

# 再次查看组成员状态信息,可以发现node2已经被切换为了主要节点

root@localhost : performance_schema:56: > select * from replication_group_members;

+---------------------------+--------------------------------------+-------------+-------------+--------------+-------------+----------------+

| CHANNEL_NAME | MEMBER_ID | MEMBER_HOST | MEMBER_PORT | MEMBER_STATE | MEMBER_ROLE | MEMBER_VERSION |

+---------------------------+--------------------------------------+-------------+-------------+--------------+-------------+----------------+

| group_replication_applier | 2d283e92-de7b-11e9-a14d-525400c33752 | node2 | 3306 | ONLINE | PRIMARY | 8.0.17 |

| group_replication_applier | 2e33b2a7-de7b-11e9-9a21-525400bdd1f2 | node3 | 3306 | ONLINE | SECONDARY | 8.0.17 |

| group_replication_applier | 320675e6-de7b-11e9-b3a9-5254002a54f2 | node1 | 3306 | ONLINE | SECONDARY | 8.0.17 |

+---------------------------+--------------------------------------+-------------+-------------+--------------+-------------+----------------+

3 rows in set (0.00 sec)

当操作运行时,您可以通过执行以下语句来检查其进度。

# 注:events_stages_current表是记录线程当前正在执行的事件信息,所以只有线程正在执行某个语句时才能够查询到事件信息,一旦线程执行完成,事件信息就会被清除

root@localhost : performance_schema:58: > SELECT event_name, work_completed, work_estimated FROM performance_schema.events_stages_current WHERE event_name LIKE "%stage/group_rpl%";

+----------------------------------------------------------------------------------+----------------+----------------+

| event_name | work_completed | work_estimated |

+----------------------------------------------------------------------------------+----------------+----------------+

| stage/group_rpl/Primary Election: Waiting for members to turn on super_read_only | 3 | 5 |

+----------------------------------------------------------------------------------+----------------+----------------+

4.1.2. 修改组的运行模式
本节介绍了如何修改组的运行模式,无论是单主模式还是多主模式。用于修改组运行模式的函数可以在组中任何成员上运行。但是。

  • 如果组已经处于单主模式,虽然可以调用group_replication_switch_to_single_primary_mode() UDF,但是没有任何作用,会返回提示信息:"Already in single-primary mode. Did you mean to use group_replication_set_as_primary?"

  • 如果组已经处于多主模式,虽然可以调用group_replication_switch_to_multi_primary_mode() UDF,但是仍然没有任何作用,会返回提示信息:"The group is already on multi-primary mode. "

4.1.2.1. 切换到多主模式
假设组已经处于单主模式,使用group_replication_switch_to_multi_primary_mode() UDF执行如下语句,将单主模式下运行的组修改为多主模式:

# 查查看组成员的状态信息,可以发现目前组处于单主模式(有一个PRIMARY成员,2个SECONDARY成员)

root@localhost : performance_schema:30: > select * from replication_group_members;

+---------------------------+--------------------------------------+-------------+-------------+--------------+-------------+----------------+

| CHANNEL_NAME | MEMBER_ID | MEMBER_HOST | MEMBER_PORT | MEMBER_STATE | MEMBER_ROLE | MEMBER_VERSION |

+---------------------------+--------------------------------------+-------------+-------------+--------------+-------------+----------------+

| group_replication_applier | 2d283e92-de7b-11e9-a14d-525400c33752 | node2 | 3306 | ONLINE | PRIMARY | 8.0.17 |

| group_replication_applier | 2e33b2a7-de7b-11e9-9a21-525400bdd1f2 | node3 | 3306 | ONLINE | SECONDARY | 8.0.17 |

| group_replication_applier | 320675e6-de7b-11e9-b3a9-5254002a54f2 | node1 | 3306 | ONLINE | SECONDARY | 8.0.17 |

+---------------------------+--------------------------------------+-------------+-------------+--------------+-------------+----------------+

3 rows in set (0.00 sec)

# 查看组模式系统变量值,发现已经被启用,组处于单主模式下

root@localhost : performance_schema:12: > show variables like '%group_replication_single_primary_mode%';

+---------------------------------------+-------+

| Variable_name | Value |

+---------------------------------------+-------+

| group_replication_single_primary_mode | ON |

+---------------------------------------+-------+

1 row in set (0.01 sec)

# 执行如下语句切换到多主模式(不能指定组成员的系统变量server_uuid值作为参数,否则报错:"ERROR 1123 (HY000): Can't initialize function 'group_replication_switch_to_multi_primary_mode'; Wrong arguments: This function takes no arguments.")

root@localhost : performance_schema:38: > SELECT group_replication_switch_to_multi_primary_mode();

+--------------------------------------------------+

| group_replication_switch_to_multi_primary_mode() |

+--------------------------------------------------+

| Mode switched to multi-primary successfully. |

+--------------------------------------------------+

1 row in set (1.02 sec)

# 再次查看组成员状态信息,可以发现3个成员的MEMBER_ROLE列值都为PRIMARY,表示此时3个组成员都可读写

root@localhost : performance_schema:39: > select * from replication_group_members;

+---------------------------+--------------------------------------+-------------+-------------+--------------+-------------+----------------+

| CHANNEL_NAME | MEMBER_ID | MEMBER_HOST | MEMBER_PORT | MEMBER_STATE | MEMBER_ROLE | MEMBER_VERSION |

+---------------------------+--------------------------------------+-------------+-------------+--------------+-------------+----------------+

| group_replication_applier | 2d283e92-de7b-11e9-a14d-525400c33752 | node2 | 3306 | ONLINE | PRIMARY | 8.0.17 |

| group_replication_applier | 2e33b2a7-de7b-11e9-9a21-525400bdd1f2 | node3 | 3306 | ONLINE | PRIMARY | 8.0.17 |

| group_replication_applier | 320675e6-de7b-11e9-b3a9-5254002a54f2 | node1 | 3306 | ONLINE | PRIMARY | 8.0.17 |

+---------------------------+--------------------------------------+-------------+-------------+--------------+-------------+----------------+

3 rows in set (0.01 sec)

# 再次查看组模式系统变量,发现已经被关闭,组处于多主模式下

root@localhost : performance_schema:10: > show variables like '%group_replication_single_primary_mode%';

+---------------------------------------+-------+

| Variable_name | Value |

+---------------------------------------+-------+

| group_replication_single_primary_mode | OFF |

+---------------------------------------+-------+

1 row in set (0.00 sec)

上述步骤中,调用group_replication_switch_to_multi_primary_mode() 函数之后,组内经过一些能够确保数据的安全性和一致性的协调操作之后,组中所有的成员都成为了主要节点。

  • PS:当将单主模式运行的组更改为以多主模式运行时,如果组中存在MySQL 8.0.17或更高版本的成员,且高于组中所有成员的最低版本时,会自动将MySQL 8.0.17或更高版本的成员置于只读模式。运行在MySQL 8.0.16或更低版本的成员不执行此检查,且始终处于读写模式。所以,在MySQL 8.0.17或更高版本中,多主模式下不一定是所有成员都会处于读写模式,如果你的组中使用了多个版本的MySQL Server,则需要留意。
    当函数运行过程中(未执行完成),您可以通过执行如下语句来检查其执行进度。
root@localhost : (none):48: > SELECT event_name, work_completed, work_estimated FROM performance_schema.events_stages_current WHERE event_name LIKE "%stage/group_rpl%";

+----------------------------------------------------------------------+----------------+----------------+

| event_name | work_completed | work_estimated |

+----------------------------------------------------------------------+----------------+----------------+

| stage/group_rpl/Multi-primary Switch: applying buffered transactions | 0 | 1 |

+----------------------------------------------------------------------+----------------+----------------+

4.1.2.2. 切换到单主模式
假设组已经处于多主模式,使用group_replication_switch_to_single_primary_mode() UDF将运行在多主模式的组更改为单主模式,当切换到单主模式时,同时还会禁用所有组成员上的严格一致性检查(group_replication_mandatory _update_everywhere_check =OFF),因为单主模式要求组关闭严格一致性检查。
在调用group_replication_switch_to_single_primary_mode() UDF时,可以为其指定一个成员的server_uuid字符串作为参数,这样指定的成员将成为新的主要节点,如果未指定,则将自动根据选举策略选出新的主要节点。示例如下:

# 先查看一下组成员的状态信息,可以发现此时3个成员都为主要节点

root@localhost : performance_schema:40: > select * from replication_group_members;

+---------------------------+--------------------------------------+-------------+-------------+--------------+-------------+----------------+

| CHANNEL_NAME | MEMBER_ID | MEMBER_HOST | MEMBER_PORT | MEMBER_STATE | MEMBER_ROLE | MEMBER_VERSION |

+---------------------------+--------------------------------------+-------------+-------------+--------------+-------------+----------------+

| group_replication_applier | 2d283e92-de7b-11e9-a14d-525400c33752 | node2 | 3306 | ONLINE | PRIMARY | 8.0.17 |

| group_replication_applier | 2e33b2a7-de7b-11e9-9a21-525400bdd1f2 | node3 | 3306 | ONLINE | PRIMARY | 8.0.17 |

| group_replication_applier | 320675e6-de7b-11e9-b3a9-5254002a54f2 | node1 | 3306 | ONLINE | PRIMARY | 8.0.17 |

+---------------------------+--------------------------------------+-------------+-------------+--------------+-------------+----------------+

3 rows in set (0.00 sec)

# 查看组模式系统变量,发现此时单主模式被关闭,组处于多主模式下

root@localhost : performance_schema:10: > show variables like '%group_replication_single_primary_mode%';

+---------------------------------------+-------+

| Variable_name | Value |

+---------------------------------------+-------+

| group_replication_single_primary_mode | OFF |

+---------------------------------------+-------+

1 row in set (0.00 sec)

# 执行模式切换,指定一个组成员的server_uuid作为group_replication_switch_to_single_primary_mode() UDF的参数

root@localhost : performance_schema:07: > SELECT group_replication_switch_to_single_primary_mode('320675e6-de7b-11e9-b3a9-5254002a54f2');

+-----------------------------------------------------------------------------------------+

| group_replication_switch_to_single_primary_mode('320675e6-de7b-11e9-b3a9-5254002a54f2') |

+-----------------------------------------------------------------------------------------+

| Mode switched to single-primary successfully. |

+-----------------------------------------------------------------------------------------+

1 row in set (0.02 sec)

# 再次查看组成员的状态信息,可以发现指定的server_uuid(mode1)成员成为了主要节点

root@localhost : performance_schema:08: > select * from replication_group_members;

+---------------------------+--------------------------------------+-------------+-------------+--------------+-------------+----------------+

| CHANNEL_NAME | MEMBER_ID | MEMBER_HOST | MEMBER_PORT | MEMBER_STATE | MEMBER_ROLE | MEMBER_VERSION |

+---------------------------+--------------------------------------+-------------+-------------+--------------+-------------+----------------+

| group_replication_applier | 2d283e92-de7b-11e9-a14d-525400c33752 | node2 | 3306 | ONLINE | SECONDARY | 8.0.17 |

| group_replication_applier | 2e33b2a7-de7b-11e9-9a21-525400bdd1f2 | node3 | 3306 | ONLINE | SECONDARY | 8.0.17 |

| group_replication_applier | 320675e6-de7b-11e9-b3a9-5254002a54f2 | node1 | 3306 | ONLINE | PRIMARY | 8.0.17 |

+---------------------------+--------------------------------------+-------------+-------------+--------------+-------------+----------------+

3 rows in set (0.00 sec)

# 再次查看组模式系统变量值,发现已经被启用,组处于单主模式下

root@localhost : performance_schema:12: > show variables like '%group_replication_single_primary_mode%';

+---------------------------------------+-------+

| Variable_name | Value |

+---------------------------------------+-------+

| group_replication_single_primary_mode | ON |

+---------------------------------------+-------+

1 row in set (0.01 sec)

如果在运行MySQL 8.0.17及其更高版本的组成员上调用UDF,且所有成员都运行在MySQL 8.0.17或更高版本中,且组中存在不同版本的组成员时,则,只能基于补丁版本指定组中最低MySQL Server版本的成员做为主要节点。此保护措施用于确保组与新功能保持兼容性。如果没有为UDF指定新的主要节点,则选举过程中将会考虑组成员的补丁版本(自动选举组中最低版本的成员作为主要节点)。
如果任何成员正在运行MySQL 8.0.13和MySQL 8.0.16之间的MySQL Server版本,则不会对组强制执行此保护,可以指定任何新的组成员作为主要节点,但建议选择组中最低MySQL Server版本的成员作为主要节点。如果没有指定新的主要节点,则选举过程只考虑组成员的主要版本(选举最低主版本的成员作为主要节点)。

当函数运行过程中(未执行完成),您可以通过执行如下语句来检查其执行进度。

root@localhost : performance_schema:24: > SELECT event_name, work_completed, work_estimated FROM performance_schema.events_stages_current WHERE event_name LIKE "%stage/group_rpl%";

+----------------------------------------------------------------------------+----------------+----------------+

| event_name | work_completed | work_estimated |

+----------------------------------------------------------------------------+----------------+----------------+

| stage/group_rpl/Primary Switch: waiting for pending transactions to finish | 4 | 20 |

+----------------------------------------------------------------------------+----------------+----------------+

4.1.3. 设置组并发可写实例数
本节介绍如何检查和配置一个组中的最大并行可写实例数(即,并行在组成员之间广播传输的最大并发可写实例数)。这个最大值称为组的事件范围,可以通过调整该值来优化组复制的性能。例如:默认值10适用于在LAN上运行的组,但是对于在WAN等速度较慢的网络上运行的组,可以通过增加这个数字来提高性能。
检查组的写并发性:使用group_replication_get_write_concurrency() UDF在运行时检查组的事件范围值(注:该函数不需要传入参数),如下:

root@localhost : performance_schema:04: > select group_replication_get_write_concurrency();

+-------------------------------------------+

| group_replication_get_write_concurrency() |

+-------------------------------------------+

| 10 |

+-------------------------------------------+

1 row in set (0.00 sec)

配置组的写并发性:使用group_replication_set_write_concurrency() UDF设置组可以并行执行的可写实例的最大数量,如下:


root@localhost : performance_schema:04: > select group_replication_set_write_concurrency(100);

+-----------------------------------------------------------------------------------+

| group_replication_set_write_concurrency(100) |

+-----------------------------------------------------------------------------------+

| UDF is asynchronous, check log or call group_replication_get_write_concurrency(). |

+-----------------------------------------------------------------------------------+

1 row in set (0.00 sec)

PS:

  • 使用该UDF自定义函数配置组的写并发性(修改组中允许并行执行的可写实例的最大数量)需要用户具有GROUP_REPLICATION_ADMIN权限。
  • 默认的最大并发读写实例数为10,该函数的有效参数值为10~200。
    4.1.4. 设置组的通信协议版本
    从MySQL 8.0.16开始,组复制就有了组通信协议的概念。可以显式地管理组复制通信协议的版本,并将其设置为你希望组支持的最老的MySQL Server版本号。这使得组允许由运行在不同MySQL Server版本的成员组成,同时确保向后兼容性。MySQL 5.7.14的版本允许压缩消息(这里的消息可以简单理解为MySQL通讯协议中对客户端与服务端之间按照不同的协议传输的数据的一种抽象概念),MySQL 8.0.16的版本也允许消息分段。组的所有成员必须使用相同的通信协议版本,这样使用不同MySQL Server版本的组成员之间才能够发送与接收相互都能理解的消息。
    假如一个MySQL Server的版本为X,则它只能加入通讯协议版本小于等于X的组。当有新的Server申请加入组时,它将检查组中声明的现有通讯协议版本,如果新加入成员支持该版本(符合版本规则),则该成员加入组且使用组中已声明的通讯协议版本(即使新加入组的成员版本更高,支持更多的额外功能也是如此,因为需要保证组中成员之间的兼容性),如果新加入成员不支持组声明的通讯协议版本,则加入组将会失败。
    只有新加入组的成员的通讯协议版本与组中声明的通讯协议版本兼容时,才允许加入组。如果存在多个成员同时加入组时,且他们的通讯协议版本与组中声明的通讯协议版本相同,则允许同时加入组,否则,他们只能串行加入组,具体的判断规则如下:
  • 一个MySQL 8.0.16版本的Server可以成功加入使用通信协议版本为5.7.24的组,因为组的通讯协议版本为5.7.24,小于新加入成员的版本8.0.16。
  • 一个MySQL 5.7.24版本的Server无法成功加入使用通讯协议版本为8.0.16的组,因为组的通讯协议版本为8.0.16,大于新加入成员的版本5.7.24。
  • 两个MySQL 8.0.16版本的Server不能同时加入使用通讯协议版本为5.7.24的组,因为组的通讯协议版本为5.7.24,小于(不等于)新加入成员的版本8.0.16。
  • 两个MySQL 8.0.16版本的Server可以同时加入使用通讯协议版本为8.0.16的组,因为组的通讯协议版本为8.0.16,等于新加入成员的版本8.0.16。
  • PS:
  • 为什么会存在这种版本限制呢?在单主模式下,组中最小版本的成员通常会被选举为主要节点,相当于主从复制拓扑中的主库,而其他更高版本的成员通常会置为辅助节点,相当于主从复制拓扑中的从库。在主从复制拓扑中,为了保证从库回放主库的二进制日志时能够向下兼容主库,所以从库的版本必须大于等于主库的版本。在一个正常运行的组中,一定存在着一个已经选举成功的写节点,而写节点通常是版本最低的(组中的通讯协议版本通常是以组中最低版本的成员为准),新加入一个成员就相当于在主从复制拓扑中新加入一个从库,所以,同理,在组复制拓扑中,辅助节点为了保证版本能够向下兼容主要节点,新加入的成员版本必须大于等于组中的通讯协议版本(组中所有成员中最低的MySQL Server版本) 。
  • 注意:通讯协议版本不意味着组中存在该版本的成员,例如:默认情况下,MySQL 版本为8.0.17的Server使用的是8.0.16的通讯协议版本,该协议版本表示该组当前声明的所能支持的最低MySQL Server版本号(即,小于该版本号的MySQL Server无法加入组·)。
    使用group_replication_get_communication_protocol() UDF检查组使用的通信协议,UDF返回组支持的最老的MySQL Server版本。组中所有现有成员中执行该UDF都会返回相同的通信协议版本。如下:
root@localhost : (none):56: > SELECT group_replication_get_communication_protocol();

+------------------------------------------------+

| group_replication_get_communication_protocol() |

+------------------------------------------------+

| 8.0.16 |

+------------------------------------------------+

1 row in set (0.00 sec)

如果需要更改组的通信协议版本,以便使用更早期版本的成员可以加入组,可以使用group_replication_set_communication_protocol() UDF指定你希望允许加入组的最老的MySQL Server版本。如果指定的旧版本成员成功加入组,则将使组退回到兼容的通信协议版本(最低版本的成员支持的通讯协议版本)。使用该UDF需要用户具有GROUP_REPLICATION_ADMIN权限,当执行该语句时,所有组中的现有成员都必须在线(组可用),如下:

# 注意:group_replication_get_communication_protocol () UDF返回的是组中支持的最低MySQL Server版本(组中声明的通讯协议版本),这可能与使用group_replication_set_communication_protocol () UDF设置组的通讯协议版本时传递给它的版本不同、也可能与组中的最低成员的MySQL Server版本不同(这一点上文中已经提到过了)

root@localhost : (none):32: > SELECT group_replication_set_communication_protocol("5.7.25");

+-----------------------------------------------------------------------------------+

| group_replication_set_communication_protocol("5.7.25") |

+-----------------------------------------------------------------------------------+

| The operation group_replication_set_communication_protocol completed successfully |

+-----------------------------------------------------------------------------------+

1 row in set (0.00 sec)

root@localhost : (none):32: > SELECT group_replication_get_communication_protocol();

+------------------------------------------------+

| group_replication_get_communication_protocol() |

+------------------------------------------------+

| 5.7.14 |

+------------------------------------------------+

1 row in set (0.00 sec)

如果将组中所有成员都升级到新的MySQL Server版本,则组不会自动将该组的通信协议版本升级到匹配最新的版本。必须使用group_replication_set_communication_protocol() UDF将通信协议版本设置为最新MySQL Server版本。如下:

root@localhost : (none):32: > select version();

+-----------+

| version() |

+-----------+

| 8.0.17 |

+-----------+

1 row in set (0.00 sec)

root@localhost : (none):35: > SELECT group_replication_set_communication_protocol("8.0.17");

+-----------------------------------------------------------------------------------+

| group_replication_set_communication_protocol("8.0.17") |

+-----------------------------------------------------------------------------------+

| The operation group_replication_set_communication_protocol completed successfully |

+-----------------------------------------------------------------------------------+

1 row in set (0.01 sec)

root@localhost : (none):35: > SELECT group_replication_get_communication_protocol();

+------------------------------------------------+

| group_replication_get_communication_protocol() |

+------------------------------------------------+

| 8.0.16 |

+------------------------------------------------+

1 row in set (0.00 sec)

group_replication_set_communication_protocol() UDF作为一个组操作实现,因此它同时在组的所有成员上执行。执行该函数过程中,组操作会缓冲消息并等待通讯协议版本修改完成之后再将缓冲的消息发送出去。如果某个Server在更改通信协议版本后尝试加入组,则组中的成员将使用最新的通讯协议版本来决定是否允许该Server加入组。

14 MGR事务一致性

对于组复制这样的分布式系统来说,主要的需求之一是它需要有数据的一致性保证。换句话说,需要保证在组成员之间分布的事务的全局同步的一致性。本节将介绍组复制如何根据组中发生的事件处理一致性保证、以及如何最佳配置组的一致性保证。

4.2.1. 理解事务一致性保证
就分布式一致性保证而言,组复制无论是在正操或者故障修复的操作中,它始终是一个最终一致性的系统。这意味着一旦传入组复制的流量减慢或停止,所有组成员将具有相同的数据内容。与系统一致性相关的事件可以分为:手动操作或由故障自动触发的控制操作、数据流操作。
对于组复制,与一致性相关的控制操作包括:

  • 添加和移除组成员,关于添加和移除组成员的数据一致性保证
  • 网络故障保护。
  • 主要节点故障转移:单主模式中,主要节点的故障转移也包括通过group_replication_set_as_primary() 函数触发的操作。
    主要节点故障转移一致性保护
  • 在单主模式的组中,如果主要节点发生故障转移,一个辅助节点被提升为新主要节点时,对于存在积压事务的情况下新的主要节点如何处理新写入的事务,有两种可选的处理方式:无论事务积压的大小如何,允许立即处理新写入的事务;在新的主要节点中进行访问限制,直到积压事务被应用完成才处理新写入的事务。
  • 使用第一种方式:在主要节点发生故障之后,为了确保组成员资格以最短的时间达到稳定(重新配置组视图),组会通过选举产生一个新的主要节点,然后新的主要节点在应用来自旧的主要节点的积压事务时,立即允许新的事务写入。这种方式能够确保写一致性,但是,在新的主要节点应用完成积压事务之前,读操作可能会查询到陈旧的数据。例如:如果客户端C1刚好在旧的主要节点发生故障之前成功写入了A=2 WHERE A=1(将A=1修改为A=2),则当客户端C1重新连接到新的主要节点时,新的主要节点应用完成积压事务并完全追赶上旧的主要节点脱离集群时的状态之前,它可能会读取到A=1。
  • 使用第二种方式:与第一种方式相比,在主要节点发生故障之后,仍然会通过组选举产生一个新的主要节点来保护稳定的组成员资格,但是,新的主要节点需要等待完全应用完成来自旧的主要节点中的积压事务之后,才允许新的事务写入。这就避免了客户端C1连接到新的主要节点时读取到陈旧的数据。不过,这样做的代价是,故障转移所需的时间可能会比较长(故障转移所需的时间与积压事务的大小成正比),在正确的配置均衡的组中,积压事务应该保持一个较小值。
  • 在MySQL 8.0.14之前,不支持配置故障转移策略,默认情况下采用可用性最大化的策略(第一种方式)。在MySQL 8.0.14及更高版本中可以使用系统变量group_replication_consistency配置组成员在主要节点故障转移期间提供的事务一致性保证级别。详情可参考"4.2.2. 配置事务一致性保证"。
    数据流操作
  • 数据流与组中读写操作的一致性保护有关,尤其是当这些读写操作分布在所有成员中时,数据一致性的保护尤其重要。数据流操作适用于单主模式和多主模式的组。在单主模式中,通常将只读事务和读写事务进行拆分,将读写事务路由到主要节点,将只读事务均匀分配给其余的辅助节点。对于组复制来说,可能存在多个数据库Server,但是,对应用访问来说,它应该作为一个整体对外提供服务,因此,理论上,对于应用来说,在主要节点上写入的数据可以在辅助节点上即时查询。尽管组复制是使用基于Paxos算法的组通讯系统(GCS)协议编写的,但是组复制的某些流程部分是异步的,这意味着主要节点写入的事务在辅助节点中是异步应用的,因此可能出现这样的情况:客户端C2在主要节点上写入B=2 where B=1(将B=1修改为B=2),然后,客户端C2立即连接到其他辅助节点中执行查询可能会读取到B=1,这是由于该事务在辅助节点中可能处于积压状态(在辅助节点中还未来得及应用)。
    事务同步点(位置)
  • 在读取或写入事务时你可以根据需要选择是否需要配置组的数据强一致性。如果配置了组的数据强一致性,则,只读事务不会读取到陈旧的数据,读写事务在非发起写事务的组成员中不会造成数据延迟(通过系统变量group_replication_consistency进行配置,详情可参考"4.2.2. 配置事务一致性保证")。
  • 如果需要实时读取数据,则可以配置为在读取时进行数据同步,当前客户端会话在执行只读事务时将等待一个给定的点位(应用完成所有之前的更新事务的时间点)之后才会真正开始执行。使用这种方法只影响当前会话,不影响所有其他并发数据操作(只读和读写事务)。
  • 如果在执行修改数据时实时同步给其他组成员,则可以配置为在写入时进行数据同步,当客户端会话执行读写事务时将一直等待,直到所有其他的辅助节点都已写入其数据。由于组复制对写入操作遵循的是全局顺序,因此,这意味着一个读写事务需要等待其他所有成员应用完成它们队列中所有先前写入的事务以及本次写入的事务。
  • 在读取时进行数据同步和在写入时进行数据同步,这两种方法都可以确保上述对于客户端C2在主要节点写入数据,然后再连接到辅助节点执行查询时无法读取到最新数据的问题。这两种选择各有其优缺点,这些优缺点与系统的工作负载直接相关,以下是一些根据不同的工作负载类型选择不同的数据同步方法的建议。
  • 在这些情况下,您应该选择在写入时进行同步。
  • 组中的访问读多写少,希望对读取操作进行负载均衡,从任何读成员中读取时都希望能够读取到最新的数据,且不对读负载均衡中的任何成员通过配置相关的限制来避免读取到陈旧的数据。
  • 组中的访问读多写少,希望在读写事务提交后就应用于所有组成员,以便后续的读取操作能够读取到最新的数据(包括发起事务写入的成员)。这样可确保每个RO事务执行不受到影响,而只影响RW事务的提交时长。
  • 在这些情况下,您应该选择在读取时进行同步。
  • 组中的访问写多读少,希望对读操作进行负载均衡,从任何读成员读取时都希望能够读取到最新的数据,且不对读负载均衡中的任何成员通过配置相关的限制来避免读取到陈旧的数据。
  • 希望工作负载中的特定事务始终从组中读取最新数据,例如:每当敏感数据被更新时(例如文件或类似数据的凭据)希望强制读取最新数据。
    4.2.2. 配置事务一致性保证
    在"4.2.1. 理解事务一致性保证"中的"务同步点(位置)"部分我们简单介绍了两个概念:事务同步点可以选择"读时同步"和"写时同步",但这是为了方便理解的一种简化术语,在组复制中使用的术语是:事务执行前(before)和执行后(after)。本节将介绍不同的一致性级设置对组处理的只读(RO)和读写(RW)事务的不同影响(不同的一致性级别设置,分别对应着RO和RW事务在执行前、执行后的不同的组合)。
    4.2.2.1. 一致性级别详解
    事务一致性级别由系统变量group_replication_consistency进行设置,可以在会话级别根据需要动态对某个事务进行设置,你可以使用这些不同设置值来灵活地为组复制中事务提供一致性保证。下面根据系统变量group_replication_consistency的有效值列表对一致性级别进行详细介绍。
    EVENTUAL:RO和RW事务在执行之前都不会等待前面的事务应用完成(即,事务直接执行,不等待积压事务应用完成)。这是group_replication_consistency变量的默认值(也是引入该系统变量之前组复制的默认行为)。RW事务不等待其他成员应用事务。意味着设置该值的成员中的事务可以先于其他成员外部化。还意味着,在发生主要节点故障转移时,新的主要节点不需要等待积压的事务(来自旧的主要节点的事务)立即接受新的RO和RW事务,这可能造成新的RO事务读取到陈旧的数据(因为之前旧主要节点中的最新数据还未同步到新的主要节点)、新的RW事务可能由于冲突导致回滚(冲突认证检测会发现新的RW事务可能与来自旧主要节点的积压RW事务发生冲突)。
  • EVENTUAL一致性级别的流程图如下。
    MYSQL MGR 从入门到精通 02

  • 结合上图对EVENTUAL一致性级别算法的简要描述。
    1. 事务T1(一致性级别为EVENTUAL)从组成员M1开始执行。
    1. 事务T1执行到提交点(commit)时,在这里会将事务的变更数据广播发送给所有组成员(包括发起事务T1的M1成员,对于M1来说,T1就是它的本地事务,对于M2和M3来说,T1就是它的远端事务)。
    1. 事务T1被组中的所有成员接收到之后,所有成员都会各自对其进行冲突认证检测:如果存在冲突,则对于M1来说,回滚T1事务,对于M2和M3来说,丢弃接收到的T1事务数据包;如果不存在冲突,则对于M1来说,提交T1事务,对于M2和M3来说,对T1事务进行排队等待执行与提交。
    1. 事务T2(一致性级别为EVENTUAL)从组成员M3上开始执行,紧接着,M3接收到T1事务的数据,这时,在M3中,T2事务不需要等待T1事务应用(提交)完成就可以继续往后执行,如果T2事务操作的数据与T1事务存在重叠,则T2读取到的数据就可能不是最新的,且对于T2事务来说,如果与T1事务存在冲突,还存在着被回滚的可能。
      BEFORE_ON_PRIMARY_FAILOVER:新RO或RW事务在新当选的主要节点应用完成来自旧的主要节点的积压事务之前,会被保持(不应用,类似于处在等待状态,积压事务被应用完成之后,才会处理新的RO和RW事务)。这确保当主要节点故障转移发生时,客户端总是能查询到发生故障的主要节点上的最新值,从而保证了一致性。但这意味着新的主要节点在应用积压事务过程中的延迟(这里指的是客户端访问新主要节点的响应延迟)客户端需要自行处理。通常这种延迟很小,但是实际延迟时间的长短取决于积压事务的大小。
      BEFORE:RW事务在应用(applied)之前会等待所有前面的事务(积压事务)完成。RO事务在执行(executed)之前会等待所有前面的事务(积压事务)完成。这样使得事务仅通过牺牲响应延迟就可以确保读取到最新的值。实际上,只是确保了RO事务上的同步,对于RW事务来说,只是等待了它之前积压的事务完成,并不会等待它在所有的其他组成员上完成应用(不过,由于RO事务要求同步,RO事务能够将一部分甚至大部分数据进行同步,所以能够一定程度上减少RW事务上的同步开销,也就是说,该一致性级别适合于写多读少的场景)。
  • BEFORE一致性级别的流程图如下。
    MYSQL MGR 从入门到精通 02

  • 结合上图对BEFORE一致性级别算法的简要描述。
    1. 事务T1(一致性级别为EVENTUAL)从组成员M1开始执行。
    1. 事务T1执行到提交点(commit)时,在这里会将事务的变更数据广播发送给所有组成员。
    1. 事务T1被组中的所有成员接收到之后,所有成员都会各自对其进行冲突认证检测:如果存在冲突,则对于M1来说,回滚T1事务,对于M2和M3来说,丢弃接收到的T1事务数据包;如果不存在冲突,则对于M1来说,提交T1事务,对于M2和M3来说,对T1事务进行排队等待执行与提交。
    1. 事务T2(一致性级别为BEFORE)从组成员M3上开始执行,在T2执行之前,会向所有组成员发送一条消息,该消息提供了T2事务的全局顺序(从上图中我们可以得知,T1事务的全局顺序在T2之前,因为T1事务先执行)。
    1. 组中的成员收到并按顺序处理该消息时,M3将从消息流中获取组复制应用程序的RECEIVED_TRANSACTION_SET(RECEIVED_TRANSACTION_SET是被允许提交的远程事务的集合,无论这些事务是否实际上已经提交,它们都包含在此集合中)。该集合提供了在T2事务之前存在的所有远程事务,由于Server已经确保了本地事务的一致性,所以这里只需要跟踪远程事务。尽管M3之前已经将包含了T2事务的全局顺序的消息发送给了组中的所有成员,但是只有M3才需要对其进行操作,因此其他组成员会丢弃该消息而不采取任何行动。
    1. 在M3中,在应用(提交)完成了RECEIVED_TRANSACTION_SET中所有的远程事务之后,事务T2才开始执行,这与可以确保T2不会读取或执行像你对于全局顺序(这里的全局顺序为:T1->T2)来说已经过时的数据。这种等待仅发生在执行了一致性级别为BEFORE的事务的Server上(这里为执行了一致性级别为BEFORE的T2事务的组成员M3),对于组中的其他组成员(这里指组成员M1和M2)不受此等待的影响。
    1. 一旦T2事务开始执行,接下来就会按照步骤2和步骤3继续往下执行。
      PS:对于BEFORE这个一致性级别,它涵盖了BEFORE_ON_PRIMARY_FAILOVER提供的一致性保证。
      AFTER:RW事务会等待它的更改被应用到所有其他成员。此一致性级别对RO事务没有影响(因为RO事务不会产生数据变更)。它只确保在本地成员上提交RW事务时,该RW事务的数据变更会在组中其他所有成员中应用,以便所有后续的事务在任何成员上都能够获取到最新的数据(通过确保只在RW事务上使用同步,RW事务会将所有写入的新数据都实时同步到组中其他的所有成员中,这就减少了RO事务上的同步开销。也就是说,该一致性级别比较适合读多写少的场景)。
  • AFTER一致性级别的流程图如下。
    MYSQL MGR 从入门到精通 02

  • 结合上图对AFTER一致性级别算法的简要描述。
    1. 事务T1(一致性级别为AFTER)从组成员M1开始执行。
    1. 事务T1执行到提交点(commit)时,在这里会将事务的变更数据广播发送给所有组成员。
    1. 事务T1被组中的所有成员接收到之后,所有成员都会各自对其进行冲突认证检测:如果存在冲突,则对于M1来说,回滚T1事务,对于M2和M3来说,丢弃接收到的T1事务数据包;如果不存在冲突,则进入步骤4。
    1. 在其他组成员上(这里指M2和M3),T1事务被排队执行,一旦事务进入prepare阶段(即,数据在等待commit指令的存储引擎上持久化完成),它将向所有的成员发送ACK确认。
    1. 一旦所有的成员都接收到来自所有成员的ACK确认(对于M1来说,T1事务是由它自己发起,所以已经隐含确认了prepare状态),此时,所有成员都将继续执行T1事务的提交操作(commit)。
    1. 事务T2(一致性级别为EVENTUAL)从组成员M3开始执行,此时由于T1事务正在提交过程中(还未提交完成),所以T2事务会持续等待T1事务完成之后才开始执行,这样,就可以确保T1事务之后的任何事务都将读取到T1的数据变更(最新数据)。
    1. 一旦事务T2开始执行,接下来就会按照"EVENTUAL一致性级别算法的简要描述"中的步骤2和步骤3继续往下执行(注意,由于T2事务的一致性级别是EVENTUAL,所以T2事务的后续步骤不会按照"AFTER一致性级别算法的简要描述"中的步骤2和 步骤3往下执行)。
  • PS:对于AFTER这个一致性级别,它涵盖了BEFORE_ON_PRIMARY_FAILOVER提供的一致性保证。
    BEFORE_AND_AFTER:一致性级别要求最高,RW和RO事务执行时都要求数据同步,RW事务在执行时需要等待之前的积压事务应用完成,且需要等待自己的数据变更在其他所有组成员上都应用。RO事务在执行时需要等待之前的积压事务应用完成。该一致性级别适合对数据的读写一致性都要求高的场景。
  • BEFORE_AND_AFTER一致性级别的流程图如下。
    MYSQL MGR 从入门到精通 02

  • 结合上图对BEFORE_AND_AFTER一致性级别算法的简要描述。
  • BEFORE_AND_AFTER一致性级别下,同一事务会将BEFORE和AFTER一致性级别的算法结合在一起。
  • PS:对于BEFORE_AND_AFTER一致性级别,它涵盖了BEFORE_ON_PRIMARY_FAILOVER提供的一致性保证。
    一致性级别BEFORE和BEFORE_AND_AFTER都可以用于RO和RW事务。但AFTER一致性级别对RO事务没有影响,因为RO事务不会产生数据变更。
    4.2.2.2. 如何选择一致性级别
    不同的一致性级别有不同的适用场景。下面是一些不同一致性级别的适用场景建议:
  • 场景1:读多写少,且在不允许读取到陈旧的数据的情况下,还要求配置读负载均衡。在这种情况下,应该选择一致性级别为 AFTER。
  • 场景2:写多读少,且不允许读取到陈旧的数据。在这种情况下,你应该选择一致性级别为 BEFORE。
  • 场景3:工作负载中的特定事务(如:更新敏感数据)需要读取组的最新数据。在这种情况下,应该选择一致性级别为 BEFORE。
  • 场景4:读多写少,且希望RW事务一旦提交就会被同步到组中的其他任何成员,以便后续的RO事务都能够读取到最新数据,不会导致RO事务读取最新数据时产生同步开销。在这种情况下,应该选择一致性级别为 AFTER。
  • 场景5:读多写少,且希望RW事务一旦提交就会被同步到组中的其他任何成员,且希望后续的RW和RO事务总是能够读取到最新的数据,也不希望后续RO事务读取最新数据时产生同步开销。这种情况下,应该选择一致性级别为 BEFORE_AND_AFTER。
    您可以根据需要自行选择强制执行一致性级别的范围。要注意:如果将一致性级别设置为全局范围,则会对组性能产生负面影响。因此,除非必须,否则建议只在会话级别根据需要动态使用系统变量group_replication_consistency来配置组的一致性级别。
# 若要在当前会话上强制执行一致性级别,用如下语句设置
mysql> SET @@SESSION.group_replication_consistency= 'BEFORE';

# 要在所有会话上强制执行一致性级别,使用如下语句设置
mysql> SET @@GLOBAL.group_replication_consistency= 'BEFORE';

在特定会话上设置一致性级别的场景可能有如下一些:

  • 场景6:在该应用场景中,多数语句不需要强一致性,只有少数语句(例如:某一种类型的语句)要求强一致性。例如:关于访问某库表的权限修改,在此场景中,希望确保任何时候所有客户端都看到正确的权限。只需要在操作修改库表权限的会话中先执行set @@SESSION.group_replication_consistency= 'AFTER' 语句即可(其他任何操作不需要修改一致性级别,保持系统变量group_replication_consistency的全局值为"EVENTUAL")。

  • 场景7:与场景6中描述的相同的系统上,每天都要读取最新数据执行一次数据分析处理。只需要在执行该分析处理的会话中先执行set @@SESSION.group_replication_consistency=‘BEFORE’ 语句即可。

总而言之,除非必须,否则不需要全局针对所有事务都运行较高的一致性级别,特别是只有一部分事务有一致性级别要求的场景中。

注意:所有RW事务在组中都是全局排序的,所以,一旦在当前会话中设置会话级别的一致性级别为AFTER,则在该会话中执行RW事务时会等待其他成员应用完成该事务。也就是说,由于RW事务全局是排序的,而该RW事务是后发起的,所以,实际上等于还需要同时等待该RW事务之前所有的积压事务应用完成,而不仅仅只是该RW事务。

4.2.2.3. 一致性级别的影响
分类一致性级别的另一种方法是根据对组的影响进行划分,即一致性级别对其他成员的影响。
对于BEFORE一致性级别,除了在事务流上排序之外,它只影响本地成员,即,它不需要协调其他成员,也不影响其他成员的事务。换句话说,BEFORE一致性级别只影响使用该一致性级别的事务。
AFTER和BEFORE_AND_AFTER一致性级别对在其他成员上执行的并发事务具有副作用,当执行具有AFTER或BEFORE_AND_AFTER一致性级别的事务时,即使后续的事务是以EVENTUAL一致性级别运行的也仍然需要等待AFTER或BEFORE_AND_AFTER一致性级别的事务执行完成。对于其他成员也是如此。即,AFTER或BEFORE_AND_AFTER一致性级别会影响所有的ONLINE成员。
为了进一步说明这一点,假设一个组包含三个组成员:M1、M2、M3,在M1上执行了如下语句:

# 修改一致性级别为AFTER
mysql> SET @@SESSION.group_replication_consistency= AFTER;
# 执行一个INSERT语句
mysql> BEGIN;
mysql> INSERT INTO t1 VALUES (1); # 为了方便看到效果,这里最好是一个大事务
mysql> COMMIT;

然后,在应用上述事务时,对成员M2 执行如下语句:

# 修改一致性级别为EVENTUAL
mysql> SET SESSION group_replication_consistency= EVENTUAL;
# 执行DML事务,就可以发现M2执行的事务被阻塞,需要等待上述执行先执行完成。需要注意的是,如果在M2执行事务时,M1中的事务还没有被M2收到时,select语句是可以立即执行成功的,但如果M1中的事务被M2收到并进入队列之后,执行select ... for update语句也会被阻塞

只能在ONLINE状态的组成员中使用BEFORE、AFTER_AND_BEFORE一致性级别,试图在其他状态(其他状态包括:RECOVERING、OFFLINE、ERROR、UNREACHABLE)的成员上使用时会导致报错。
如果一个非EVENTUAL的一致性级别的事务执行时,持续等待且一直未返回,当达到了由统变量wait_timeout系配置的超时时间(默认为8小时)之后,会抛出ER_GR_HOLD_WAIT_TIMEOUT错误信息。

4.2.2.4. 一致性对选主的影响
本节描述在一个单主模式的主中,一致性级别是如何影响选主的(选举主要节点)。例如:组自动检测故障并调整处于活动状态的成员的视图,换句话说就是成员资格配置。此外,如果组以单主模式部署,每当组成员资格发生更改时,都会检查所有的组成员状态,以便检测组中是否仍然存在主要节点。如果没有,则从辅助节点成员列表中选择一个组成员提升为新的主要节点。这个过程就是选举一个辅助节点晋升为主要节点的过程。
当系统检测到故障并自动重新配置时,你也许希望一旦辅助节点晋升完成,则新的主要节点与旧的主要节点之间的数据状态相同。换句话说,你可能希望新的主要节点能够对外提供读写访问时,新的主要节点就已经应用完成了所有积压事务,即,一旦应用程序完成了故障转移到新的主要节点时,就不会读取或者修改陈旧的数据记录(即使是暂时的也不行)。
从MySQL 8.0.14版本开始,辅助节点晋升为主要节点之后,你可以指定新主要节点的行为,通过新增的系统变量group_replication_consistency来控制新的主要节点采用什么一致性级别(默认为EVENTUAL),如果设置为BEFORE_ON_PRIMARY_FAILOVER,则在对外提供读写访问之前,会先应用完成积压事务。这就确保了客户端完成了故障转移到新主要节点之后,能够看到最新数据。同时,也可以防止出现下列不正常的现象:

  • RO和RW事务不会读取到陈旧的数据,这可以防止这些陈旧数据被应用程序访问到。
  • 不会导致新执行的RW事务发生荒谬的回滚,这是因为与复制事务(远程事务)存在写冲突的新的RW事务此时会处于待处理状态,需要先应用积压事务才会处理新的RW事务。
  • 读写事务不会发生读偏差(不会读取到陈旧的数据),如:
mysql> BEGIN;
# 假设x=1在t1表中,x=2还在积压事务中,那么,在这里需要等待积压事务应用完成,才能执行查询,以便读取到最新的数据x=2
mysql> SELECT x FROM t1; 
mysql> INSERT x INTO t2;
mysql> COMMIT;

以上事务如果不使用BEFORE_ON_PRIMARY_FAILOVER一致性级别,那么,将导致插入t2表中的值为x=1,而不是x=2(因为发生了读偏差),但是,无论是否设置为BEFORE_ON_PRIMARY_FAILOVER一致性级别,都不会导致写冲突,而最多只会发生读偏差,从而导致写入t2表的数据不是最新的。
为了确保组中所有的组成员无论谁被晋升为新的主要节点之后,都会提供相同的一致性级别,组的所有成员都应该在配置中持久化一致性级别为BEFORE_ON_PRIMARY_FAILOVER(或更高的一致性级别)。这可以防止应用程序故障转移完成之后查询到陈旧的数据,设置语句如下:


# 使用set语句将系统变量group_replication_consistency的值持久化为BEFORE_ON_PRIMARY_FAILOVER
root@localhost : (none):57: > SET PERSIST group_replication_consistency='BEFORE_ON_PRIMARY_FAILOVER';
Query OK, 0 rows affected (0.02 sec)

# 上述语句执行完成之后,该系统变量值会被持久化到auto.cnf文件中(该文件是一个JSON格式数组,且其中已经存在了组辅助的一些预设持久化变量,注意:对于使用 SET PERSIST语句持久化系统变量的操作,只会影响到当前成员,其他成员不会进行同步,所以,建议在所有组成员中都执行相同的操作)
[root@node1 ~]# cat /data//mysqldata1/mydata/mysqld-auto.cnf  
{ "Version" : 1 , "mysql_server" : { "group_replication_consistency" : { "Value" : "BEFORE_ON_PRIMARY_FAILOVER" , "Metadata" : { "Timestamp" : 1569841133777625 , "User" : "root" , "Host" : "localhost" } } , "mysql_server_static_options" : { "group
_replication_enforce_update_everywhere_checks" : { "Value" : "OFF" , "Metadata" : { "Timestamp" : 1569402731795015 , "User" : "mysql.session" , "Host" : "localhost" } } , "group_replication_single_primary_mode" : { "Value" : "ON" , "Metadata" : {
"Timestamp" : 1569402731795762 , "User" : "mysql.session" , "Host" : "localhost" } } } } }

尽管在使用BEFORE_ON_PRIMARY_FAILOVER一致性级别时,在未应用完成所有的积压事务之前,所有的写操作都会进入待处理状态,但并不是所有的读操作都被阻塞,对于一些不修改数据的查询是允许执行的(例如:对于一些状态表的查看等,这对一些问题排查和性能监控非常有用)。

  • SHOW 语句

  • SET 语句

  • DO 语句

  • EMPTY 语句

  • USE 语句

  • 对perfor*
  • 对不指定表的用户自定义函数使用SELECT 语句

  • STOP GROUP_REPLICATION 语句

  • SHUTDOWN 语句

  • RESET PERSIST 语句

事务不能永远处于待处理状态(on-hold),如果处于该状态的时间超过系统变量wait_timeout设置的值,则会返回ER_GR_HOLD_WAIT_TIMEOUT错误信息。

15 MGR分布式恢复

每当一个Server新加入或重新加入一个复制组时,对于新加入的Server,它必须要追平组中的最新数据,对于重新加入的Server,它必须要追平它脱离组之后的最新数据。这个追平最新数据的过程称为分布式恢复。
申请加入组的Server首先检查其组复制通道group_replication_applier对应的中继日志,查看它已经从组中接收到了但尚未应用的任何事务。如果是重新加入组的Server,那么它可能在脱离组时存在着未应用完成的事务,在这种情况下,它将第一步应用这些事务,如果是新加入组的Server则不存在这种情况,所以在这一步没有任何东西需要应用。
之后,申请加组的Server会与组中的现有成员建立连接进行状态传输。申请加入组的Server会从组中现有成员中(提供状态传输的组成员称为donor节点,接收状态传输的Server称为joiner节点)传输在其加入组之前或者在其脱离组之后组中的所有事务数据。然后,申请加入组的Server将应用在状态传输过程中组内新事务写入的数据。当这个过程完成时,就表示申请加入组的Server已经赶上了组中其余成员中的数据,此时,新加入组的Server就会转换为ONLINE状态,并开始正常地参与组中的各项工作。
在分布式恢复期间,组复制使用如下方法的组合进行状态传输:

  • 使用克隆插件功能进行远程克隆操作,该功能在MySQL 8.0.17中引入。要启用此状态传输方法,所有成员(包括已经在组中的成员和待加入Server)必须使用该版本,且必须在所有成员中都加载克隆插件。加载克隆插件之后,组复制会自动配置所需的克隆插件设置并管理远程克隆操作(注意,该操作针对于还尚未加入组的Server有效,已经加入组中的成员不能使用克隆功能来传输数据,另外,克隆插件的安装要求和安装、克隆操作步骤,详见链接:
  • https://dev.mysql.com/doc/refman/8.0/en/clone-lugin.html
  • 从donor节点的二进制日志中复制事务数据并将其应用于joiner节点。此方法使用一个名为group_replication_recovery的标准异步复制通道,该通道是建立在donor节点与joiner节点之间建立的。
  • 在joiner节点上执行START GROUP_REPLICATION语句后,组复制将自动选择这些方法的最佳组合进行状态传输。为此,组复制会检查组中哪些现有成员适合作为donor节点,joiner节点需要从donor节点获取多少事务,以及joiner节点所需的事务在组中的所有成员的二进制日志中是否存在。如果joiner节点与donor节点之间的事务差距很大,或者joiner节点所需的某些事务在组中的所有成员的二进制日志中都不存在,则组复制将通过远程克隆操作执行分布式恢复。如果joiner节点与donor节点之间的事务差距不大,或者没有安装克隆插件,则组复制直接使用donor节点的二进制日志进行状态传输。
  • 在远程克隆操作期间,joiner节点中的现有数据将被删除,并替换为donor节点的数据副本。当远程克隆操作完成且joiner节点完成重新启动时,将继续使用来自donor节点的二进制日志执行状态传输,以获取在执行远程克隆操作期间组中新产生的事务数据。
    在从donor节点的二进制日志进行状态传输期间,joiner节点会从来自donor节点的二进制日志中复制并应用所需的事务,直到将joiner节点记录到二进制日志为止(当joiner节点成功加入组时,二进制日志中会记录视图更改事件)。在此过程中,joiner节点会缓冲组中的新事务。当完成从二进制日志的状态传输时,joiner节点会应用这些缓冲事务。
    当joiner节点追赶上组中的最新数据时,它将声明已经处于ONLINE状态,并可以作为正常成员参与组中的各项工作,至此,分布式恢复完成。
    PS:如果joiner节点与donor节点之间的事务差距很大,或者joiner节点所需的某些事务在组中的所有成员的二进制日志中都不存在时,如果也未配置克隆功能,则,joiner节点将加入组失败。
    4.3.1. 克隆用于分布式恢复
    MySQL 8.0.17版本中引入了克隆插件。如果希望在组复制中使用远程克隆的方式进行分布式恢复,则必须对组中的现有成员和joiner节点进行预先设置。如果不进行相应的设置,则组复制只能使用二进制日志进行状态传输。
    要使用克隆功能,必须对组中的至少一个现有成员和joiner节点进行预先设置,以支持远程克隆操作。即,至少需要在donor节点和joiner节点上安装克隆插件,创建一个具有BACKUP_ADMIN权限的复制用户用于分布式恢复,并将系统变量group_replication_clone_threshold设置为适当量级的数值(默认情况下为GTID序列允许的最大值,表示正常情况下,始终优先使用基于二进制日志的状态传输,除非joiner节点所请求的事务在组中任意成员中都不存在,这个时候,如果设置好了克隆功能,则无论该系统变量的值设置为多少,都会触发通过克隆的方式进行分布式恢复,例如:全新初始化的Server申请加入组时。如果不希望使用克隆功能,则不要对其进行安装与配置)。为了确保donor节点的最大可用性,建议在组中所有的现有成员和joiner节点中都设置好克隆功能,以便后续有Server加入组时能够使用远程克隆操作来快速追赶组中的最新数据。
    请注意,远程克隆操作在从donor节点执行传输数据之前会删除掉joiner节点中用户创建的数据和表空间。如果中途远程克隆操作意外终止,则该joiner节点中的用户数据可能已经被清空或者只剩下残留数据,无法重新启动实例。这个时候,可以通过重试远程克隆操作来修复此问题(这里主要针对远程克隆时使用DATA DIRECTORY子选项指定了一个数据保存路径的情况,指定路径时,数据会保存在指定的目录下,即克隆之后的数据与操作克隆的实例没有关联,需要手动启动实例并指定datadir到保存克隆数据的目录进行启动),当然,MGR插件可以自动执行远程克隆的重试操作(需要保证克隆操作不指定DATA DIRECTORY子选项,在这种情况下,远程克隆数据会覆盖掉操作远程克隆的Server数据,完成远程克隆操之后,操作远程克隆的Server会基于克隆数据自动重新启动)。另外,克隆插件虽然与组复制配合使用对组复制的管理维护来说更加自动化,但是,克隆插件不要求必须在组中运行(但MGR插件必须要安装)。
    4.3.1.1. 克隆的前提条件
    关于组复制中使用克隆功能,需要注意以下要点和区别:
  • donor节点(即,捐献者,指的是组中的现有成员)和joiner节点(即,接受者,指的是申请加入组的Server。也可以称为recipient节点)都必须安装并激活克隆插件。
  • donor节点和joiner节点必须在相同的操作系统平台上运行,并且必须有相同的MySQL Server版本(必须是MySQL 8.0.17及其以上版本才能支持克隆插件)。
  • 如果分布式恢复启用了SSL (group_replication_recovery_use_ssl=ON),则组复制会将此设置应用于远程克隆操作。组复制会自动配置克隆功能相关的SSL系统变量(clone_ssl_ca、clone_ssl_cert、clone_ssl_key),以匹配组复制分布式恢复相关的SSL系统变量(group_replication_recovery_ssl_ca、group_replication_recovery_ssl_cert、group_replication_recovery_ssl_key)的设置。
  • 不需要在joiner节点中为系统变量clone_valid_donor_list指定有效的donor节点列表。组复制会从现有的组成员中选择一个成员作为donor节点,然后自动配置该系统变量的值。注意,远程克隆操作使用MySQL Server的SQL协议主机地址(IP)和端口,而不是组成员之间的组通讯地址和端口。
  • 克隆插件具有许多系统变量(MySQL 8.0.17版本中为11个),可以用于管理远程克隆操作的网络负载和性能影响。但组复制并不负责管理与配置这些系统变量,因此,如果需要控制克隆插件的网络负载与克隆性能,需要手工查看克隆插件相关的状态变量,并使用克隆插件的系统变量进行相应的设置。注意,当使用远程克隆操作进行分布式恢复时,克隆插件的系统变量clone_enable_compression仅适用于远程克隆操作,不会影响组复制的压缩设置。
  • 远程克隆操作在joiner节点上执行,组复制使用内部mysql.session用户,该用户为内置用户,已经具有了CLONE_ADMIN权限,不需要对其进行设置。
  • 在donor节点上作为远程克隆操作的克隆用户,组复制会使用在配置组复制通道时配置的用于分布式恢复的复制用户。因此,必须在组中所有支持克隆功能的组成员上为该复制用户授予BACKUP_ADMIN权限。如果有joiner节点,也要为此Server的复制用户授予该权限,因为在该新Server加入组之后,也可能作为其他joiner节点的donor节点。要将此权限授予现有成员上的复制用户,可以使用如下语句执行(在执行赋权操作时,对于组内成员,可以在任意一个可读写的成员中执行即可,对于joiner节点,可以无需单独授予,如果要单独授予该权限,则需要保证不与组内的数据冲突,可以在授予该权限时使用sql_log_bin=0的方式临时关闭二进制日志记录功):
  • 
    GRANT BACKUP_ADMIN ON *.* TO rpl_user@'%';

**4.3.1.2. 克隆触发阈值**
当组成员设置了支持克隆时,会通过系统变量group_replication_clone_threshold指定的阈值(该阈值表示若干个事务)来判断在分布式恢复过程中是否需要使用远程克隆操作。如果donor节点的事务与joiner节点之间的事务差距大于此数字(组复制会根据组中的现有成员的系统变量gtid_execution中的GTID SET计算出它们之间的事务数量的差距是否超出了阈值),则在技术上可行的情况下,将使用远程克隆操作将donor节点的状态传输到joiner节点,无需事先手动将组的数据传输到joiner节点主机中,还可以让延迟非常大的组成员能够快速追赶上来。
系统变量group_replication_clone_threshold的默认设置非常高(GTID中事务的最大允许序列号),因此只要可以从二进制日志传输状态,组复制就不会使用克隆功能传输状态。要想让组复制在何时的时候使用远程克隆操作进行状态传输,可以根据具体情况对系统变量group_replication_clone_threshold设置合适的值。但是要注意,在组中有成员正在使用远程克隆操作进行状态传输的过程中,不要对系统变量group_replication_clone_threshold设置过低的阈值。因为,如果在进行远程克隆操作时组中存在着大量超过阈值的新的事务请求,则joiner节点在重新启动数据库进程后将再次触发远程克隆操作,并无限循环远程克隆操作。要避免这种情况,需要将该阈值设置为高于组内的最高并发事务请求数。
当无法从donor节点的二进制日志进行状态传输时,组复制会尝试执行远程克隆操作。此时会忽略系统变量group_replication_clone_threshold的阈值设置,例如,joiner节点所需的事务在任何现有组成员的二进制日志中都不可用(找不到)。组复制基于现有组成员的系统变量gtid_purged的GTID SET来进行比对。当任何现有组成员的二进制日志文件中都没有joiner节点所需的事务时,组复制会尝试执行远程克隆操作进行状态传输,且这种情况下无法通过系统变量group_replication_clone_threshold的阈值设置来停用克隆操作,因为在这种情况下,克隆是将组的状态传输到joiner节点的一个可行的替代方法。
**4.3.1.3. 克隆操作**
当为组成员以及待加入组的成员都设置好了克隆功能时,组复制会接管远程克隆操作。远程克隆操作过程可能需要一些时间才能完成,具体取决于数据的大小。有关克隆操作过程的监控信息,详情可留意后续克隆插件系列文章。

performance_schema.clone_progress表中记录了整个克隆操作的每一个阶段及其对应的阶段信息,每一个阶段会生成一行记录(注意,该表中只记录一次克隆操作的过程信息,下一次执行克隆操作时,上一次的信息会被覆盖)

admin@localhost : performance_schema:37: > select * from clone_progress;
+------+-----------+-----------+----------------------------+----------------------------+---------+------------+------------+------------+------------+---------------+
| ID | STAGE | STATE | BEGIN_TIME | END_TIME | THREADS | ESTIMATE | DATA | NETWORK | DATA_SPEED | NETWORK_SPEED |
+------+-----------+-----------+----------------------------+----------------------------+---------+------------+------------+------------+------------+---------------+
| 1 | DROP DATA | Completed | 2019-10-08 16:46:58.757964 | 2019-10-08 16:46:59.128436 | 1 | 0 | 0 | 0 | 0 | 0 |
| 1 | FILE COPY | Completed | 2019-10-08 16:46:59.128766 | 2019-10-08 16:47:16.857536 | 8 | 8429731840 | 8429731840 | 8430190882 | 0 | 0 |
| 1 | PAGE COPY | Completed | 2019-10-08 16:47:16.857737 | 2019-10-08 16:47:17.159531 | 8 | 0 | 0 | 785 | 0 | 0 |
| 1 | REDO COPY | Completed | 2019-10-08 16:47:17.159748 | 2019-10-08 16:47:17.460516 | 8 | 2560 | 2560 | 3717 | 0 | 0 |
| 1 | FILE SYNC | Completed | 2019-10-08 16:47:17.460788 | 2019-10-08 16:47:20.926184 | 8 | 0 | 0 | 0 | 0 | 0 |
| 1 | RESTART | Completed | 2019-10-08 16:47:20.926184 | 2019-10-08 16:47:28.623732 | 0 | 0 | 0 | 0 | 0 | 0 |
| 1 | RECOVERY | Completed | 2019-10-08 16:47:28.623732 | 2019-10-08 16:47:34.898453 | 0 | 0 | 0 | 0 | 0 | 0 |
+------+-----------+-----------+----------------------------+----------------------------+---------+------------+------------+------------+------------+---------------+
7 rows in set (0.00 sec)

performance_schema.clone_status表中记录了克隆操作的一些元数据信息,例如,donor节点地址信息,对应数据的二进制日志位置信息和GTID信息(注意,该表中只记录一次克隆操作的信息,下一次执行克隆操作时,该表中的信息会被覆盖)

admin@localhost : performance_schema:38: > select * from clone_status\G
1. row
ID: 1
PID: 0
STATE: Completed
BEGIN_TIME: 2019-10-08 16:46:58.758
END_TIME: 2019-10-08 16:47:34.898
SOURCE: 10.10.30.162:3306
DESTINATION: LOCAL INSTANCE
ERROR_NO: 0
ERROR_MESSAGE:
BINLOG_FILE: mysql-bin.000022
BINLOG_POSITION: 222104704
GTID_EXECUTED: 320675e6-de7b-11e9-b3a9-5254002a54f2:1-4,
aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:1-2771494
1 row in set (0.01 sec)


注意:当完成状态传输后,组复制将重新启动joiner节点的数据库进程以完成该过程。如果在joiner节点上设置了group_replication_start_on_boot=OFF,则在数据库进程重新启动完成之后必须再次手动执行START GROUP_REPLICATION语句启动组复制。如果在配置文件中设置了group_replication_start_on_boot=ON和启动组复制所需的其他设置,或者使用了SET PERSIST语句将group_replication_start_on_boot=ON和启动组复制所需的其他设置进行了持久化,则在数据库进程重启之后不需要进行干预,组复制会继续自动执行成员加入组并使其达到ONLINE状态的流程。

远程克隆操作会将donor节点的datadir下的各种数据文件克隆到joiner节点中(表中可能包含了一些配置信息及其用户数据等)。但保存在配置文件(如组复制本地地址配置等)中的组复制成员设置不会被克隆,也不会在joiner节点上做任何更改。即,组复制相关的配置需要自行配置好,不能跟组中的现有成员冲突,远程克隆操作只负责克隆数据文件,不会克隆配置信息(当然,如果某些配置信息保存在表里,对于克隆操作来说,也会被当做数据进行克隆)。
donor节点中用于组复制专用通道group_replication_recovery的用户凭证(复制用户和密码),在克隆操作完成之后,会被新成员使用,所以,该用户和密码及其权限必须在新成员中也有效。因此,所有组成员才能够使用相同的复制用户和密码通过远程克隆操作接收状态传输进行分布式恢复。但是,组复制会保留与使用SSL相关的组复制通道设置,这些设置对单个成员来说可以是惟一的(即,每个组成员使用不同的复制用户和密码)。如果使用了PRIVILEGE_CHECKS_USER帐户来帮助保护复制应用线程(从MySQL 8.0.18开始,可以创建一个具有特定权限的用户账号,然后将其指定为PRIVILEGE_CHECKS_USER帐户,这样可以防止将未经授权或意外将具有特权的账号用于组复制通道),则在克隆操作完成之后新加入成员不会使用该用户帐户作为组复制通道的用户。此时必须为组复制通道手工指定合适的复制用户。

关于PRIVILEGE_CHECKS_USER帐户的详细信息,详见链接:

https://dev.mysql.com/doc/refman/8.0/en/replication-privilege-checks.html

**4.3.2. 配置分布式恢复**
分布式恢复的复制用户
* 分布式恢复需要具有正确权限(replication slave权限)的复制用户,以便组复制可以在成员之间建立直接相连的复制通道。如果该复制用户还同充当远程克隆操作中的克隆用户,则在donor节点中该复制用户还必须具有远程克隆相关的正确权限(BACKUP_ADMIN权限)。有关设置此复制用户的说明,请参见 2.1.3节“用户凭证”。
手动状态传输
* 基于二进制日志进行状态传输是组复制执行分布式恢复的基本机制,如果复制组中的donor节点和joiner节点未设置克隆功能支持,那么基于二进制日志进行状态传输就是惟一可用的分布式恢复方法。由于基础二进制日志的状态传输是基于典型的异步复制的数据同步类型,因此,如果joiner节点中没有任何组的数据、或者与组中的数据差距太大(例如,数据取自很久之前的备份),则整个分布式恢复的过程可能需要很长时间,如果在组中任何成员中都找不到joiner节点所需的数据,则会导致joiner节点加入组失败。因此,在这种情况下,建议先手动从组中获取一份最新的数据快照,使用该快照数据先恢复到joiner节点中,基于该快照数据加入组可以最小化分布式恢复所花费的时间,并减少对donor节点的影响。
连接尝试次数
* 对于基于二进制日志的状态传输,组复制限制了joiner节点从可用的donor节点池(即,可作为donor节点的组成员列表)尝试获取donor节点的尝试次数。如果达到重试限制次数之后仍然没有成功与donor节点建立连接,则分布式恢复过程将报错终止。注意,该限制次数指的joiner节点可以重试连接donor节点的总次数。例如,如果组中有2个组成员可作为donor候选节点,且重试连接次数限制为4,则每个donor候选节点都可以被重试连接2次。
* 默认的连接重试限制是10。可以使用系统变量group_replication_recovery_retry_count进行设置,如下:

下面的语句将尝试重新连接donor节点的最大次数限制为5:

mysql> SET GLOBAL group_replication_recovery_retry_count= 5;

注意:对于远程克隆操作,此重试次数不适用。在开始尝试基于二进制日志的状态传输之前,组复制仅对每一个合适的donor候选节点做一次克隆操作连接尝试


连接尝试的时间间隔

* 对于基于二进制日志的状态传输,系统变量group_replication_recovery_reconnect_interval定义了分布式恢复过程中重新连接donor节点的时间间隔。注意,如果最大重试次数设置为4,且组内有2个donor候选节点,则,会先连续2次分别尝试连接这两个donor候选节点(不会使用系统变量group_replication_recovery_reconnect_interval设置的间隔时间,因为这两个donor候选节点之间并没有相互的强关联影响因素,所以没有必要在占满这两个donor候选节点之前就执行重试等待)。一旦joiner节点尝试与所有的donor候选节点都执行了连接尝试之后(假设这里2个donor候选节点都在同时做连接尝试),那么,比起donor候选节点数量来讲,多余的重试连接次数(4-2=2次)就会按照系统变量group_replication_recovery_reconnect_interval配置的时间间隔(单位秒)对分布式恢复程序进行休眠。

* 默认的连接重试间隔是60秒,可以使用如下语句进行动态修改。

以下语句将分布式恢复中donor节点的连接重试间隔设置为120秒

mysql> SET GLOBAL group_replication_recovery_reconnect_interval= 120;

对于远程克隆操作,此间隔时间不适用。开始尝试基于二进制日志的状态传输之前,组复制仅对每一个合适的donor候选节点做一次克隆操作连接尝试



标记joiner节点在线

* 当分布式恢复成功完成了从donor节点到joiner节点之间的状态传输时,joiner节点在组中就可以被标记为online状态并准备参与到组内的各项工作中。默认情况下,此操作是在joiner节点接收并应用完成了所有缺失的事务之后执行的。但,可以允许joiner节点在接收并验证完成所有缺失事务之后,应用它们之前,将joiner节点标记为online状态。可以在joiner节点中将系统变量group_replication_recovery_complete_at设置为TRANSACTIONS_CERTIFIED值来实现。

分布式恢复使用SSL身份验证

* 您可以选择将SSL用于组成员之间的分布式恢复连接。用于分布式恢复的SSL与用于普通组通信的SSL是分开配置的,后者由MySQL Server的SSL相关的系统变量和系统变量group_replication_ssl_mode共同设置。对于分布式恢复连接的SSL配置,使用专用的分布式恢复相关的SSL系统变量来配置相关的证书和密码。

* 默认情况下,分布式恢复连接不使用SSL。要启用此功能,可以通过系统变量group_replication_recovery_use_ssl=ON进行设置,并配置组复制的分布式恢复相关的SSL系统变量,并创建一个启用了SSL的专用用户,详情可参考"5.2. 组复制安全套接字层(SSL)支持"。

* 当为分布式恢复配置使用SSL时,组复制会将此设置应用于远程克隆操作,以及基于二进制日志的状态传输。组复制会自动配置克隆操作相关的SSL系统变量(即,clone_ssl_ca、clone_ssl_cert、clone_ssl_key等系统变量),以匹配相应的分布式恢复系统变量值(即,group_replication_recovery_ssl_ca、group_replication_recovery_ssl_cert和group_replication_recovery_ssl_key等系统变量)。

* 如果没有为分布式恢复配置使用SSL(即,group_replication_recovery_use_ssl=OFF),且组复制的复制用户是使用的caching_sha2_password插件(caching_sha2_password是MySQL 8.0中默认使用的身份认证插件)或sha256_password插件进行身份认证的,则会将RSA密钥对用于密码交换。在这种情况下,需要使用系统变量group_replication_recovery_public_key_path来指定RSA公钥文件,或使用系统变量group_replication_recovery_get_public_key设置秘钥文件从主要节点中获取,否则,分布式恢复操作将发生报错终止。

分布式恢复压缩

* 从MySQL 8.0.18开始,基于二进制日志的状态传输支持压缩功能。当网络带宽有限,且发送方(donor节点)需要向接收方(joiner节点)传输大量事务时,压缩功能有利于提高分布式恢复的效率。通过系统变量group_replication_recovery_compression_algorithm配置允许的压缩算法,通过系统变量group_replication_recovery_zstd_compression_level设置zstd压缩级别。

* 注意,这些压缩设置不适用于远程克隆操作。当使用远程克隆操作进行分布式恢复时,如果需要使用压缩功能,可以使用克隆插件的系统变量clone_enable_compression进行设置。

***4.3.3. 分布式恢复的容错能力
***
组复制有许多内置的容错措施,以确保在分布式恢复过程中出现任何问题时能够进行容错。
分布式恢复的donor节点是从组的当前视图中的在线成员列表中随机选择的一个合适的组成员。随机选择donor节点就意味着当多个Server同时申请加入组时,组中的同一个成员很可能不会被同时多次选中。从MySQL 8.0.17开始,对于基于二进制日志的状态传输,同时申请加入组的所有Server只选择同一个donor节点来提供状态传输,这个donor节点运行的MySQL Server的补丁版本要求比申joiner节点的Server版本低或相同(可以将donor节点类比为主库,joiner节点类比为从库,为了保证兼容性,主库的版本不能高于从库的版本)。但对于更早的版本,组中所有的在线成员都可以成为donor节点。对于基于远程克隆操作的状态传输,joiner节点只选择与自身运行相同Server版本的组成员作为donor节点(因为克隆操作是拷贝数据文件,不同版本之间可能存在着一些文件格式兼容性问题,要处理这些问题可能需要做升级与降级处理,而克隆不做这些处理)。注意,joiner节点在克隆操作结束之后重新启动数据库进程时,它将重新选择一个新的donor节点建立连接并执行基于二进制日志的状态传输,这个新的donor节点可能与用于远程克隆操作的原始donor节点不是同一个组成员。
在以下情况下,组复制检测到分布式恢复过程中的错误时,会自动切换到一个新的donor节点,并重试状态传输操作:
* 连接错误:在连接到候选donor节点时存在身份验证问题或其他问题。
* 复制错误:基于二进制日志的状态传输的复制线程(接收线程或应用线程)之一出现错误。因为这种状态传输方法使用了现有的MySQL主从复制基础架构,所以一些临时错误可能会在接收线程或应用程序线程中引起错误。
* 远程克隆操作错误:远程克隆操作失败或在执行完成之前意外停止。
* donor节点脱离组:在执行状态传输过程中,donor节点意外脱离组,或者在donor节点上停止组复制。
在上述情况下,donor节点发生错误之后,joiner节点将尝试重新选择donor节点。通过选择新的donor节点就可能避免之前joiner节点中发生的错误,从而保证能够继续执行分布式恢复。如果安装了克隆插件,则组复制在这种情况下将首先尝试使用支持克隆功能的合适的在线donor候选节点执行远程克隆操作。如果所有支持克隆的候选donor节点尝试远程克隆都失败了,则组复制会接着继续依次尝试所有合适的候选donor节点进行基于二进制日志的状态传输(如果可能的话)。
* 注意:对于远程克隆操作,在开始执从donor节点接收数据之前,会先删除joiner节点上的原始数据文件。所以,一旦远程克隆操作启动之后但未执行完成就被终止,则joiner节点上可能只剩下部分原始数据文件,或者原始数据文件完全被清空。此时,可以通过重试克隆操作来修复这种情况(组复制会自动执行该修复操作)。
在以下情况下,无法完成分布式恢复过程,joiner节点会执行退出组的操作:
* 事务被清理:joiner节点所需的事务,在组中现有的任何在线成员的二进制日志中都无法找到,且也无法执行远程克隆操作(例如,因为克隆插件没有安装,或者所有支持克隆操作的所有候选donor节点尝试连接都失败了)。此时,joiner节点无法获取组中的数据,无法加入组。
* 事务冲突:joiner节点已经包含了组中不存在的一些事务。如果执行远程克隆操作,这些组中不存在的事务将被删除(对于joiner节点来说,意味着这些事务数据丢失),因为joiner节点上的数据目录将被删除。如果使用基于二进制日志的状态传输,则joiner节点中多余的事务可能会与组内的事务发生冲突,所以,joiner节点可能无法加入组。
* 已达到连接重试限制:joiner节点已耗尽了重试连接donor节点的限制次数。可以使用系统变量group_replication_recovery_retry_count来指定限制次数,关于该系统变量的配置,详情可参见"4.3.2. 配置分布式恢复"。
* 没有更多的donor节点:joiner节点向每个在线的且支持克隆操作的组成员之间尝试执行远程克隆操作的状态传输都失败之后,再向每个在线的且合适的组成员尝试执行基于二进制日志的状态传输也都失败时,joiner节点无法加入组。
* * joiner节点脱离组:在执行状态传输过程中,joiner节点意外脱离组,或者在joiner节点上停止组复制,则joiner节点加入组过程终止。
* 在上述几种情况中,除了最后一种之外,其他几种joiner节点退出组时,它将继续执行系统变量group_replication_exit_state_action指定的操作。
* **4.3.4. 分布式恢复的工作原理**
* 当组复制的分布式恢复过程基于二进制日志执行状态传输时,为了使joiner节点与donor节点在特定时间点上保持同步,joiner节点和donor节点使用了GTID机制。但是,GTID机制只提供了一种识别joiner节点缺失了哪些事务的方法。它并不能标记joiner节点必须要追赶上组中某个特定的时间点之后才能算作成功加入组(即,joiner节点在不断执行状态传输的过程中,通过GTID是无法知道在什么时间点算成功加入了组),它也不能传递认证信息(这是二进制日志中的View_change_log_event事件的工作,该事件用于标记二进制日志流中的视图变更,且还包含附加的元数据信息,为joiner节点提供缺失的事务数据与证书相关的数据)。下面将配合一些示意图来详细介绍基于二进制日志的状态传输的步骤。
视图和视图变更相关的概念。
* view:view(视图)对应于组中活跃成员的当前配置,换句话说,它是在特定的时间点所有组成员达成一致状态的配置。
* view changes:View Changes 指的是引起组配置发生变化的事件,当视图发生变更时(例如,有Server新加入组或有组成员脱离组时),视图将发生变更。任何组成员资格的变更都会导致在相同的逻辑时间点上向所有组成员发送视图变更消息。
* View Identifier:View Identifier(视图标识符)可以唯一标识一个视图。它是在视图发生变更时生成的。当发生View Changes时,就会生成这样一个唯一标识来表示这个新的View。
* 在组通信层,View changes 产生的view ids作为joiner节点与donor节点之间同步数据的临界点。View change 事件对应到binary log里面是一个新binary log event的实现,即“View_change_log_event”(视图变更日志事件),在这个事件里面携带了view id。它用于记录视图标识符,以便能够划分组成员资格发生变更之前和之后传输的事务边界。
* 视图标识符本身由两部分构成:随机生成的整数数字部分和单调递增的整数数字(例如:view_id=15692965051216743:3)。随机生成的部分是在创建组时生成的,在组中至少有一个成员开始,该随机部分无论组中后续有多少个成员加入或脱离组(只要至少有一个成员)都会保持不变。当每次发生视图变更时,单调递增的整数部分就会递增。通过使用这两个不同的部分,视图标识符就可以唯一标识由于Server加入组或组成员脱离组而导致的组成员资格变更,还可以标记在完全关闭组时所有成员退出组的情况,这将确保二进制日志中的数据标记保持惟一,以便在完全关闭组后不会重用相同的标识符,以防止将来的分布式恢复出现问题。
1) 从一个稳定的组开始。
* 所有成员都在线并正在持续处理来自组中的事务。有些成员可能存在复制延迟,但最终它们会达到一致的状态。组充当一个分布式的数据库副本。下图表示一个稳定的组。
![](http://www.icode9.com/i/li/?n=4&i=images/blog/202103/01/b9e19d575d30cb3632b840b3f6121cb4.png?,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=)
2)新Server申请加入组,视图发生变更。
* 每当有新Server申请加入组并因此执行视图变更时,每个在线的成员都会将视图变更日志事件排队等待执行。之所以要排队,因为在视图变更之前,每个成员中可能还有一些属于旧视图的事务还在队列中未应用完成,将视图变更日志时间排在这些属于旧视图的事务之后可以确保正确标记什么时候发生了视图变更。
* 同时,新加入组的Server从视图声明的在线成员列表中选择一个合适的donor节点。如下图所示,Server S4申请加入组时生成视图4(VC4),在线的所有组成员将视图变更日志事件写入二进制日志中(如果有成员存在应用延迟,则会先将View_change_log_event事件缓存在队列里排队,在该事件之前的事务属于旧视图,在该事件之后的事务属于新视图)。
![](http://www.icode9.com/i/li/?n=4&i=images/blog/202103/01/4f24e1571744dd5369bb5398f8cd8292.png?,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=)
3)使用状态传输追赶组中的最新数据。
* 如果组中的成员和joiner节点都设置好了克隆插件(详情可参考 "4.3.1. 克隆用于分布式恢复"),如果joiner节点与组之间的事务差异量超过了系统变量group_replication_clone_threshold设置的阈值,组复制就会通过远程克隆操作执行分布式恢复;如果joiner节点在组中的任何组成员的二进制日志文件中都找不到所需的事务,组复制也会通过远程克隆操作执行分布式恢复。在远程克隆操作期间,joiner节点上的现有数据将会被全部删除,并替换为donor节点的数据副本。当远程克隆操作完成且joiner节点完成数据库实例的重启操作之后,将从donor节点执行基于二进制日志的状态传输,以获取在远程克隆操作期间该组中新应用的事务;如果joiner节点与组之间的事务差异不大,或者没有安装克隆插件,则组复制直接从donor节点执行基于二进制日志进行状态传输。
* 对于从donor节点执行基于二进制日志的状态传输,会在joiner节点(这里为Server S4)和donor节点之间建立一个异步复制机制的专用通道,然后开始状态传输。与donor节点的这种交互将一直持续,直到Server S4中的applier线程处理视图变更日志事件(这里为VC4)为止,该事件对应于Server S4进入该组时触发的视图变更。这个时候,组内的所有成员读取到VC4时,通过VC4都能够清楚地知道在这之前的事务属于old view,在这之后的事务属于new view。如下图。
![](http://www.icode9.com/i/li/?n=4&i=images/blog/202103/01/7616c0d2f50dea85e32354d9135557c1.png?,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=)
由于视图标识符(VC4)在同一逻辑时间会传输给组中的所有成员,所以Server S4知道应该在哪个视图标识符(VC4)处停止复制(注意,这里说的停止复制指的是停止在Server S4与donor节点之间建立的专用的异步复制通道)。这避免了复杂的GTID SET计算,因为视图标识符(VC4)清楚地标记(界定)了哪些数据属于哪个组视图。
4)使用缓存来追赶组的最新数据。
* 当Server S4从donor节点复制数据的过程中,它也同时缓存来自组的新执行的事务。最终,当Server S4停止与donor节点之间的异步复制连接之后,它将应用那些被缓存的事务(这是实现Server S4加入组的过程中,在该阶段不阻塞写业务的关键特性)。如下图。
![](http://www.icode9.com/i/li/?n=4&i=images/blog/202103/01/729cfc4a3a2207227682e1b56bab22dc.png?,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=)
5)追赶数据完成
* 当joiner节点(Server S4)使用预期的视图标识符识别了视图变更日志事件(VC4)时,它与donor节点之间的连接将终止,并开始应用自身缓存中的增量事务。VC4 除了在二进制日志中充当新旧视图的分隔标记之外,它还扮演另一个角色。当Server S4成员进入组时,它传递所有服务器感知到的认证信息,即,就是最后一次视图变更。如果没有VC4,Server S4将没有所需的信息来验证(检测冲突)后续的事务。
* 追赶的持续时间是不确定的,因为它取决于工作负载和整个过程中组内新进入的事务速率。因为,这个过程是完全在线的,Server S4在追赶组数据的过程中不会阻塞组中的任何其他成员写入新的数据。因此,当Server S4在执行此阶段过程中,后续新写入组的事务可能堆积,堆积的事务多少取决于它的工作负载。
* 当Server S4应用完成缓存中的事务(缓存队列为空)且其存储的数据与组中其他成员达到一致时,其公共状态将更改为ONLINE。如下图 

![](http://www.icode9.com/i/li/?n=4&i=images/blog/202103/01/aee99044bc0eb6ac3ff299e57bf6c054.png?,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=)
上一篇:【DB吐槽大会】第43期 - PG 倒排索引启动和recheck代价高


下一篇:带你走进MySQL全新高可用解决方案-MGR