MySQL复制(八)组复制

欢迎关注公众号:一介IT
Alt

摘自个人网站,文章原文地址 https://l080l.com/mysql/ha/chapter10.html

本文 8.4万 字。建议收藏。

文章目录

1. 组复制 (MGR) 介绍

MySQL Group Replication(简称 MGR )是 MySQL 官方于 2016 年 12 月推出的一个全新的高可用与高扩展的解决方案。组复制是 MySQL 5.7.17 版本出现的新特性,它提供了高可用、高扩展、高可靠的 MySQL 集群服务。MySQL 组复制分单主模式和多主模式,传统的mysql复制技术仅解决了数据同步的问题,如果主机宕机,意味着数据库管理员需要介入,应用系统可能需要修改数据库连接地址或者重启才能实现。(这里也可以使用数据库中间件产品来避免应用系统数据库连接的问题,例如 Mycat 和 atlas 等产品)。

创建容错系统最常见的方法是组件冗余。换句话说,一个组件被移除时,系统应该继续按预期运行。这产生了一系列挑战,将这种系统的复杂性提高到了一个完全不同的水平。数据复制必须维护和管理多个服务器,还必须处理若干其它经典的分布式系统问题,如网络分区或脑裂。对 MySQL 而言,最终的挑战是将数据复制逻辑与协调多个服务器的逻辑相融合。这可以概括为让每个服务器的状态在数据变化时达成一致,即便它们都作为单个数据库系统运行,但最终都收敛到相同的状态。

MGR 对属于同一组的服务器自动进行协调。对于要提交的事务,组成员必须就全局事务序列中给定事务的顺序达成一致。提交或回滚事务由每个服务器单独完成,但所有服务器都必须做出相同的决定。如果存在网络分区,导致成员无法达成事先定义的分割策略,则在解决此问题之前系统不会继续进行,这是一种内置的自动裂脑保护机制。MGR由组通信系统(Group Communication System,GCS ) 协议支持。该系统提供故障检测机制、组成员服务以及安全且有序的消息传递。所有这些属性都是创建系统的关键,可确保在服务器组中一致地复制数据。该技术的核心是 Paxos 算法的实现,它充当组通信引擎。

1.1 回顾传统复制

在深入了解 MySQL 组复制的细节之前,先介绍一些其产生的背景以及工作原理,以帮助理解组复制,以及传统异步复制、半同步复制和组复制之间的区别。

异步复制

传统的 MySQL 复制提供了一种简单的主从复制方法。有一个主库,一个或多个从库。主库执行并提交事务,然后通过二进制日志将事务相关的事件异步发送到从库,以便重放。这是一个无共享系统,默认情况下所有服务器都拥有完整的数据副本。

异步复制

半同步复制

半同步复制为异步复制协议添加了一个同步步骤。这意味着主库在提交时等待至少一个从库确认它已收到该事务,才会继续提交操作。

半同步复制

1.2 组复制原理

组复制是一种可用于实现容错系统的技术。 复制组是一个通过消息传递相互交互的server集群。通信层提供了原子消息 (atomic message) 和完全有序信息交互等保障机制,实现了基于复制协议的多主更新。复制组由多个 server成员构成,并且组中的每个 server 成员可以独立地执行事务。但所有读写 (RW) 事务只有在冲突检测成功后才会提交。只读 (RO) 事务不需要在冲突检测,可以立即提交。句话说, 对于任何 RW 事务,提交操作并不是由始发server单向决定的,而是由组来决定是否提交。准确地说,在始发 server 上,当事务准备好提交时,该 server 会广播写入值 (已改变的行) 和对应的写入集 (已更新的行的唯一标识符)。然后会为该事务建立一个全局的顺序。最终,这意味着所有 server 成员以相同的顺序接收同一组事务。因此, 所有server成员以相同的顺序应用相同的更改,以确保组内一致。

基于组的复制(Group-based Replication)是一种被使用在容错系统中的技术。Replication-group (复制组) 是由能够相互通信的多个服务器(节点)组成的。在通信层,Group replication 实现了一系列的机制:比如原子消息 (atomic message delivery) 和全序化消息 (total ordering of messages)。这些原子化,抽象化的机制,为实现更先进的数据库复制方案提供了强有力的支持。MySQL Group Replication 正是基于这些技术和概念,实现了一种多主全更新的复制协议。

简而言之,一个 Replication-group 就是一组节点,每个节点都可以独立执行事务,而读写事务则会在于 group 内的其他节点进行协调之后再 commit。因此,当一个事务准备提交时,会自动在 group 内进行原子性的广播,告知其他节点变更了什么内容/执行了什么事务。这种原子广播的方式,使得这个事务在每一个节点上都保持着同样顺序。这意味着每一个节点都以同样的顺序,接收到了同样的事务日志,所以每一个节点以同样的顺序重演了这些事务日志,最终整个 group 保持了完全一致的状态。然而,不同的节点上执行的事务之间有可能存在资源争用。这种现象容易出现在两个不同的并发事务上。

假设在不同的节点上有两个并发事务,更新了同一行数据,那么就会发生资源争用。面对这种情况,Group Replication 判定先提交的事务为有效事务,会在整个 group 里面重演,后提交的事务会直接中断,或者回滚,最后丢弃掉。因此,这也是一个无共享的复制方案,每一个节点都保存了完整的数据副本。看下图描述了具体的工作流程,能够简洁的和其他方案进行对比。这个复制方案,在某种程度上,和数据库状态机 (DBSM) 的 Replication 方法比较类似。

MySQL组复制协议工作流程:

组复制

MySQL 组复制是一种无共享 (share-nothing) 复制方案,其中每个服务器成员都有自己的完整数据副本。

1.3 组复制与传统复制对比

传统复制:

  • 主-从复制: 有一个主和不等数量的从。主节点执行的事务会异步发送给从节点,在从节点重新执行。(异步和半同步;半同步相对异步 Master 会确认 Slave 是否接到数据,更加安全)。
  • 并行复制: 复制->广播->正式复制

组复制相比传统复制的优势在于:

  • 弹性复制(高扩展性):server 动态添加移除;
  • 高可用分片(高扩展性):分片实现写扩展,每个分片是一个复制组;
  • 替代主从复制(高扩展性):整组写入,避免单点争用;
  • 自动化系统:自动化部署 Mysql 复制到已有复制协议的自动化系统;
  • 故障检测与容错: 自动检测,若服务 faild,组内成员大多数达成认为该服务已不正常,则自动隔离;

在 MySQL 组复制环境中,组内成员会构成一个视图,组内成员主动加入或离开 (主动或被动),都会更新组配置,更新视图。成员自愿离开,先更新组配置,然后采用大多数成员 (不包含主动脱离的成员) 意见是否确认该成员离开更新视图。如果是故障要排除,则需大多数服务确认 (包括故障成员意见),然后才会更新组配置和视图。

特别注意:组复制最大允许即时故障数:f=(n-1)/2,多数正常则正常。

1.4 组复制模式

组复制可以以单主模式或多主模式运行,缺省采用单主模式。单主模式中只有一个可以读写的服务器,其它服务器只读。多主模式中,所有服务器均可读写。无论部署模式如何,组复制都不处理客户端故障转移,而必须由应用程序本身、连接器或中间件 (如 MySQL router) 处理此问题。

1.4.1 单主模式

single-primary mode(单写或单主模式),单写模式 group 内只有一台节点可写可读,其他节点只可以读。当主服务器失败时,会自动选择新的主服务器,如图所示。

img

对于组的部署,需要先跑起 primary 节点 (即那个可写可读的节点,read_only = 0) 然后再跑起其他的节点,并把这些节点一一加进组。其他的节点就会自动同步主节点上面的变化,然后将自己设置为只读模式 (read_only = 1)。当主节点意外宕机或者下线,在满足大多数节点存活的情况下,组内部发起选举,选出下一个可用的读节点,提升为主节点。选择哪个服务器作为新主库由group_replication_member_weight系统变量控制,该变量值最高的成员将被选为新主库。如果多个服务器具有相同的group_replication_member_weight值,则组复制将根据按字典顺序排列的server_uuid,优先选择第一个在线服务器作为新主库。新主库将自动设置为读写,其它服务器仍为从库,因此为只读。新主库只有在处理了来自旧主库的所有事务后才可写,这避免了在新主库上并行执行新老事务的问题。将客户端应用程序重新路由到新主库之前,等待新主服务器应用其复制相关的中继日志是一种很好的做法。

如果组成员的 MySQL 版本不同,选举新主库的过程可能会受到影响。例如,如果有成员不支持group_replication_member_weight,则根据较低主版本成员的server_uuid顺序选择主库;如果运行不同 MySQL 版本的所有成员都支持group_replication_member_weight,则根据较低主版本成员的group_replication_member_weight选择主库。

# 主服务器可由下面查询确认:
select member_id, member_host, member_role from performance_schema.replication_group_members;

在切换 primary 期间,mysql group 不会处理应用客户端重连接到新的主,这需要应用层自己或者由另外的中间件层 (proxy 或 router) 去保证!

1.4.2 多主模式

multi-primary mode(多写或多主模式),组内的所有机器都是 primary 节点,同时可以进行读写操作,并且数据是最终一致的。如图所示。

img

在三个节点上分别部署 MySQL 实例,每个节点都接收写请求;额外可以加入一个节点,测试节点的动态增加。

多主模式下部署组复制时,将进行以下检查:

  • 如果事务在 SERIALIZABLE 隔离级别下执行,则在与组同步时其提交失败。
  • 如果事务在具有级联外键约束的表上执行,则在与组同步时事务无法提交。

通过将选项group_replication_enforce_update_everywhere_checks设置为 FALSE,可以禁用这些检查。单主模式下此选项必须设置为 FALSE。

1.5 组复制特点

  • 高一致性
    • 基于原生复制及 Paxos 协议的组复制技术,并以插件的方式提供,提供一致数据安全保证。确保组内数据最终一致性【重要】(通过分布式协议和分布式 recovery 机制保证);
  • 高容错性
    • 确保组内高可用。只要不是大多数节点坏掉就可以继续工作,有自动检测机制,当不同节点产生资源争用冲突时,不会出现错误,按照先到者优先原则进行处理,并且内置了自动化脑裂防护机制;
  • 高扩展性
    • 良好的扩展能力,可动态增删节点,组成员自动管理。节点的新增和移除都是自动的,新节点加入后,会自动从其他节点上同步状态,直到新节点和其他节点保持一致,如果某节点被移除了,其他节点自动更新组信息,自动维护新的组信息;
  • 高灵活性
    • 有单主模式和多主模式,单主模式下,会自动选主,所有更新操作都在主上进行;
    • 多主模式下,所有 Server 都可以同时处理更新操作。
  • 多写,写冲突检测。

1.6 组复制的要求与限制

  • 存储引擎必须为Innodb,即仅支持 InnoDB 表,并且每张表一定要有一个主键,用于做write set的冲突检测;
  • 每个表必须提供主键
  • 只支持 ipv4,网络需求较高;
  • 必须打开GTID特性,二进制日志格式必须设置为ROW,用于选主与write set
  • COMMIT 可能会导致失败,类似于快照事务隔离级别的失败场景;
  • 目前一个 MGR 集群组最多支持 9 个节点;
  • 不支持外键于save point特性,无法做全局间的约束检测与部分部分回滚;
  • 二进制日志 binlog 不支持Replication event checksums
  • 多主模式(多写模式) 不支持SERIALIZABLE事务隔离级别;
  • 多主模式不能完全支持级联外键约束;
  • 多主模式不支持在不同节点上对同一个数据库对象并发执行 DDL (在不同节点上对同一行并发进行 RW 事务,后发起的事务会失败)。

1.7 组复制相关服务

    1. 故障检测
    • 组复制包括一种故障检测机制,该机制能够查找并报告哪些服务器已经宕机。故障检测是提供关于哪些 server 可能已死的信息(猜测)的分布式服务。 某个 server 无响应时触发猜测,组中其余成员进行协调决定以排除给定成员。如果某个 server 与组的其余成员隔离,则它会怀疑所有其他 server 都失败了。由于无法与组达成协议 (因为它无法确保仲裁成员数),其怀疑不会产生后果。当服务器以此方式与组隔离时,它无法执行任何本地事务。 在线 server 列表通常称为视图,新成员 server 的加入离开,无论是自愿还是被迫的离开,该组都会动态地重新规划其配置,并触发视图更新。
    • 如果一个服务器与组的其余部分隔离,它会怀疑所有其它服务器都已失败,但由于无法与该组达成协议(因为无法确保法定票数),其怀疑并没有结果。当服务器以这种方式与组隔离时,它无法执行任何本地事务。
    1. 组成员服务
    • MGR 依赖于组成员服务,该服务内置于插件中。它定义了哪些服务器在线并参与该组。在线服务器列表通常称为视图。因此,组中的每个服务器都具有一致的视图,其中是在给定时刻主动参与该组的成员。
    • 服务器不仅必须同意事务提交,还要同意当前视图。因此,如果服务器同意新服务器成为组的一部分,则组本身将重新配置为将该服务器集成在其中,从而触发视图更改。相反的情况也会发生,如果服务器离开组,则组会动态更新配置并触发视图更改。
    • 组成员离开组分主动与被动。主动离开会启动组的动态重新配置,这会触发所有其它成员必须在没有该服务器的情况下就新视图达成一致。被动离开 (如已意外停止或断网) 时,故障检测机制会建议重新配置组,这需要组中大多数服务器的同意。如果该组无法达成协议,为阻止脑裂,系统无法动态更改配置,这意味着管理员需要介入解决此问题。
    1. 容错
    • MGR 构建于Paxos分布式算法的实现之上,需要多数服务器处于活动状态才能达到法定票数,从而做出决定。这直接影响系统可以容忍的故障机数量,但不会影响组复制自身及其整体功能。容忍 f 个故障机所需的服务器数量 n 为:n = 2*f + 1
    • 实践中为了容忍一个故障机,该组必须具有三个服务器,因为此时如果一个服务器发生故障,仍然有两个服务器构成多数,并允许系统继续自动做出决策并继续提供服务。但如果第二个服务器继续失败,那么该组 (剩下一个服务器) 会阻塞,因为没有多数票可以做出决定。

1.8 组复制技术细节

1.8.1 组复制插件体系结构

MGR 是一个 MySQL 插件,它以现有的 MySQL 复制架构为基础,利用二进制日志、基于行的日志记录和全局事务标识符 (GTID) 等功能。图中显示了 MGR 插件架构。

MGR插件架构

MGR 插件包含一组捕获、应用和生命周期API,用于控制插件与 MySQL 服务器的交互方式。这些接口将 MySQL 服务器核心与 MGR 插件隔离。服务器向插件通知启动、恢复、准备接收连接、即将提交事务等消息。插件指示服务器执行诸如提交事务、中止正在进行的事务、事务在中继日志中排队等动作。
组复制插件体系结构的下一层是一组组件。捕获组件负责跟踪与正在执行的事务相关的上下文。应用组件负责在数据库上执行远程事务。恢复组件管理分布式恢复,负责选择捐赠者,对故障做出反应,执行追赶程序,使加入该组的服务器获得更新。
堆栈下一层的复制协议模块包含复制协议的特定逻辑。它处理冲突检测,接收事务并将其传播到组。
组复制插件体系结构的最后两层是组通信系统(GCS)API,以及基于Paxos的组通信引擎(XCom)的实现。GCS API 将消息传递层的实现与插件上层分离,组通信引擎处理与复制组成员的通信。

1.8.2 复制组

MGR 中的一组服务器构成一个复制组,组名形式为 UUID。组是动态的,服务器可以离开 (主动或被动) 并随时加入组。服务器加入或离开时,组会自行调整。如果服务器加入组,组会通过从现有服务器获取状态自动更新新加入的服务器。状态通过 MySQL 异步复制进行传输。如果服务器离开该组,其余服务器会知道它已离开并自动重新配置该组。

1.8.3 DML

组中的每个服务器都被允许随时执行读写事务。任何服务器都可以在没有任何事先协调的情况下执行事务。但在提交时,它与组中的其余服务器协调,以便就该事务的操作做出决定。这种协调有两个目的:一是检查事务是否可以提交;二是传播更新,以便其它服务器也可以应用该事务。

当事务通过原子广播发送时,组中的所有服务器都接收该事务,或者都不接收该事务。它们会以与之前发送的其它事务相同的顺序收到它,并通过检查和比较写入事务集来执行冲突检测。冲突解决遵循首个提交者获胜规则。例如,事务 t1 和 t2 在不同的站点同时执行,t2 排在 t1 之前,并且两者都改变了同一行,那么 t2 赢得冲突被执行,t1 被中止。如果两个事务经常发生冲突,最好在同一台服务器上启动它们,这样它们有机会在本地锁管理机制下并行执行,而不是稍后在复制协议中中止。

1.8.4 DDL

在组复制拓扑中,执行数据定义语句时需要小心。MySQL 8.0 引入了对原子数据定义语言的支持,其中完整的 DDL 语句作为单个原子事务提交或回滚。但是,原子或其它 DDL 语句隐式结束当前会话中处于活动状态的任何事务,就好像在执行语句之前已完成 COMMIT 一样。这意味着 DDL 语句自成事务,不能在另一个事务中,或在START TRANSACTION ... COMMIT等事务控制语句中,或者与同一事务中的其它语句结合使用。

组复制基于乐观复制,其中语句被乐观执行(执行时不事先锁定对象)并在稍后必要时回滚。每个服务器首先执行和提交而不保护组协议。因此,在多主模式下复制 DDL 语句时需要更加小心。如果对同一对象进行结构更改 (使用 DDL) 并更改对象包含的数据 (使用 DML),则需要通过同一服务器处理更改。如果不这样做,可能会因操作中断或部分完成,导致数据不一致。如果组以单主模式部署,则不会发生此问题,因为所有更改都是通过同一服务器 (主服务器) 执行的。

1.8.5 分布式恢复
1.8.5.1 分布式恢复基础

组复制分布式恢复可以概括为服务器从组中获得丢失事务的过程,以便它可以加入具有已处理相同事务集成员的组。在分布式恢复期间,加入组的服务器会缓冲其正在接收的,组中所需的事务和成员事件。一旦加入该组的服务器收到了该组的所有事务,它就会应用在恢复过程中缓冲的事务。此过程结束时,服务器随之作为在线成员加入组。

分布式恢复分为两个阶段。第一阶段,加入该组的服务器选择该组上的一个在线服务器作为其缺失状态的捐赠者。捐赠者负责为新服务器提供加入该组的所有数据,直到它加入该组为止。这是通过在捐赠者和加入该组的服务器之间建立的标准异步复制通道来实现的。复制通道是 MySQL 5.7 中提出的概念。简单讲一个复制通道表示从主库到从库的一条复制路径,在多源复制中主到从可以存在多条复制通道。通过此复制通道复制捐赠者的二进制日志,直到加入该组的服务器成为该组的一部分,并发生视图更改时。加入该组的服务器在收到捐赠者的二进制日志时应用它们。

复制二进制日志时,加入该组的服务器还会缓存在组内交换的每个事务。也就是说,它监听在加入该组之后发生的事务,同时应用来自捐赠者的数据。当第一阶段结束并且关闭捐赠者的复制通道时,加入该组的服务器开始第二阶段:追赶。在此阶段,加入组的服务器继续执行高速缓存的事务。排队等待执行的事务数最终达到零时,该成员将在线声明。

当加入组的服务器从捐赠者获取二进制日志时,恢复过程可以承受捐赠者故障。在这种情况下,捐赠者在第一阶段期间失败时,加入该组的服务器将故障转移到新的捐赠者并从新捐赠者恢复。加入该组的服务器将关闭与失败的捐赠者的连接,并打开与新捐赠者的连接,这些都是自动进行的。

1.8.5.2 基于时间点的恢复

为了使加入组的服务器与捐赠者同步到特定时间点,加入组和捐赠者的服务器利用 MySQL 全局事务标识符 (GTID) 机制。但 GTID 仅提供了一种方法来发现加入该组的服务器缺少哪些事务,不会传达认证信息。这是二进制日志视图标记的工作,它标记二进制日志流中的视图更改,还包含其它元数据信息,如认证相关数据。

视图对应于主动参与当前配置的一组成员,在特定时间点,这些组成员在系统中是正确的和在线的。视图更改发生在组配置修改(例如成员加入或离开)时。任何组成员身份更改都会导致在同一逻辑时间点向所有成员传达视图更改。视图标识符唯一标识视图。只要视图发生更改,就会生成一个视图标识符。

在通信层,视图更改及其关联的视图 ID 是成员加入之前和之后数据变化的边界。此概念通过新的二进制日志事件实现:“视图更改日志事件”。因此视图 ID 也成为在组成员资格发生变化之前和之后传输的事务的标记。视图标识符本身由两部分构成:随机部分和单调递增整数部分。第一部分在创建组时生成,并且在组中至少有一个成员时保持不变。每次视图更改发生时,第二部分都会递增。随机部分识别组的开始,增量部分标识组的改变。

1.8.5.3 视图更改

视图更改时,执行以下步骤将标识符合并到二进制日志事件:

1). 开始:稳定组

如下图所示,所有服务器都在线并处理来自组的传入事务。一些服务器复制的事务可能稍微落后,但最终它们会相同。此时该组充当一个分布式数据库副本。

稳定组

2). 视图更改:加入一个组成员

每当新成员加入组并因此执行视图更改时,每个联机服务器都会把视图更改日志事件排入队列以备执行。在视图更改之前,服务器上可能有一些属于旧视图的事务排队进行应用,将视图更改事件排在它们之后可确保正确标记何时发生了视图更改。

同时,加入该组的服务器通过视图的在线服务器列表中选择捐赠者。如下图所示,成员加入时生成视图 S4,在线成员将此视图更改事件写入二进制日志。

成员加入

3). 状态转移:追赶

一旦加入该组的服务器选择该组中的某服务器作为捐赠者,则在两者之间建立新的异步复制连接并且开始状态转移 (第一阶段)。这种与捐赠者的交互一直持续到服务器加入组的应用程序线程,该线程处理服务器进入组时所触发的视图更改日志事件。加入该组的服务器从捐赠者复制,直到它到达与视图改变相匹配的视图标识符,如下图所示。

追赶

加入该组的服务器知道它应该在哪个视图标识符停止复制。由于视图标识符在相同的逻辑时间被发送到组中的所有成员,避免了复杂的 GTID 集合计算,因为视图 ID 清楚地标记了属于每个组视图的数据。加入该组的服务器正在从捐赠者复制时,它也会缓存来自该组的传入事务。最后它停止从捐赠者复制并切换到应用缓存的那些事务,如下图所示。

排队的事务

4). 完成:赶上

当加入组的服务器识别出具有预期视图标识符的视图更改日志事件时,终止与捐赠者的连接并开始应用缓存的事务。视图更改日志事件除了在二进制日志中充当分隔标记,还扮演另一个角色。当新服务器进入组时,它传达所有服务器感知的认证信息,即最后的视图改变。如果没有视图更改事件,加入该组的服务器将没有必要的信息对后续事务进行冲突检测。追赶的持续时间 (第二阶段) 是不确定的,它取决于负载和进入组的事务的多少。此过程完全联机,加入组的服务器在追赶时不会阻止组中的任何其它服务器。当进行到第二阶段时,加入该组的服务器的事务可能落后,落后的多少取决于负载。当加入组的服务器达到零排队事务并且其存储的数据等于其它成员时,其公共状态将更改为联机,如下图所示。

实例联机
1.8.5.4 分布式恢复的使用建议和限制

分布式恢复基于传统的异步复制,如果加入组的服务器没有数据或者只有非常旧的备份数据,恢复过程可能很慢。这意味着要在第一阶段传输大量数据,新增服务器可能需要很长时间才能恢复。因此建议在将服务器添加到组之前,应该为其配置已经在组中的服务器的相当近的快照。这最小化了第一阶段的所需时间并减少了对捐赠服务器的影响,因为它只需保传输较少的二进制日志。

2. 基于GTID的组复制分布式集群的环境部署

需要清楚知道:MySQL 复制组能够以一种自动优先选择的单主模式运行,在某个时间只有一个服务器接受更新 。但是对于更高优先级的用户,组能够以多主模式部署,所有的服务器都能够接受更新,即使它们是同时发生的。组复制中存在着一种内建的组成员关系服务用来保持组的视图一致,并且在任意时间对于组中的所有的服务器都可用。MySQL 服务器能够退出或者加入组中,而且视图也会相应的更新。有时服务器可能会意外的退出组 (故障),在这种情况下失败检测机制检测这种情况并且告知复制组视图发生了变化,这所有的一切都是自动实现的。

2.1 服务器环境部署

参考【第02章_MySQL复制环境搭建】部署,本节实验环境部署的是mysql 8.0.31三台实例

主机名IPServer IDOSMySQL version
node1192.168.2.8181Centos 7.8Mysql 8.0.31
node2192.168.2.8282Centos 7.8Mysql 8.0.31
node3192.168.2.8383Centos 7.8Mysql 8.0.31
#所有主机添加hosts解析
[root@node1 ~]# cat /etc/hosts
....
192.168.2.81	node1
192.168.2.82	node2
192.168.2.83	node3

2.2 MGR组复制单主模式配置

(本案例采用 MGR 默认单主模式)。

2.2.1 组复制第一个实例配置
2.2.1.1 参数配置

先给这组 MGR 起个组名,组名可以随便起,但是不能使用主机的 GTID.

可以通过节点的 UUID 作为 loose-group_replication_group_name 的组名,并且每个节点的这个组名必须一样!

这里使用参照官网设置手册,如下设置组名,https://dev.mysql.com/doc/refman/8.0/en/group-replication-configuring-instances.html

# 如果要通过节点的UUID设置组名,查看节点node1的uid
[root@node1 mysql]# cat /var/lib/mysql/auto.cnf 
[auto]
server-uuid=dd746660-528a-11ed-9c86-000c293b9f86
	-- 或者通过sql查看
mysql> select uuid();
+--------------------------------------+
| uuid()                               |
+--------------------------------------+
| bf4b19ea-60f1-11ed-a06b-000c293b9f86 |
+--------------------------------------+
# 配置参数文件
[root@node1 ~]# vim /etc/my.cnf
[mysqld]
# 原有默认配置
datadir=/var/lib/mysql
socket=/var/lib/mysql/mysql.sock
log-error=/var/log/mysqld.log
pid-file=/var/run/mysqld/mysqld.pid
# 密码验证方式
default_authentication_plugin=mysql_native_password
#Storage Engines
disabled_storage_engines="MyISAM,BLACKHOLE,FEDERATED,ARCHIVE,MEMORY"
#Replication Framework
server_id=81
gtid_mode=ON
enforce_gtid_consistency=ON

binlog_checksum=NONE

log_bin=binlog
log_slave_updates=ON
binlog_format=ROW
master_info_repository=TABLE
relay_log_info_repository=TABLE
transaction_write_set_extraction=XXHASH64

sync-master-info =1
sync_binlog =1
#Group Replication Settings
plugin_load_add='group_replication.so'
group_replication_group_name="aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"
group_replication_start_on_boot=off
group_replication_local_address= "node1:33061"
group_replication_group_seeds= "node1:33061,node2:33061,node3:33061"
group_replication_bootstrap_group=off
# 双主模式,如果是单主模式无需配置,默认单主模式
# 当前不配置,后续切换多主模式后再持久化该参数
# group_replication_single_primary_mode=off
# group_replication_enforce_update_everywhere_checks=on

参数说明:

#以便在server收集写集合的同时将其记录到二进制日志。写集合基于每行的主键,并且是行更改后的唯一标识此标识将用于检测冲突。
transaction_write_set_extraction=XXHASH64
plugin_load_add='group_replication.so'
#组的名字可以随便起,但不能用主机的GTID! 所有节点的这个组名必须保持一致!
group_replication_group_name="aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"
#为了避免每次启动自动引导具有相同名称的第二个组,所以设置为OFF。
group_replication_start_on_boot=off
group_replication_local_address= "node1:33061"
group_replication_group_seeds= "node1:33061,node2:33061,node3:33061"
group_replication_bootstrap_group=off
#关闭单主模式的参数
#group_replication_single_primary_mode=off
#开启多主模式的参数
#group_replication_enforce_update_everywhere_checks=on
2.2.1.2 重启服务
# 所有节点
systemctl restart mysqld
2.2.1.3 用户凭据

为了在每个实例上单独创建复制用户,可以禁用二进制日志记录后再创建,请按照以下语句执行:

https://dev.mysql.com/doc/refman/8.0/en/group-replication-user-credentials.html

[root@node1 ~]# mysql -proot
mysql>
# mysql8默认密码认证方式,客户端是无法连接的,使用mysql_native_password,或在参数文件中加入参数
# default_authentication_plugin=mysql_native_password
# CREATE USER repl@'%' IDENTIFIED BY 'repl';
SET SQL_LOG_BIN=0;
CREATE USER repl@'%' IDENTIFIED with mysql_native_password BY 'repl';
GRANT REPLICATION SLAVE ON *.* TO repl@'%';
FLUSH PRIVILEGES;
SET SQL_LOG_BIN=1;

CHANGE MASTER TO MASTER_USER='repl', MASTER_PASSWORD='repl' FOR CHANNEL 'group_replication_recovery';
2.2.1.4 组复制插件
# 安装组复制插件
# INSTALL PLUGIN group_replication SONAME 'group_replication.so';
# 本次已经在配置文件添加引导,无需再次安装
mysql> SHOW PLUGINS;
+----------------------------+----------+--------------------+----------------------+-------------+
| Name                       | Status   | Type               | Library              | License     |
+----------------------------+----------+--------------------+----------------------+-------------+
| binlog                     | ACTIVE   | STORAGE ENGINE     | NULL                 | PROPRIETARY |

(...)

| group_replication          | ACTIVE   | GROUP REPLICATION  | group_replication.so | PROPRIETARY |
+----------------------------+----------+--------------------+----------------------+-------------+
2.2.1.5 加入组复制
# 引导只能由单个服务器完成,即启动组的服务器并且只执行一次。
# 这就是为什么group_replication_bootstrap_group选项的值没有存储在实例的选项文件中的原因。
# 如果它保存在选项文件中,则在重新启动服务器时会自动引导第二个具有相同名称的组。这将导致两个不同的组具有相同的名称
mysql>
SET GLOBAL group_replication_bootstrap_group=ON;
START GROUP_REPLICATION;
SET GLOBAL group_replication_bootstrap_group=OFF;
SELECT * FROM performance_schema.replication_group_members;
# 可以检查该组现在是否已创建并且其中有一个成员
# 要保证group_replication_applier的状态为"ONLINE"!
mysql> SELECT * FROM performance_schema.replication_group_members;
+---------------------------+--------------------------------------+-------------+-------------+--------------+-------------+----------------+----------------------------+
| CHANNEL_NAME              | MEMBER_ID                            | MEMBER_HOST | MEMBER_PORT | MEMBER_STATE | MEMBER_ROLE | MEMBER_VERSION | MEMBER_COMMUNICATION_STACK |
+---------------------------+--------------------------------------+-------------+-------------+--------------+-------------+----------------+----------------------------+
| group_replication_applier | dd746660-528a-11ed-9c86-000c293b9f86 | node1       |        3306 | ONLINE       | PRIMARY     | 8.0.31         | XCom                       |
+---------------------------+--------------------------------------+-------------+-------------+--------------+-------------+----------------+----------------------------+
1 row in set (0.00 sec)
2.2.1.6 数据测试
# 组复制环境下要求每个表都需要有主键,否则表上的DML会报错:
# ERROR 3098 (HY000): The table does not comply with the requirements by an external plugin.
mysql>
CREATE DATABASE test;
USE test;
CREATE TABLE t1 (c1 INT PRIMARY KEY, c2 TEXT NOT NULL);
INSERT INTO t1 VALUES (1, 'Luis');

mysql> SELECT * FROM test.t1;
+----+------+
| c1 | c2   |
+----+------+
|  1 | Luis |
+----+------+
mysql> SHOW BINLOG EVENTS;
2.2.2 组复制添加其他实例成员
2.2.2.1 参数配置

参数配置和第一个节点的参数类似,只需修改server_idgroup_replication_local_address.

# 将参数文件拷贝到其他节点
[root@node1 ~]# scp /etc/my.cnf root@node2:/etc/my.cnf
[root@node1 ~]# scp /etc/my.cnf root@node3:/etc/my.cnf

# 配置参数文件,其他参数相同,修改如下两个参数
[root@node2 ~]# vim /etc/my.cnf
[mysqld]
server_id = 82
group_replication_local_address= "node2:33061"
[root@node3 ~]# vim /etc/my.cnf
[mysqld]
server_id = 83
group_replication_local_address= "node3:33061"
2.2.2.2 重启服务
# 所有节点
systemctl restart mysqld
2.2.2.3 用户凭据

为了在每个实例上单独创建复制用户,可以禁用二进制日志记录后再创建,请按照以下语句执行:

User Credentials For Distributed Recovery

mysql>
SET SQL_LOG_BIN=0;
CREATE USER repl@'%' IDENTIFIED BY 'repl';
GRANT REPLICATION SLAVE ON *.* TO repl@'%';
FLUSH PRIVILEGES;
SET SQL_LOG_BIN=1;

CHANGE MASTER TO MASTER_USER='repl', MASTER_PASSWORD='repl' FOR CHANNEL 'group_replication_recovery';
2.2.2.4 组复制插件
# 安装组复制插件
# INSTALL PLUGIN group_replication SONAME 'group_replication.so';
# 本次已经在配置文件添加引导,无需再次安装
2.2.2.5 加入组复制
#这里只需要执行这一步即可,因为组已经创建
mysql> START GROUP_REPLICATION;
#查看组内情况,发现node2,node3已经成功加入这个组了
#一主两从模式
mysql> SELECT * FROM performance_schema.replication_group_members;
+---------------------------+--------------------------------------+-------------+-------------+--------------+-------------+----------------+----------------------------+
| CHANNEL_NAME              | MEMBER_ID                            | MEMBER_HOST | MEMBER_PORT | MEMBER_STATE | MEMBER_ROLE | MEMBER_VERSION | MEMBER_COMMUNICATION_STACK |
+---------------------------+--------------------------------------+-------------+-------------+--------------+-------------+----------------+----------------------------+
| group_replication_applier | 2dd88a45-6161-11ed-b5d2-000c299aa451 | node3       |        3306 | ONLINE       | SECONDARY   | 8.0.31         | XCom                       |
| group_replication_applier | 30c9d73b-6161-11ed-b523-000c2913fd72 | node2       |        3306 | ONLINE       | SECONDARY   | 8.0.31         | XCom                       |
| group_replication_applier | 32710bb7-6161-11ed-b45b-000c29b2256f | node1       |        3306 | ONLINE       | PRIMARY     | 8.0.31         | XCom                       |
+---------------------------+--------------------------------------+-------------+-------------+--------------+-------------+----------------+----------------------------+
3 rows in set (0.00 sec)
2.2.3 数据测试
#从库查询
mysql> SELECT * FROM test.t1;
+----+------+
| c1 | c2   |
+----+------+
|  1 | Luis |
+----+------+
1 row in set (0.00 sec)
#主库添加数据
INSERT INTO test.t1 VALUES (2, 'Klaus');
#从库查询
mysql>  SELECT * FROM test.t1;
+----+-------+
| c1 | c2    |
+----+-------+
|  1 | Luis  |
|  2 | Klaus |
+----+-------+
2 rows in set (0.00 sec)

至此,组复制配置结束,后面讲解组复制的模式切换方法和新出的函数使用。

2.3 使用参数切换组复制模式

当前环境是单主模式,看通过如下命令查询:

mysql> SELECT * FROM performance_schema.replication_group_members;

如果最初所有实例的配置文件中设置如下参数,那么就开启了多主模式:

[root@node1 ~]# vim /etc/my.cnf
[mysqld]
#关闭单主模式的参数
group_replication_single_primary_mode=off
#开启多主模式的参数
group_replication_enforce_update_everywhere_checks=on
2.3.1 单主切换多主模式
# 停止组复制,开启支持多主模式(所有节点执行):
mysql>
stop group_replication;
set global group_replication_single_primary_mode=OFF;
set global group_replication_enforce_update_everywhere_checks=ON;

# 随便选择某个节点执行
# 引导只能由单个服务器完成,即启动组的服务器并且只执行一次。
mysql>
SET GLOBAL group_replication_bootstrap_group=ON;
START GROUP_REPLICATION;
SET GLOBAL group_replication_bootstrap_group=OFF;

# 其他节点执行
mysql> START GROUP_REPLICATION;
2.3.2 多主模式数据测试
# 查看组信息,所有节点的 MEMBER_ROLE 都为 PRIMARY
mysql> SELECT * FROM performance_schema.replication_group_members;
+---------------------------+--------------------------------------+-------------+-------------+--------------+-------------+----------------+----------------------------+
| CHANNEL_NAME              | MEMBER_ID                            | MEMBER_HOST | MEMBER_PORT | MEMBER_STATE | MEMBER_ROLE | MEMBER_VERSION | MEMBER_COMMUNICATION_STACK |
+---------------------------+--------------------------------------+-------------+-------------+--------------+-------------+----------------+----------------------------+
| group_replication_applier | 2dd88a45-6161-11ed-b5d2-000c299aa451 | node3       |        3306 | ONLINE       | PRIMARY     | 8.0.31         | XCom                       |
| group_replication_applier | 30c9d73b-6161-11ed-b523-000c2913fd72 | node2       |        3306 | ONLINE       | PRIMARY     | 8.0.31         | XCom                       |
| group_replication_applier | 32710bb7-6161-11ed-b45b-000c29b2256f | node1       |        3306 | ONLINE       | PRIMARY     | 8.0.31         | XCom                       |
+---------------------------+--------------------------------------+-------------+-------------+--------------+-------------+----------------+----------------------------+
3 rows in set (0.00 sec)
# 现在在三个节点中的任意一个上面更新数据,那么其他两个节点的数据库都会将新数据同步过去!
# 第一个节点更新数据
INSERT INTO test.t1 VALUES (81, 'node1');
# 第二个节点更新数据
INSERT INTO test.t1 VALUES (82, 'node2');
# 第三个节点更新数据
INSERT INTO test.t1 VALUES (83, 'node3');
# 查询验证
mysql> SELECT * FROM test.t1;
+----+-------+
| c1 | c2    |
+----+-------+
|  1 | Luis  |
|  2 | Klaus |
| 81 | node1 |
| 82 | node2 |
| 83 | node3 |
+----+-------+
6 rows in set (0.00 sec)
2.3.3 多主复制的限制测试
  • 串行化隔离级别下报错
mysql> show create table test.t1\G
*************************** 1. row ***************************
       Table: t1
Create Table: CREATE TABLE `t1` (
  `a` bigint NOT NULL AUTO_INCREMENT,
  PRIMARY KEY (`a`)
) ENGINE=InnoDB AUTO_INCREMENT=763 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
1 row in set (0.00 sec)
 
mysql> set transaction_isolation='serializable';
Query OK, 0 rows affected (0.00 sec)
 
mysql> insert into test.t1 select null;
ERROR 3098 (HY000): The table does not comply with the requirements by an external plugin.
mysql> set transaction_isolation='repeatable-read';
Query OK, 0 rows affected (0.00 sec)
 
mysql> insert into test.t1 select null;
Query OK, 1 row affected (0.01 sec)
Records: 1  Duplicates: 0  Warnings: 0
  • 级联外键约束更新报错
#1、建两张表,没有外键更新约束
mysql>
use test;
create table stu(
sid int unsigned primary key auto_increment,
name varchar(20) not null);
mysql>
create table sc(
scid int unsigned primary key auto_increment,
sid int unsigned not null,
score varchar(20) default '0',
index (sid), 
foreign key (sid) references stu(sid));
# 插入数据成功
mysql>
insert into stu (name) value ('kkk');
insert into sc(sid,score) values ('1','98');

#2、建两张表,有外键更新约束
mysql>
drop table sc;
drop table stu;
mysql>
create table stu(
sid int unsigned primary key auto_increment,
name varchar(20) not null);
mysql>
create table sc(
scid int unsigned primary key auto_increment,
sid int unsigned not null,
score varchar(20) default '0',
index (sid), 
foreign key (sid) references stu(sid) on delete cascade on update cascade);
# 插入外键更新约束的数据失败
mysql> insert into stu (name) value ('kkk');
Query OK, 1 row affected (0.00 sec)
mysql> insert into sc(sid,score) values ('1','98');
ERROR 3098 (HY000): The table does not comply with the requirements by an external plugin.
mysql> 
2.3.4 多主切换单主模式
# 所有节点执行
mysql>
stop group_replication;
set global group_replication_enforce_update_everywhere_checks=OFF;
set global group_replication_single_primary_mode=ON;

# 主节点执行
mysql>
SET GLOBAL group_replication_bootstrap_group=ON;
START GROUP_REPLICATION;
SET GLOBAL group_replication_bootstrap_group=OFF;

# 从节点执行
mysql> START GROUP_REPLICATION;

# 查看MGR组信息
mysql> SELECT * FROM performance_schema.replication_group_members;
2.3.5 参数持久化
#根据场景使用多主或单主模式
#on或off如下两个参数
#如下示例设置是开启多主模式
[root@node1 ~]# vim /etc/my.cnf
[mysqld]
#关闭单主模式的参数
group_replication_single_primary_mode=off
#开启多主模式的参数
group_replication_enforce_update_everywhere_checks=on

2.4 使用函数配置组复制模式

除了以上使用参数配置复制模式,也可以用新版本的函数配置模式。可以使用一组依赖于组操作协调器的函数在组复制运行时联机配置组,这些函数由版本 MySQL 8.0.13 及更高版本中的组复制插件提供。为使协调器能够在正在运行的组上进行配置,所有成员必须 MySQL 8.0.13 或更高版本。

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

  • 可以在任何服务器发布配置操作,所有操作都以协调的方式发送到所有组成员上执行。如果调用成员宕机,任何已在运行的配置过程继续在其它成员上运行。
  • 配置更改期间,任何成员都无法加入组,在协调配置更改期间尝试加入组的任何成员将离开该组并取消其加入过程。
  • 一次只能执行一个配置。正在执行配置更改的组不能接受任何其它组配置更改,因为并发配置操作可能导致成员分歧。
  • 不能在混合版本组上使用此配置功能。由于这些配置操作的分布式特性,所有成员必须识别它们才能执行。因此,组中不能存在旧版本的服务器,否则操作被拒绝。

可以在任何成员上运行函数。操作运行时可以通过发出以下命令来检查其进度:

select event_name, work_completed, work_estimated from performance_schema.events_stages_current where event_name like "%stage/group_rpl%";
2.4.1 更改主服务器

使用group_replication_set_as_primary() 函数更改单主组中的主服务器,对于多主组复制此功能无效。只有主库才允许写入,因此如果该成员上正在运行异步通道复制,则在异步通道复制停止之前不允许切换。通过发出以下命令传递要成为该组新主服务器成员的 server_uuid:

# 查看当前node1为主节点
mysql> select member_id,member_host,MEMBER_STATE,member_role from performance_schema.replication_group_members;
+--------------------------------------+-------------+--------------+-------------+
| member_id                            | member_host | MEMBER_STATE | member_role |
+--------------------------------------+-------------+--------------+-------------+
| 2dd88a45-6161-11ed-b5d2-000c299aa451 | node3       | ONLINE       | SECONDARY   |
| 30c9d73b-6161-11ed-b523-000c2913fd72 | node2       | ONLINE       | SECONDARY   |
| 32710bb7-6161-11ed-b45b-000c29b2256f | node1       | ONLINE       | PRIMARY     |
+--------------------------------------+-------------+--------------+-------------+
3 rows in set (0.00 sec)
# 使用函数切换主节点为node2
mysql> select group_replication_set_as_primary('30c9d73b-6161-11ed-b523-000c2913fd72');
+--------------------------------------------------------------------------+
| group_replication_set_as_primary('30c9d73b-6161-11ed-b523-000c2913fd72') |
+--------------------------------------------------------------------------+
| Primary server switched to: 30c9d73b-6161-11ed-b523-000c2913fd72         |
+--------------------------------------------------------------------------+
1 row in set (0.03 sec)
# node2已经变成主节点
mysql> select member_id,member_host,MEMBER_STATE,member_role from performance_schema.replication_group_members;
+--------------------------------------+-------------+--------------+-------------+
| member_id                            | member_host | MEMBER_STATE | member_role |
+--------------------------------------+-------------+--------------+-------------+
| 2dd88a45-6161-11ed-b5d2-000c299aa451 | node3       | ONLINE       | SECONDARY   |
| 30c9d73b-6161-11ed-b523-000c2913fd72 | node2       | ONLINE       | PRIMARY     |
| 32710bb7-6161-11ed-b45b-000c29b2256f | node1       | ONLINE       | SECONDARY   |
+--------------------------------------+-------------+--------------+-------------+
3 rows in set (0.00 sec)
2.4.2 单主切换多主模式

group_replication_switch_to_multi_primary_mode()函数用于单主改多主:

# 查看当前单主模式
mysql> select member_id,member_host,MEMBER_STATE,member_role from performance_schema.replication_group_members;
+--------------------------------------+-------------+--------------+-------------+
| member_id                            | member_host | MEMBER_STATE | member_role |
+--------------------------------------+-------------+--------------+-------------+
| 2dd88a45-6161-11ed-b5d2-000c299aa451 | node3       | ONLINE       | SECONDARY   |
| 30c9d73b-6161-11ed-b523-000c2913fd72 | node2       | ONLINE       | PRIMARY     |
| 32710bb7-6161-11ed-b45b-000c29b2256f | node1       | ONLINE       | SECONDARY   |
+--------------------------------------+-------------+--------------+-------------+
3 rows in set (0.00 sec)
# 使用函数单主模式切换多主模式
mysql> 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)
# 查看已经是多主模式
mysql> select member_id,member_host,MEMBER_STATE,member_role from performance_schema.replication_group_members;
+--------------------------------------+-------------+--------------+-------------+
| member_id                            | member_host | MEMBER_STATE | member_role |
+--------------------------------------+-------------+--------------+-------------+
| 2dd88a45-6161-11ed-b5d2-000c299aa451 | node3       | ONLINE       | PRIMARY     |
| 30c9d73b-6161-11ed-b523-000c2913fd72 | node2       | ONLINE       | PRIMARY     |
| 32710bb7-6161-11ed-b45b-000c29b2256f | node1       | ONLINE       | PRIMARY     |
+--------------------------------------+-------------+--------------+-------------+
3 rows in set (0.00 sec)
2.4.3 多主切换单主模式

group_replication_switch_to_single_primary_mode()函数用于多主改单主:

# 使用函数多主模式切换单主模式
mysql> select group_replication_switch_to_single_primary_mode('32710bb7-6161-11ed-b45b-000c29b2256f');
+-----------------------------------------------------------------------------------------+
| group_replication_switch_to_single_primary_mode('32710bb7-6161-11ed-b45b-000c29b2256f') |
+-----------------------------------------------------------------------------------------+
| Mode switched to single-primary successfully.                                           |
+-----------------------------------------------------------------------------------------+
1 row in set (0.04 sec)
# 查看已经是单主模式
mysql> select member_id,member_host,MEMBER_STATE,member_role from performance_schema.replication_group_members;
+--------------------------------------+-------------+--------------+-------------+
| member_id                            | member_host | MEMBER_STATE | member_role |
+--------------------------------------+-------------+--------------+-------------+
| 2dd88a45-6161-11ed-b5d2-000c299aa451 | node3       | ONLINE       | SECONDARY   |
| 30c9d73b-6161-11ed-b523-000c2913fd72 | node2       | ONLINE       | SECONDARY   |
| 32710bb7-6161-11ed-b45b-000c29b2256f | node1       | ONLINE       | PRIMARY     |
+--------------------------------------+-------------+--------------+-------------+
3 rows in set (0.00 sec)

如果未传入任何字符串,则新主节点的选择由配置的选举权重或server_uuid字典顺序控制。

2.5 配置并发写实例数

group_replication_get_write_concurrency()函数用于在运行时检查组的最大并行可写实例数。缺省值为 10 适用于 LAN 上运行的组,对于通过较慢网络上 (如 WAN) 运行的组,增加此数量可以微调组复制的性能。

mysql> select group_replication_get_write_concurrency();
+-------------------------------------------+
| group_replication_get_write_concurrency() |
+-------------------------------------------+
|                                        10 |
+-------------------------------------------+
1 row in set (0.01 sec)

group_replication_set_write_concurrency()函数用于在运行时设置组的最大并行可写实例数,值域为 10-200。该函数是异步执行的,可以调用group_replication_get_write_concurrency确认设置生效。

mysql> select group_replication_set_write_concurrency(1);
ERROR 1123 (HY000): Can't initialize function 'group_replication_set_write_concurrency'; Argument must be between 10 and 200.
mysql> select group_replication_set_write_concurrency(201);
ERROR 1123 (HY000): Can't initialize function 'group_replication_set_write_concurrency'; Argument must be between 10 and 200.
mysql> select group_replication_set_write_concurrency(10);
+-----------------------------------------------------------------------------------+
| group_replication_set_write_concurrency(10)                                       |
+-----------------------------------------------------------------------------------+
| UDF is asynchronous, check log or call group_replication_get_write_concurrency(). |
+-----------------------------------------------------------------------------------+
1 row in set (0.00 sec)

2.6 设置组的通信协议版本

从 MySQL 8.0.16 开始,组复制具有通信协议的概念。可以显式管理组复制通信协议版本,并将其设置为支持的最老的 MySQL 服务器版本。这使得组可以由不同 MySQL 服务器版本的成员组成,同时确保向后兼容性。该组的所有成员必须使用相同的通信协议版本,以便发送所有组成员都能理解的消息。如果组的通信协议版本小于或等于 X,则版本 X 的 MySQL 服务器加入到复制组并达到ONLINE状态。新成员加入复制组时,该组的现有成员会检查加入成员的通信协议版本。如果支持该版本,则将它加入该组并使用该组已宣布的通信协议。如果不支持通信协议版本,则将其从组中移除。

只有新成员的通信协议版本与该组的通信协议版本兼容时,它们才能加入。加入该组的具有不同通信协议版本的成员必须单独加入。例如:

  • 一个 MySQL Server 8.0.16 实例可以成功加入使用通信协议版本 5.7.24 的组。
  • 一个 MySQL Server 5.7.24 实例无法成功加入使用通信协议版本 8.0.16 的组。
  • 两个 MySQL Server 8.0.16 实例无法同时加入使用通信协议版本 5.7.24 的组。
  • 两个 MySQL Server 8.0.16 实例可以同时加入使用通信协议版本 8.0.16 的组。

group_replication_get_communication_protocol()函数用于检查组使用的通信协议,该函数返回该组支持的最老的 MySQL 服务器版本。该组的所有现有成员都返回相同的通信协议版本。

mysql> select group_replication_get_communication_protocol();
+------------------------------------------------+
| group_replication_get_communication_protocol() |
+------------------------------------------------+
| 8.0.27                                         |
+------------------------------------------------+
1 row in set (0.00 sec)

group_replication_get_communication_protocol()的返回值可能与传递给group_replication_set_communication_protocol 的版本号以及该组成员上正在使用的 MySQL 服务器版本不同。如果需要更改组的通信协议版本以便早期版本的成员可以加入,使用group_replication_set_communication_protocol()函数指定要允许的最老成员的 MySQL 服务器版本。这使得该组在可能的情况下回退到兼容的通信协议版本。使用此函数需要GROUP_REPLICATION_ADMIN权限,并且在发出语句时所有现有组成员必须处于在线状态。

#指定要允许的最老成员的MySQL服务器版本。这使得该组在可能的情况下回退到兼容的通信协议版本
mysql> 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.01 sec)
 
mysql> select group_replication_get_communication_protocol();
+------------------------------------------------+
| group_replication_get_communication_protocol() |
+------------------------------------------------+
| 5.7.14                                         |
+------------------------------------------------+
1 row in set (0.00 sec)

如果将复制组的所有成员升级到新的 MySQL 服务器版本,则该组的通信协议版本不会自动升级以匹配。必须使用group_replication_set_communication_protocol()函数将通信协议版本设置为新 MySQL 服务器版本。

mysql> select group_replication_set_communication_protocol("8.0.27");
+-----------------------------------------------------------------------------------+
| group_replication_set_communication_protocol("8.0.27")                            |
+-----------------------------------------------------------------------------------+
| The operation group_replication_set_communication_protocol completed successfully |
+-----------------------------------------------------------------------------------+
1 row in set (0.00 sec)

group_replication_set_communication_protocol()函数作为组操作实现,因此它将在组的所有成员上同时执行。组操作开始缓冲消息并等待传递已完成的任何传出消息,然后更改通信协议版本并发送缓冲的消息。如果成员在更改通信协议版本后加入该组,则组成员将使用新协议版本。

2.7 恢复其他配置

新成员加入复制组时,它会连接到一个合适的捐赠者 (donor),从那里获取缺失的历史数据,直到它变为在线状态为止。此过程就是“1.8.5 分布式恢复”中详细讨论的分布式恢复。这里侧重如何设置分布式恢复相关的系统变量。捐赠者是从组中当前在线成员中随机选择的,这样当多个成员进入组时,很大可能不会选择同一服务器作为捐赠者。如果新成员与捐赠者的连接失败,会自动尝试连接到另一个新的候选捐赠者。达到连接重试限制后,恢复过程将终止并显示错误。组复制提供了强大的错误检测机制,能够在整个恢复过程中应对失败。例如,当出现以下问题时,恢复都能检测到错误并尝试切换到新的捐赠者:

  • 加入组的服务器已经包含的数据与恢复期间来自所选捐赠者的数据存在冲突。
  • 赠者包含新增成员已经清除 (purge) GTID 的数据。
  • 恢复的接收线程或应用线程失败。
2.7.1 设置重连次数

如果出现一些持续性故障甚至是瞬态故障,恢复将自动重试连接到相同或新的捐赠者。恢复数据传输依赖于二进制日志和现有的 MySQL 异步复制框架,因此一些瞬态错误可能会导致接收线程或应用线程错误。在这种情况下,捐赠者切换进程具有重试功能,重试次数通过group_replication_recovery_retry_count插件变量设置,缺省值为 10。该变量指定服务器连接到每个合适的捐赠者的全局尝试次数。

mysql> show variables like 'group_replication_recovery_retry_count';
+----------------------------------------+-------+
| Variable_name                          | Value |
+----------------------------------------+-------+
| group_replication_recovery_retry_count | 10    |
+----------------------------------------+-------+
1 row in set (0.01 sec)
# 全局参数
mysql> set group_replication_recovery_retry_count=10;
ERROR 1229 (HY000): Variable 'group_replication_recovery_retry_count' is a GLOBAL variable and should be set with SET GLOBAL
mysql> set global group_replication_recovery_retry_count=10;
Query OK, 0 rows affected (0.00 sec)
2.7.2 设置休眠时间

group_replication_recovery_reconnect_interval插件变量定义恢复进程在捐赠者连接尝试之间应休眠的时间,默认设置为 60 秒,可以动态更改。

mysql> show variables like 'group_replication_recovery_reconnect_interval';
+-----------------------------------------------+-------+
| Variable_name                                 | Value |
+-----------------------------------------------+-------+
| group_replication_recovery_reconnect_interval | 60    |
+-----------------------------------------------+-------+
1 row in set (0.02 sec)
# 全局参数
mysql> set group_replication_recovery_reconnect_interval=60;
ERROR 1229 (HY000): Variable 'group_replication_recovery_reconnect_interval' is a GLOBAL variable and should be set with SET GLOBAL
mysql> set global group_replication_recovery_reconnect_interval=60;
Query OK, 0 rows affected (0.00 sec)

并不是每次尝试连接捐赠者后都休眠。只有当加入组的服务器尝试连接到该组中所有合适的捐赠者并且没有剩余时,恢复进程才会休眠由group_replication_recovery_reconnect_interval变量配置的秒数。

2.7.3 网络分区

事务复制、组成员身份更改,以及一些使组保持一致的内部消息传递,都需要组成员达成共识。这要求大多数组成员就特定决定达成一致。当大多数组成员丢失时,组复制无法正常进行,因为无法保证多数或法定票数。例如,在一个 5 台服务器的复制组中,如果其中 3 台异常宕机,则大无法达到法定票数。事实上,剩下的两个服务器无法判断其它三台服务器是否已崩溃,或者网络分区是否已将这两台服务器单独隔离,因此无法自动重新配置该组。

另一方面,如果服务器自愿退出组,它们会指示组应该重新配置自己,这意味着离开的服务器告诉其它成员它要退出。这种情况下其它成员可以正确地重新配置组,保持成员的数据一致性并重新计算法定票数。在上述 5 个服务器离开 3 个的场景中,如果 3 个离开的服务器一个接一个地通知组它们要离开,那么成员资格能够从 5 调整到 2,同时确保法定票数。法定票数丧失本身是不良计划的结果。无论故障是连续发生,一次性发生,还是零星发生,通常容忍 f 个故障机所需的服务器数量 n 为:n = 2*f + 1

下面演示一个网络分区的例子。如下所示一个三个成员的单主模式复制组,成员均为在线状态:

# 1、单主模式node1
mysql> select member_id, member_host, member_state, member_role from performance_schema.replication_group_members;
+--------------------------------------+-------------+--------------+-------------+
| member_id                            | member_host | member_state | member_role |
+--------------------------------------+-------------+--------------+-------------+
| 2dd88a45-6161-11ed-b5d2-000c299aa451 | node3       | ONLINE       | SECONDARY   |
| 30c9d73b-6161-11ed-b523-000c2913fd72 | node2       | ONLINE       | SECONDARY   |
| 32710bb7-6161-11ed-b45b-000c29b2256f | node1       | ONLINE       | PRIMARY     |
+--------------------------------------+-------------+--------------+-------------+
3 rows in set (0.00 sec)
# 2、kill掉两从
ps -ef | grep mysqld | grep -v grep | awk '{print $2}' | xargs kill -9

[root@node2 ~]# ps -ef | grep mysqld | grep -v grep | awk '{print $2}' | xargs kill -9
[root@node3 ~]# ps -ef | grep mysqld | grep -v grep | awk '{print $2}' | xargs kill -9
# 3、主服务器node1上再次查看成员状态
mysql> select member_id, member_host, member_state, member_role from performance_schema.replication_group_members;
+--------------------------------------+-------------+--------------+-------------+
| member_id                            | member_host | member_state | member_role |
+--------------------------------------+-------------+--------------+-------------+
| 2dd88a45-6161-11ed-b5d2-000c299aa451 | node3       | UNREACHABLE  | SECONDARY   |
| 30c9d73b-6161-11ed-b523-000c2913fd72 | node2       | UNREACHABLE  | SECONDARY   |
| 32710bb7-6161-11ed-b45b-000c29b2256f | node1       | ONLINE       | PRIMARY     |
+--------------------------------------+-------------+--------------+-------------+
3 rows in set (0.00 sec)

可以看到 node1 仍为在线状态,但 node2、node3 处于不可到达状态。而且,系统无法重新配置自己以改变成员资格,因为已经无法达到法定票数 2。此时 node1 虽然在线,但无法执行任何事务,没有外部干预就无法继续提供服务。在这种特殊情况下,需要重置组成员资格以允许组复制继续进行。此时有两种选择:

  • 第一种:重启整个组复制

    # node1上执行
    stop group_replication;
    set global group_replication_bootstrap_group=on;
    start group_replication;
    set global group_replication_bootstrap_group=off;
    # 此方法实际上是重新初始化新的一个复制组,该复制组中只有node1一个成员(引导成员)。
    
  • 第二种:强制指定组成员

    第二种方法是使用group_replication_force_members变量强制指定组成员。组复制可以通过强制执行特定配置来重置组成员身份列表。本例中 node1 是唯一在线服务器,因此可以选择强制 node1 的成员资格配置。必须强调,使用group_replication_force_members应被视为最后的补救措施,必须非常小心地使用,并且只能用于失败服务器大于等于法定票数的场景。如果误用,可能会创建一个人工的裂脑情景或完全阻止整个系统。

    # 1、首先要检查node1的组通信标识符,在node1上执行下面的查询获取此信息。
    mysql> select @@group_replication_local_address;
    +-----------------------------------+
    | @@group_replication_local_address |
    +-----------------------------------+
    | 192.168.2.81:33061                |
    +-----------------------------------+
    1 row in set (0.00 sec)
    # 2、然后设置group_replication_force_members变量值为上面的查询结果,强制组成员为node1:
    mysql> set global group_replication_force_members="192.168.2.81:33061";
    Query OK, 0 rows affected (57.63 sec)
    	-- 这会通过强制执行不同的配置来取消阻止该组。
    	-- 检查node1上的replication_group_members以在此更改后验证组成员身份。
    mysql> select member_id, member_host, member_state, member_role from performance_schema.replication_group_members;
    +--------------------------------------+-------------+--------------+-------------+
    | member_id                            | member_host | member_state | member_role |
    +--------------------------------------+-------------+--------------+-------------+
    | 32710bb7-6161-11ed-b45b-000c29b2256f | node1       | ONLINE       | PRIMARY     |
    +--------------------------------------+-------------+--------------+-------------+
    1 row in set (0.00 sec)
    

    强制执行新的成员资格配置时,必须保证所有强制退出该组的服务器确实已停止。在上面描述的场景中,如果 node2、node3 不可达 (如断网) 但是 MySQL 实例可用,则它们可能已经形成了自己的网络分区 (它们是 3 个中的 2 个,因此占大多数)。这种情况下强制使用node1的组成员列表可能会产生人为的裂脑情况。因此,在强制执行新的成员资格配置前,要确保排除的服务器实例已关闭,然后再继续执行。使用group_replication_force_members系统变量成功强制新的组成员身份并取消阻止该组后,应该清除该系统变量。group_replication_force_members必须为空才能发出START GROUP_REPLICATION语句。

    mysql> show variables like 'group_replication_force_members';
    +---------------------------------+--------------------+
    | Variable_name                   | Value              |
    +---------------------------------+--------------------+
    | group_replication_force_members | 192.168.2.81:33061 |
    +---------------------------------+--------------------+
    1 row in set (0.00 sec)
     
    mysql> stop group_replication;
    Query OK, 0 rows affected (13.13 sec)
    # 放空才可执行 START GROUP_REPLICATION
    mysql> start group_replication;
    ERROR 3092 (HY000): The server is not configured properly to be an active member of the group. Please see more details on error log.
    # 全局变量
    mysql> set group_replication_force_members="";
    ERROR 1229 (HY000): Variable 'group_replication_force_members' is a GLOBAL variable and should be set with SET GLOBAL
    mysql> set global group_replication_force_members="";
    Query OK, 0 rows affected (0.01 sec)
    # 开启组复制
    mysql>
    set global group_replication_bootstrap_group=on;
    start group_replication;
    set global group_replication_bootstrap_group=off;
    

无论使用哪种方法恢复了组复制,都可以在 node2、node3 重新可用后,将它们重新添加到新的复制组。

[root@node2 ~]# systemctl start mysqld
[root@node2 ~]# mysql -proot
mysql> start group_replication;
# 之后验证组成员身份已经恢复到最初的状态:
mysql> select member_id, member_host, member_state, member_role from performance_schema.replication_group_members;
+--------------------------------------+-------------+--------------+-------------+
| member_id                            | member_host | member_state | member_role |
+--------------------------------------+-------------+--------------+-------------+
| 2dd88a45-6161-11ed-b5d2-000c299aa451 | node3       | ONLINE       | SECONDARY   |
| 30c9d73b-6161-11ed-b523-000c2913fd72 | node2       | ONLINE       | SECONDARY   |
| 32710bb7-6161-11ed-b45b-000c29b2256f | node1       | ONLINE       | PRIMARY     |
+--------------------------------------+-------------+--------------+-------------+
3 rows in set (0.00 sec)

3. 组复制监控

与监控传统主从复制的show slave status不同,组复制监控主要依赖以下几个 performance_schema 表:

  • performance_schema.replication_group_members
  • performance_schema.replication_group_member_stats
  • performance_schema.replication_connection_status
  • performance_schema.replication_applier_status

3.1 replication_group_members

**performance_schema.replication_group_members**表用于监视作为组成员的不同服务器实例的状态。只要视图更改,就会更新表中的信息。例如,因新成员加入而动态更改组的配置时。此时,服务器交换一些元数据以使其自身同步并继续一起协作。信息在作为复制组成员的所有服务器实例之间共享,因此可以从任何成员查询有关所有组成员的信息。此表可用作获取复制组状态的高级视图。

CHANNEL_NAME				#:通道名称。组复制插件创建两个复制通道。
							#: group_replication_recovery用于与分布式恢复阶段相关的复制更改。
							#: group_replication_applier用于来自组传入的更改,是应用直接来自组的事务的通道。
MEMBER_ID					#:组成员实例的server_uuid。
MEMBER_HOST					#:组成员主机名。如果配置了report_host参数,这里显示IP地址。
MEMBER_PORT					#:组成员主机端口
MEMBER_STATE				#:成员状态,取值和含义如下表所示:
MEMBER_ROLE					#:成员角色,主为PRIMARY,从为SECONDARY。
MEMBER_VERSION				#:成员数据库实例版本。
EMBER_COMMUNICATION_STACK	#:通讯协议
取值含义状态是否在组内同步
ONLINE表示该成员可正常提供服务YES
RECOVERING表示当前成员正在从其它节点恢复数据YES
OFFLINE表示组复制插件已经加载,但是该成员不属于任何一个复制组NO
ERROR表示成员在recovery阶段出现错误或者从其它节点同步状态中出现错误NO
UNREACHABLE成员处于不可达状态,无法与之进行网络通讯NO

从上表可以知道,只有 ONLINE 和 RECOVERING 两种状态会在集群中得到同步。这个状态同步是指状态在所有成员上查询均能保持一致。对于 OFFLINE、ERROR和UNREABLE:

  • 只有在当前 OFFLINE 成员查询 replication_group_members 表才能得到 OFFLINE 状态,在其它成员上查询 replication_group_members 表,则没有该成员的状态,因为 OFFLINE 成员已经不属于这个复制组了。
  • 只有在当前 ERROR 成员查询 replication_group_members 表才能得到 ERROR 状态,同上面的 OFFLINE,在其它成员上查询也看不到该成员。
  • 假设成员 A 与 B 网络通讯失败,那么在节点 A 上查询 replication_group_members 表,有可能得到 B 的状态为 UNREACHABLE。

成员状态转移如图所示。

当一个成员加进一个复制组,其状态首先变成 RECOVERING,表示当前成员正处于集群恢复阶段。这个阶段下,成员会选择集群中一个成员作为捐赠者 (donor),利用传统的异步复制做数据恢复。当数据能够成功追平,成员的状态将会变成 ONLINE,这个过程中通过其他成员也可以看到该节点的状态,不管是 RECOVERING 还是最后的 ONLINE。

假如该成员在 RECOVERING 阶段出现了异常,如选择 donor 进行复制失败或者在追赶 donor 数据的过程中失败,那么该成员的状态将会变成 ERROR。注意,这时候在其它成员上查询时,发现该 RECOVERING 节点已经从组里面被移除。另外,如果一个 ONLINE 成员失去与其它成员的通讯 (可能因为成员宕机或者网络异常),则该成员在其他成员上面查询到的状态将会是UNREACHABLE。如果这个 UNREACHABLE 成员在规定的超时时间内没有恢复,那么成员将会被移除。这个规定的超时时间,取决于集群失去这个成员后还能不能达到可用状态。如果失去这个成员集群仍然可用,那么这个 UNREACHABLE 的超时时间很短,几乎看不到这个状态。但是,如果失去这个成员后集群马上不可用,那么这个成员将会一直处于 UNREACHABLE 状态。

以一个例子来验证。kill (注意是kill实例而不是正常shutdown实例) node3 的 MySQL 实例。

# 当前单主模式
# 注意实验时,切换单主或多主模式的参数是否持久化,导致重启实例加入组复制报错问题
[root@node3 ~]#
ps -ef | grep mysqld | grep -v grep | awk '{print $2}' | xargs kill -9

通过其它可用成员查询到,那一kill掉的实例从集群中被移除了:

mysql> SELECT * FROM performance_schema.replication_group_members;
+---------------------------+--------------------------------------+-------------+-------------+--------------+-------------+----------------+----------------------------+
| CHANNEL_NAME              | MEMBER_ID                            | MEMBER_HOST | MEMBER_PORT | MEMBER_STATE | MEMBER_ROLE | MEMBER_VERSION | MEMBER_COMMUNICATION_STACK |
+---------------------------+--------------------------------------+-------------+-------------+--------------+-------------+----------------+----------------------------+
| group_replication_applier | 30c9d73b-6161-11ed-b523-000c2913fd72 | node2       |        3306 | ONLINE       | SECONDARY   | 8.0.31         | XCom                       |
| group_replication_applier | 32710bb7-6161-11ed-b45b-000c29b2256f | node1       |        3306 | ONLINE       | PRIMARY     | 8.0.31         | XCom                       |
+---------------------------+--------------------------------------+-------------+-------------+--------------+-------------+----------------+----------------------------+
2 rows in set (0.01 sec)

接下来再 kill 掉 node2 的 MySQL 实例。再次查询replication_group_members:

[root@node2 ~]#
ps -ef | grep mysqld | grep -v grep | awk '{print $2}' | xargs kill -9
# 查看node2一直处于UNREACHABLE
mysql> SELECT * FROM performance_schema.replication_group_members;
+---------------------------+--------------------------------------+-------------+-------------+--------------+-------------+----------------+----------------------------+
| CHANNEL_NAME              | MEMBER_ID                            | MEMBER_HOST | MEMBER_PORT | MEMBER_STATE | MEMBER_ROLE | MEMBER_VERSION | MEMBER_COMMUNICATION_STACK |
+---------------------------+--------------------------------------+-------------+-------------+--------------+-------------+----------------+----------------------------+
| group_replication_applier | 30c9d73b-6161-11ed-b523-000c2913fd72 | node2       |        3306 | UNREACHABLE  | SECONDARY   | 8.0.31         | XCom                       |
| group_replication_applier | 32710bb7-6161-11ed-b45b-000c29b2256f | node1       |        3306 | ONLINE       | PRIMARY     | 8.0.31         | XCom                       |
+---------------------------+--------------------------------------+-------------+-------------+--------------+-------------+----------------+----------------------------+
2 rows in set (0.00 sec)

这个时候,UNREACHABLE 状态将一直持续。而且此时,集群不满足 2N + 1,集群已经不可用 (即使有主成员,它也是不可写的)。恢复组复制步骤:

#1\在node1重新创建一个新的复制组
stop group_replication;
set global group_replication_bootstrap_group=on;
start group_replication;
set global group_replication_bootstrap_group=off;
#2\启动node2、node3的MySQL实例
systemctl start mysqld
#3\将node2、node3重新加入新复制组
change master to master_user='repl', master_password='repl' for channel 'group_replication_recovery';
start group_replication;
# 此时查看二进制日志中的视图更改事件,可以看到创建过两个复制组,每添加一个成员,view_id的序号加1。
[root@node1 mysql]# mysqlbinlog binlog.000010 | grep view_id
#221112 11:23:23 server id 81  end_log_pos 540 View_change_log_event: view_id=16682234050793514:1
#221112 11:23:23 server id 81  end_log_pos 923 View_change_log_event: view_id=16682234050793514:2
#221112 11:23:23 server id 81  end_log_pos 1306 View_change_log_event: view_id=16682234050793514:3
#221112 11:43:43 server id 81  end_log_pos 1689 View_change_log_event: view_id=16682246251187675:1
#221112 11:43:43 server id 81  end_log_pos 2072 View_change_log_event: view_id=16682246251187675:2
#221112 11:43:43 server id 81  end_log_pos 2455 View_change_log_event: view_id=16682246251187675:3
#221112 11:49:13 server id 81  end_log_pos 2838 View_change_log_event: view_id=16682249547626734:1
#221112 11:49:13 server id 81  end_log_pos 3221 View_change_log_event: view_id=16682249547626734:2
#221112 11:49:13 server id 81  end_log_pos 3604 View_change_log_event: view_id=16682249547626734:3

3.2 replication_group_member_stats

performance_schema.replication_group_member_stats表提供与认证过程相关的组级信息,以及由复制组的每个成员接收和发起的事务的统计信息。信息在作为复制组成员的所有服务器实例之间共享,因此可以从任何成员查询有关所有组成员的信息。刷新远程成员的统计信息由group_replication_flow_control_period配置参数中指定的消息周期控制 (缺省值为1秒),因此可能与本地收集的查询成员的统计信息略有不同。

#performance_schema.replication_group_member_stats表字段含义如下:
CHANNEL_NAME						#:组复制通道名称。
VIEW_ID								#:复制组当前视图ID。
MEMBER_ID							#:组成员的server_uuid。
COUNT_TRANSACTIONS_IN_QUEUE			#:队列中等待冲突检测的事务数。一旦事务通过了冲突检查,它们就会排队等待应用。
COUNT_TRANSACTIONS_CHECKED			#:通过冲突检查的事务数。
COUNT_CONFLICTS_DETECTED			#:未通过冲突检测检查的事务数。
COUNT_TRANSACTIONS_ROWS_VALIDATING	#:冲突检查数据库的大小。
TRANSACTIONS_COMMITTED_ALL_MEMBERS	#:已在复制组的所有成员上成功提交的事务,显示为GTID集。这是以固定的时间间隔更新的。
LAST_CONFLICT_FREE_TRANSACTION		#:最后一次冲突,被释放的事务标识符。
COUNT_TRANSACTIONS_REMOTE_IN_APPLIER_QUEUE	#:此成员从复制组收到的等待应用的事务数。
COUNT_TRANSACTIONS_REMOTE_APPLIED	#:此成员从复制组中收到并已应用的事务数。
COUNT_TRANSACTIONS_LOCAL_PROPOSED	#:源自此成员并发送给复制组的事务数。
COUNT_TRANSACTIONS_LOCAL_ROLLBACK	#:源自此成员并被复制组回滚的事务数。

该表字段对于监视组中连接成员的性能很重要。例如,假设组中的一个成员总是在其队列中报告与其它成员相比存在大量事务。这意味着该成员存在延迟,并且无法与该组的其它成员保持同步。根据此信息,可能决定从组中删除该成员,或者延迟处理该组其它成员上的事务,以减少排队事务的数量。此信息还可以帮助决定如何调整组复制插件的流控制。

3.3 replication_connection_status

performance_schema.replication_connection_status显示有关组复制的信息,例如已从组接收并在应用程序队列 (中继日志) 中排队的事务。performance_schema.replication_applier_status显示与组复制相关的通道和线程的状态。如果有许多不同的工作线程应用事务,那么该表也可用于监视每个工作线程正在执行的操作。

#performance_schema.replication_connection_status表字段含义如下:
CHANNEL_NAME										#:组复制通道名称。始终存在默认复制通道,可以添加更多复制通道。
GROUP_NAME											#:如果此服务器是组的成员,则显示服务器所属的组的名称。
SOURCE_UUID											#:组标识符。
THREAD_ID											#:I/O线程ID。
SERVICE_STATE										#:ON表示线程存在且处于活动状态或空闲状态
													#: OFF表示线程不再存在,CONNECTING表示线程存在并连接到主服务器。
COUNT_RECEIVED_HEARTBEATS							#:成员自上次重启、重置、或者发出了CHANGE MASTER TO语句后,收到的心跳信号总数。
LAST_HEARTBEAT_TIMESTAMP							#:成员收到最新心跳信号的时间戳。
RECEIVED_TRANSACTION_SET							#:已经被接收的GTID集。
LAST_ERROR_NUMBER									#:导致I/O线程停止的最新错误的错误号,0表示无错误。RESET MASTER或RESET SLAVE将重置该列中显示的值。
LAST_ERROR_MESSAGE									#:导致I/O线程停止的最新错误的错误消息,空字符串表示无错误。RESET MASTER或RESET SLAVE将重置该列中显示的值。
LAST_ERROR_TIMESTAMP								#:最近发生I/O错误的时间戳。
LAST_QUEUED_TRANSACTION								#:排队到中继日志的最后一个事务的GTID。
LAST_QUEUED_TRANSACTION_ORIGINAL_COMMIT_TIMESTAMP	#:中继日志中排队的最后一个事务在原始主服务器上提交的时间戳。
LAST_QUEUED_TRANSACTION_IMMEDIATE_COMMIT_TIMESTAMP	#:中继日志中排队的最后一个事务在当前服务器上提交的时间戳。
LAST_QUEUED_TRANSACTION_START_QUEUE_TIMESTAMP		#:I/O线程将最后一个事务放入中继日志队列的时间戳。
LAST_QUEUED_TRANSACTION_END_QUEUE_TIMESTAMP			#:最后一个事务排队到中继日志文件的时间戳。
QUEUEING_TRANSACTION								#:中继日志中当前排队事务GTID。
QUEUEING_TRANSACTION_ORIGINAL_COMMIT_TIMESTAMP		#:当前排队事务在原始主服务器上提交的时间戳。
QUEUEING_TRANSACTION_IMMEDIATE_COMMIT_TIMESTAMP		#:当前排队事务在当前服务器上提交的时间戳。
QUEUEING_TRANSACTION_START_QUEUE_TIMESTAMP			#:当前排队事务的第一个事件何时被I/O线程写入中继日志。

3.4 replication_applier_status

performance_schema.replication_applier_status表字段含义如下:

CHANNEL_NAME				#:制通道名称。始终存在默认复制通道,可以添加更多复制通道。
SERVICE_STATE				#:复制通道的应用程序线程处于活动状态或空闲状态时显示ON,OFF表示应用程序线程未处于活动状态。
REMAINING_DELAY				#:迟复制时则此字段指示剩余的延迟秒数,其它情况此字段为NULL。 
COUNT_TRANSACTIONS_RETRIES	#:示由于SQL线程无法应用事务而进行的重试次数。
							#:事务的最大重试次数由slave_transaction_retries系统变量设置。
							#:plication_applier_status_by_worker表显示有关单线程或多线程从库的事务重试的详细信息。

4. 多主模式容错测试

4.1 单节点故障

当组内的某个节点发生故障时,会自动从将该节点从组内踢出,与其他节点隔离。剩余的节点之间保持主从或主主复制的正常同步关系。当该节点的故障恢复后,只需手动激活组复制即可 (即执行START GROUP_REPLICATION;);

4.1.1 节点1故障
# 查看当前节点都是PRIMARY,多主模式
mysql> SELECT * FROM performance_schema.replication_group_members;
+---------------------------+--------------------------------------+-------------+-------------+--------------+-------------+----------------+----------------------------+
| CHANNEL_NAME              | MEMBER_ID                            | MEMBER_HOST | MEMBER_PORT | MEMBER_STATE | MEMBER_ROLE | MEMBER_VERSION | MEMBER_COMMUNICATION_STACK |
+---------------------------+--------------------------------------+-------------+-------------+--------------+-------------+----------------+----------------------------+
| group_replication_applier | 2dd88a45-6161-11ed-b5d2-000c299aa451 | node3       |        3306 | ONLINE       | PRIMARY     | 8.0.31         | XCom                       |
| group_replication_applier | 30c9d73b-6161-11ed-b523-000c2913fd72 | node2       |        3306 | ONLINE       | PRIMARY     | 8.0.31         | XCom                       |
| group_replication_applier | 32710bb7-6161-11ed-b45b-000c29b2256f | node1       |        3306 | ONLINE       | PRIMARY     | 8.0.31         | XCom                       |
+---------------------------+--------------------------------------+-------------+-------------+--------------+-------------+----------------+----------------------------+
# 当前是多主模式,模拟节点1故障
[root@node1 ~]# systemctl stop mysqld
# 在剩余的两个节点中的任意一个查看
mysql> SELECT * FROM performance_schema.replication_group_members;
+---------------------------+--------------------------------------+-------------+-------------+--------------+-------------+----------------+----------------------------+
| CHANNEL_NAME              | MEMBER_ID                            | MEMBER_HOST | MEMBER_PORT | MEMBER_STATE | MEMBER_ROLE | MEMBER_VERSION | MEMBER_COMMUNICATION_STACK |
+---------------------------+--------------------------------------+-------------+-------------+--------------+-------------+----------------+----------------------------+
| group_replication_applier | 2dd88a45-6161-11ed-b5d2-000c299aa451 | node3       |        3306 | ONLINE       | PRIMARY     | 8.0.31         | XCom                       |
| group_replication_applier | 30c9d73b-6161-11ed-b523-000c2913fd72 | node2       |        3306 | ONLINE       | PRIMARY     | 8.0.31         | XCom                       |
+---------------------------+--------------------------------------+-------------+-------------+--------------+-------------+----------------+----------------------------+
2 rows in set (0.00 sec)
# 如上,node1节点的mysql发生故障后,会自动从这个组内踢出,剩余的两个节点的组复制同步关系正常!
4.1.2 其他节点更新数据
#node2
INSERT INTO test.t1 VALUES (8201, 'node2:node1 down');
#node3查看
mysql> SELECT * FROM test.t1;
+------+------------------+
| c1   | c2               |
+------+------------------+
|    1 | Luis             |
|    2 | Klaus            |
|   81 | node1            |
|   82 | node2            |
|   83 | node3            |
| 8201 | node2:node1 down |
+------+------------------+
4.1.3 节点1恢复
[root@node1 ~]# systemctl start mysqld
# 其他节点查看
mysql> SELECT * FROM performance_schema.replication_group_members;
+---------------------------+--------------------------------------+-------------+-------------+--------------+-------------+----------------+----------------------------+
| CHANNEL_NAME              | MEMBER_ID                            | MEMBER_HOST | MEMBER_PORT | MEMBER_STATE | MEMBER_ROLE | MEMBER_VERSION | MEMBER_COMMUNICATION_STACK |
+---------------------------+--------------------------------------+-------------+-------------+--------------+-------------+----------------+----------------------------+
| group_replication_applier | 2dd88a45-6161-11ed-b5d2-000c299aa451 | node3       |        3306 | ONLINE       | PRIMARY     | 8.0.31         | XCom                       |
| group_replication_applier | 30c9d73b-6161-11ed-b523-000c2913fd72 | node2       |        3306 | ONLINE       | PRIMARY     | 8.0.31         | XCom                       |
+---------------------------+--------------------------------------+-------------+-------------+--------------+-------------+----------------+----------------------------+
2 rows in set (0.00 sec)
# 如上结果,node1节点恢复后,不会自动添加到组内,需要手动激活下该节点的组复制功能
4.1.4 节点1手动加入组复制
[root@node1 ~]# mysql -proot
mysql>
START GROUP_REPLICATION;
SELECT * FROM performance_schema.replication_group_members;

4.2 多节点故障

要是三个节点都发生故障的话,在节点的故障都恢复后,需要手动重新做组复制 (当然这种情况不属于容错的范畴),操作流程如下:

#第一个节点
mysql> reset master;
mysql> SET SQL_LOG_BIN=1;
mysql> CHANGE MASTER TO MASTER_USER='repl', MASTER_PASSWORD='repl' FOR CHANNEL 'group_replication_recovery';
mysql> STOP GROUP_REPLICATION;
mysql> SET GLOBAL group_replication_bootstrap_group=ON;
mysql> START GROUP_REPLICATION;
mysql> SET GLOBAL group_replication_bootstrap_group=OFF;
mysql> SELECT * FROM performance_schema.replication_group_members;
  
#第二个节点
mysql> reset master;
mysql> SET SQL_LOG_BIN=1;
mysql> CHANGE MASTER TO MASTER_USER='repl', MASTER_PASSWORD='repl' FOR CHANNEL 'group_replication_recovery';
mysql> START GROUP_REPLICATION;
mysql> SELECT * FROM performance_schema.replication_group_members;
  
#第三个节点
mysql> reset master;
mysql> SET SQL_LOG_BIN=1;
mysql> CHANGE MASTER TO MASTER_USER='repl', MASTER_PASSWORD='repl' FOR CHANNEL 'group_replication_recovery';
mysql> START GROUP_REPLICATION;
mysql> SELECT * FROM performance_schema.replication_group_members;

5. 单主模式容错测试

以三成员为例,验证以下场景下对整个集群的影响:

  • 一个 SECONDARY实例 正常 shutdown。
  • 一个 SECONDARY实例 异常 shutdown。
  • PRIMARY 实例 正常 shutdown。
  • PRIMARY 实例 异常 shutdown。

因为只有三个成员,这四种场景均能够保证最大票数。无法保证最大票数时,如上面例子中三个成员中的两个异常宕机,则整个集群无法正常读写,需要管理员人为介入解决问题。这种情况显然不属于容错的范畴。

# 查看当前是单主模式
	-- PRIMARY		node1
	-- SECONDARY	node2,node3
mysql> SELECT * FROM performance_schema.replication_group_members;
+---------------------------+--------------------------------------+-------------+-------------+--------------+-------------+----------------+----------------------------+
| CHANNEL_NAME              | MEMBER_ID                            | MEMBER_HOST | MEMBER_PORT | MEMBER_STATE | MEMBER_ROLE | MEMBER_VERSION | MEMBER_COMMUNICATION_STACK |
+---------------------------+--------------------------------------+-------------+-------------+--------------+-------------+----------------+----------------------------+
| group_replication_applier | 2dd88a45-6161-11ed-b5d2-000c299aa451 | node3       |        3306 | ONLINE       | SECONDARY   | 8.0.31         | XCom                       |
| group_replication_applier | 30c9d73b-6161-11ed-b523-000c2913fd72 | node2       |        3306 | ONLINE       | SECONDARY   | 8.0.31         | XCom                       |
| group_replication_applier | 32710bb7-6161-11ed-b45b-000c29b2256f | node1       |        3306 | ONLINE       | PRIMARY     | 8.0.31         | XCom                       |
+---------------------------+--------------------------------------+-------------+-------------+--------------+-------------+----------------+----------------------------+
3 rows in set (0.00 sec)

5.1 一个从实例正常关机

#1、创建一个能长时间执行的存储过程。
drop database test;
create database test;
use test;
create table t1(a bigint auto_increment primary key);

delimiter //
create procedure p1(a int)
begin
   declare i int default 1;
   while i<=a do
      insert into t1 select null;
      set i=i+1;
end while;
end;
//
delimiter ;

#2、主库上执行长时间运行的事务
use test;
truncate table t1;
call p1(100000);

#3、在上一步执行期间停止一个从库
[root@node3 ~]# mysqladmin -proot shutdown

#4、检查剩余组复制成员状态
	-- 在node1上检查复制组成员状态:
mysql> select * from performance_schema.replication_group_members;
+---------------------------+--------------------------------------+-------------+-------------+--------------+-------------+----------------+----------------------------+
| CHANNEL_NAME              | MEMBER_ID                            | MEMBER_HOST | MEMBER_PORT | MEMBER_STATE | MEMBER_ROLE | MEMBER_VERSION | MEMBER_COMMUNICATION_STACK |
+---------------------------+--------------------------------------+-------------+-------------+--------------+-------------+----------------+----------------------------+
| group_replication_applier | 30c9d73b-6161-11ed-b523-000c2913fd72 | node2       |        3306 | ONLINE       | SECONDARY   | 8.0.31         | XCom                       |
| group_replication_applier | 32710bb7-6161-11ed-b45b-000c29b2256f | node1       |        3306 | ONLINE       | PRIMARY     | 8.0.31         | XCom                       |
+---------------------------+--------------------------------------+-------------+-------------+--------------+-------------+----------------+----------------------------+
2 rows in set (0.00 sec)
	-- 在node2检查复制事务和数据:
mysql> select * from performance_schema.replication_group_member_stats where member_id='30c9d73b-6161-11ed-b523-000c2913fd72'\G;
*************************** 1. row ***************************
                              CHANNEL_NAME: group_replication_applier
                                   VIEW_ID: 16682253322344042:4
                                 MEMBER_ID: 30c9d73b-6161-11ed-b523-000c2913fd72
               COUNT_TRANSACTIONS_IN_QUEUE: 0
                COUNT_TRANSACTIONS_CHECKED: 21046
                  COUNT_CONFLICTS_DETECTED: 0
        COUNT_TRANSACTIONS_ROWS_VALIDATING: 8801
        TRANSACTIONS_COMMITTED_ALL_MEMBERS: aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:1-12295:1000022-1000023:2000022
            LAST_CONFLICT_FREE_TRANSACTION: aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:21095
COUNT_TRANSACTIONS_REMOTE_IN_APPLIER_QUEUE: 0
         COUNT_TRANSACTIONS_REMOTE_APPLIED: 21047
         COUNT_TRANSACTIONS_LOCAL_PROPOSED: 0
         COUNT_TRANSACTIONS_LOCAL_ROLLBACK: 0
1 row in set (0.00 sec)

ERROR: 
No query specified
mysql> select min(a),max(a),count(*) from test.t1;
+--------+--------+----------+
| min(a) | max(a) | count(*) |
+--------+--------+----------+
|      1 |  28808 |    28808 |
+--------+--------+----------+
1 row in set (0.07 sec)

可以看到,一个 SECONDARY 实例正常 shutdown,对应用来说只是少了只读实例。复制组中的剩余成员状态依然是 ONLINE。主库正常读写,从库正常复制,并且没有积压的事务 (COUNT_TRANSACTIONS_IN_QUEUE 为 0)。

#5、恢复shutdown的实例
[root@node3 ~]# systemctl start mysqld
	-- 在node3上检查组复制成员状态:
[root@node3 ~]# mysql -proot
mysql> select * from performance_schema.replication_group_members;
+---------------------------+-----------+-------------+-------------+--------------+-------------+----------------+----------------------------+
| CHANNEL_NAME              | MEMBER_ID | MEMBER_HOST | MEMBER_PORT | MEMBER_STATE | MEMBER_ROLE | MEMBER_VERSION | MEMBER_COMMUNICATION_STACK |
+---------------------------+-----------+-------------+-------------+--------------+-------------+----------------+----------------------------+
| group_replication_applier |           |             |        NULL | OFFLINE      |             |                |                            |
+---------------------------+-----------+-------------+-------------+--------------+-------------+----------------+----------------------------+
1 row in set (0.01 sec)
	-- 此时node3的状态为OFFLINE,已经从复制组中被移除。在node3检查复制事务和数据:
mysql> select * from performance_schema.replication_group_member_stats where member_id='5c93a708-a393-11e9-8343-005056a5497f'\G
mysql> select min(a),max(a),count(*) from test.t1;
+--------+--------+----------+
| min(a) | max(a) | count(*) |
+--------+--------+----------+
|      1 |   1148 |     1148 |
+--------+--------+----------+
1 row in set (0.02 sec)

可以看到,此时performance_schema.replication_group_member_stats表中已经没有此成员相关的信息,实例停止时插入了 1148 条数据。

#6、将node3重新加入复制组:
change master to master_user='repl', master_password='repl' for channel 'group_replication_recovery';
start group_replication;
	-- 再次检查成员状态、复制事务和数据:
mysql> select * from performance_schema.replication_group_member_stats where member_id='2dd88a45-6161-11ed-b5d2-000c299aa451'\G
*************************** 1. row ***************************
                              CHANNEL_NAME: group_replication_applier
                                   VIEW_ID: 16682253322344042:5
                                 MEMBER_ID: 2dd88a45-6161-11ed-b5d2-000c299aa451
               COUNT_TRANSACTIONS_IN_QUEUE: 2569
                COUNT_TRANSACTIONS_CHECKED: 0
                  COUNT_CONFLICTS_DETECTED: 0
        COUNT_TRANSACTIONS_ROWS_VALIDATING: 0
        TRANSACTIONS_COMMITTED_ALL_MEMBERS: 
            LAST_CONFLICT_FREE_TRANSACTION: 
COUNT_TRANSACTIONS_REMOTE_IN_APPLIER_QUEUE: 0
         COUNT_TRANSACTIONS_REMOTE_APPLIED: 0
         COUNT_TRANSACTIONS_LOCAL_PROPOSED: 0
         COUNT_TRANSACTIONS_LOCAL_ROLLBACK: 0
1 row in set (0.02 sec)

mysql> select min(a),max(a),count(*) from test.t1;
+--------+--------+----------+
| min(a) | max(a) | count(*) |
+--------+--------+----------+
|      1 |   6991 |     6991 |
+--------+--------+----------+
1 row in set (0.03 sec)

node3 开始处于 RECOVERING 状态,表明它正在追赶组的复制进度,当赶上后,它的状态将变为 ONLINE。由此可见,一个 SECONDARY 实例正常shutdown基本对复制组没有影响 (就是少了一个读成员)。当把它重新加入组中,落后的事务自动恢复,直至赶上状态自动变为 ONLINE。

5.2 一个从实例异常关机

#同上一节操作,从机停止的时候,kill掉进程,模拟异常关机
[root@node3 ~]# 
ps -ef | grep mysqld | grep -v grep | awk {'print $2'} | xargs kill -9

操作过程 (略)。实验结果发现,一个 SECONDARY 实例的正常 shutdown 和异常 shutdown,对复制组的影响和恢复过程都没有任何区别。其实就多了一个 MySQL 实例的恢复过程,这是在重新启动 node3 时自动进行的,对用户是完全透明。

5.3 主实例正常关机

#1、主库上执行长时间运行的事务
use test;
truncate table t1;
call p1(100000);

#2、在上一步执行期间停止主库
[root@node1 ~]# mysqladmin -proot shutdown

#3、检查剩余组复制成员状态
	-- 在node2、node3上检查复制组成员状态:
mysql> select * from performance_schema.replication_group_members;
+---------------------------+--------------------------------------+-------------+-------------+--------------+-------------+----------------+----------------------------+
| CHANNEL_NAME              | MEMBER_ID                            | MEMBER_HOST | MEMBER_PORT | MEMBER_STATE | MEMBER_ROLE | MEMBER_VERSION | MEMBER_COMMUNICATION_STACK |
+---------------------------+--------------------------------------+-------------+-------------+--------------+-------------+----------------+----------------------------+
| group_replication_applier | 2dd88a45-6161-11ed-b5d2-000c299aa451 | node3       |        3306 | ONLINE       | PRIMARY     | 8.0.31         | XCom                       |
| group_replication_applier | 30c9d73b-6161-11ed-b523-000c2913fd72 | node2       |        3306 | ONLINE       | SECONDARY   | 8.0.31         | XCom                       |
+---------------------------+--------------------------------------+-------------+-------------+--------------+-------------+----------------+----------------------------+
2 rows in set (0.00 sec)
	-- 在node2、node3检查复制事务和数据:
mysql> select * from performance_schema.replication_group_member_stats\G
*************************** 1. row ***************************
                              CHANNEL_NAME: group_replication_applier
                                   VIEW_ID: 16682318930215702:4
                                 MEMBER_ID: 2dd88a45-6161-11ed-b5d2-000c299aa451
               COUNT_TRANSACTIONS_IN_QUEUE: 0
                COUNT_TRANSACTIONS_CHECKED: 763
                  COUNT_CONFLICTS_DETECTED: 0
        COUNT_TRANSACTIONS_ROWS_VALIDATING: 762
        TRANSACTIONS_COMMITTED_ALL_MEMBERS: aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:1-109020:1000022-1000023:2000022
            LAST_CONFLICT_FREE_TRANSACTION: aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:109784
COUNT_TRANSACTIONS_REMOTE_IN_APPLIER_QUEUE: 0
         COUNT_TRANSACTIONS_REMOTE_APPLIED: 765
         COUNT_TRANSACTIONS_LOCAL_PROPOSED: 0
         COUNT_TRANSACTIONS_LOCAL_ROLLBACK: 0
*************************** 2. row ***************************
                              CHANNEL_NAME: group_replication_applier
                                   VIEW_ID: 16682318930215702:4
                                 MEMBER_ID: 30c9d73b-6161-11ed-b523-000c2913fd72
               COUNT_TRANSACTIONS_IN_QUEUE: 0
                COUNT_TRANSACTIONS_CHECKED: 763
                  COUNT_CONFLICTS_DETECTED: 0
        COUNT_TRANSACTIONS_ROWS_VALIDATING: 762
        TRANSACTIONS_COMMITTED_ALL_MEMBERS: 
            LAST_CONFLICT_FREE_TRANSACTION: aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:109784
COUNT_TRANSACTIONS_REMOTE_IN_APPLIER_QUEUE: 0
         COUNT_TRANSACTIONS_REMOTE_APPLIED: 763
         COUNT_TRANSACTIONS_LOCAL_PROPOSED: 0
         COUNT_TRANSACTIONS_LOCAL_ROLLBACK: 0
2 rows in set (0.00 sec)

mysql> select min(a),max(a),count(*) from test.t1;
+--------+--------+----------+
| min(a) | max(a) | count(*) |
+--------+--------+----------+
|      1 |    762 |      762 |
+--------+--------+----------+
1 row in set (0.00 sec)

可以看到,当 PRIMARY 实例正常 shutdown,复制组中的剩余成员状态依然是 ONLINE,并且自动把其中一个 SECONDARY (本例中为node3) 提升为 PRIMARY.

#4、恢复关机的实例node1
[root@node1 ~]# systemctl start mysqld
#5、在node1上检查组复制成员状态:
mysql> mysql -proot
mysql> select * from performance_schema.replication_group_members;
+---------------------------+-----------+-------------+-------------+--------------+-------------+----------------+----------------------------+
| CHANNEL_NAME              | MEMBER_ID | MEMBER_HOST | MEMBER_PORT | MEMBER_STATE | MEMBER_ROLE | MEMBER_VERSION | MEMBER_COMMUNICATION_STACK |
+---------------------------+-----------+-------------+-------------+--------------+-------------+----------------+----------------------------+
| group_replication_applier |           |             |        NULL | OFFLINE      |             |                |                            |
+---------------------------+-----------+-------------+-------------+--------------+-------------+----------------+----------------------------+
1 row in set (0.00 sec)
	-- 此时node1的状态为OFFLINE,已经从复制组中被移除。在node1检查复制事务和数据:
mysql> select * from performance_schema.replication_group_member_stats where member_id='32710bb7-6161-11ed-b45b-000c29b2256f'\G
Empty set (0.01 sec)
mysql> select min(a),max(a),count(*) from test.t1;
+--------+--------+----------+
| min(a) | max(a) | count(*) |
+--------+--------+----------+
|      1 |    761 |      761 |
+--------+--------+----------+
1 row in set (0.00 sec)

可以看到,此时performance_schema.replication_group_member_stats表中已经没有此成员相关的信息,实例停止时插入了 761 条数据。

#6、将node1重新加入复制组
change master to master_user='repl', master_password='repl' for channel 'group_replication_recovery';
start group_replication;
	-- 再次检查成员状态、复制事务和数据:
mysql> select * from performance_schema.replication_group_members;
+---------------------------+--------------------------------------+-------------+-------------+--------------+-------------+----------------+----------------------------+
| CHANNEL_NAME              | MEMBER_ID                            | MEMBER_HOST | MEMBER_PORT | MEMBER_STATE | MEMBER_ROLE | MEMBER_VERSION | MEMBER_COMMUNICATION_STACK |
+---------------------------+--------------------------------------+-------------+-------------+--------------+-------------+----------------+----------------------------+
| group_replication_applier | 2dd88a45-6161-11ed-b5d2-000c299aa451 | node3       |        3306 | ONLINE       | PRIMARY     | 8.0.31         | XCom                       |
| group_replication_applier | 30c9d73b-6161-11ed-b523-000c2913fd72 | node2       |        3306 | ONLINE       | SECONDARY   | 8.0.31         | XCom                       |
| group_replication_applier | 32710bb7-6161-11ed-b45b-000c29b2256f | node1       |        3306 | ONLINE       | SECONDARY   | 8.0.31         | XCom                       |
+---------------------------+--------------------------------------+-------------+-------------+--------------+-------------+----------------+----------------------------+
3 rows in set (0.00 sec)
mysql> select * from performance_schema.replication_group_member_stats where member_id='32710bb7-6161-11ed-b45b-000c29b2256f'\G
*************************** 1. row ***************************
                              CHANNEL_NAME: group_replication_applier
                                   VIEW_ID: 16682318930215702:5
                                 MEMBER_ID: 32710bb7-6161-11ed-b45b-000c29b2256f
               COUNT_TRANSACTIONS_IN_QUEUE: 0
                COUNT_TRANSACTIONS_CHECKED: 0
                  COUNT_CONFLICTS_DETECTED: 0
        COUNT_TRANSACTIONS_ROWS_VALIDATING: 1
        TRANSACTIONS_COMMITTED_ALL_MEMBERS: 
            LAST_CONFLICT_FREE_TRANSACTION: 
COUNT_TRANSACTIONS_REMOTE_IN_APPLIER_QUEUE: 0
         COUNT_TRANSACTIONS_REMOTE_APPLIED: 0
         COUNT_TRANSACTIONS_LOCAL_PROPOSED: 0
         COUNT_TRANSACTIONS_LOCAL_ROLLBACK: 0
1 row in set (0.00 sec)
mysql> select min(a),max(a),count(*) from test.t1;
+--------+--------+----------+
| min(a) | max(a) | count(*) |
+--------+--------+----------+
|      1 |    762 |      762 |
+--------+--------+----------+
1 row in set (0.01 sec)
#node1处于ONLINE状态,但角色已经变成了SECONDARY。
#此时node1变为一个只读成员:
mysql> show variables like '%read_only%';
+-----------------------+-------+
| Variable_name         | Value |
+-----------------------+-------+
| innodb_read_only      | OFF   |
| read_only             | ON    |
| super_read_only       | ON    |
| transaction_read_only | OFF   |
+-----------------------+-------+
4 rows in set (0.01 sec)
#此时node3变为一个可读可写成员(主库):
mysql> show variables like '%read_only%';
+-----------------------+-------+
| Variable_name         | Value |
+-----------------------+-------+
| innodb_read_only      | OFF   |
| read_only             | OFF   |
| super_read_only       | OFF   |
| transaction_read_only | OFF   |
+-----------------------+-------+
4 rows in set (0.01 sec)

由此可见,当 PRIMARY 实例正常 shutdown,组复制会把一个 SECONDARY 提升为新的 PRIMARY。而原来的 PRIMARY 在重新加入组后立即 ONLINE,角色变为 SECONDARY。这一切都是自动进行的,应用需要做的是重新连接新的 PRIMARY 实例,以便继续执行读写事务。

5.4 主实例异常关机

实验 (略)。最后得出结论,PRIMARY 实例正常 shutdown 与异常 shutdown,对组复制和应用的影响来说没有任何区别。最终得出的结论是,只要保证最大票数的实例可用,组复制中成员的数据恢复、主从角色交换等容错行为是全自动的,应用只要在必要时修改到主库的连接即可。

6. 保证数据一致性

6.1 组复制数据一致性简介

对分布式系统的一个重要需求是它能够提供一致性保证。就分布式一致性而言,无论是在正常还是失败修复的操作中,组复制始终是始终保持最终一致性。可以将影响数据一致性的事件分为两类:一是手动或由故障自动触发的控制操作;二是数据流。

6.1.1 控制操作

与一致性相关的组复制操作包括:添加或移除组成员、网络故障保护和主库故障转移。添加或移除组成员的数据一致性保障已经在“1.8.5 分布式恢复”中描述。

6.1.1.1 网络故障写保护

多主模式中,当一个组成员离开复制组后,该成员不能继续接收更新事务,否则只能在本地提交,造成数据不一致。为了提高安全性,执行STOP GROUP_REPLICATION时,在服务器上启用超级只读模式。这将导致服务器在离开组时默认只读,无论它离开的原因是手动还是网络故障。

# 多主模式下,所有节点只读关闭
mysql> select member_id,member_host,MEMBER_STATE,member_role from performance_schema.replication_group_members;
+--------------------------------------+-------------+--------------+-------------+
| member_id                            | member_host | MEMBER_STATE | member_role |
+--------------------------------------+-------------+--------------+-------------+
| 2dd88a45-6161-11ed-b5d2-000c299aa451 | node3       | ONLINE       | PRIMARY     |
| 30c9d73b-6161-11ed-b523-000c2913fd72 | node2       | ONLINE       | PRIMARY     |
| 32710bb7-6161-11ed-b45b-000c29b2256f | node1       | ONLINE       | PRIMARY     |
+--------------------------------------+-------------+--------------+-------------+
3 rows in set (0.00 sec)

mysql> show variables like '%read_only%';
+-----------------------+-------+
| Variable_name         | Value |
+-----------------------+-------+
| innodb_read_only      | OFF   |
| read_only             | OFF   |
| super_read_only       | OFF   |
| transaction_read_only | OFF   |
+-----------------------+-------+
4 rows in set (0.00 sec)
# node2 停止组复制
mysql> stop group_replication;
Query OK, 0 rows affected (7.92 sec)
 
mysql> select member_id,member_host,MEMBER_STATE,member_role from performance_schema.replication_group_members;
+--------------------------------------+-------------+--------------+-------------+
| member_id                            | member_host | MEMBER_STATE | member_role |
+--------------------------------------+-------------+--------------+-------------+
| 30c9d73b-6161-11ed-b523-000c2913fd72 | node2       | OFFLINE      |             |
+--------------------------------------+-------------+--------------+-------------+
1 row in set (0.00 sec)
# 组成员node2离开复制组后,启用超级只读模式
mysql> show variables like '%read_only%';
+-----------------------+-------+
| Variable_name         | Value |
+-----------------------+-------+
| innodb_read_only      | OFF   |
| read_only             | ON    |
| super_read_only       | ON    |
| transaction_read_only | OFF   |
+-----------------------+-------+
4 rows in set (0.00 sec)
 
mysql> start group_replication;
Query OK, 0 rows affected (3.47 sec)
 
mysql> show variables like '%read_only%';
+-----------------------+-------+
| Variable_name         | Value |
+-----------------------+-------+
| innodb_read_only      | OFF   |
| read_only             | OFF   |
| super_read_only       | OFF   |
| transaction_read_only | OFF   |
+-----------------------+-------+
4 rows in set (0.01 sec)
 
mysql> select member_id,member_host,MEMBER_STATE,member_role from performance_schema.replication_group_members;
+--------------------------------------+-------------+--------------+-------------+
| member_id                            | member_host | MEMBER_STATE | member_role |
+--------------------------------------+-------------+--------------+-------------+
| 2dd88a45-6161-11ed-b5d2-000c299aa451 | node3       | ONLINE       | PRIMARY     |
| 30c9d73b-6161-11ed-b523-000c2913fd72 | node2       | ONLINE       | PRIMARY     |
| 32710bb7-6161-11ed-b45b-000c29b2256f | node1       | ONLINE       | PRIMARY     |
+--------------------------------------+-------------+--------------+-------------+
3 rows in set (0.00 sec)
6.1.1.2 主库故障转移

单主模式中,当主库发生故障,一个从库被提升为主库时,对于积压事务与新事务有两种可选的处理方式:

  • 1). 可以立即服务应用程序,无论复制积压的数量有多大;
  • 2). 在处理积压事务之前限制访问。

第一种方案中,系统将花费最少的时间在主库故障之后通过选择新主库来保护稳定的组成员资格,然后在应用旧主库积压的事务时立即允许数据访问。这种方式能够保证写入一致性,但可能读取到过时的数据。

使用第二种方法,系统将在主库故障后保护稳定的组成员资格,并以与第一种方案相同的方式选择新主库。但在这种情况下,该组将等待新主库应用所有积压事务,之后才允许数据访问。故障转移所需的时间与积压大小成正比,在均衡的复制组中,积压应该很小。

MySQL 8.0.14 之前采用可用性最大化策略 (第一种方法),并且不可配。MySQL 8.0.14 及更高版本中可以使用group_replication_consistency变量配置组成员在主库故障转移期间提供的事务一致性保证级别。

6.1.2 数据流

数据流影响组一致性的方式基于读取和写入。单主模式中,通常将传入的读/写事务进行拆分,写入路由到主库,读取均匀分配给从库。复制组对外应该表现为单个实体,因此可以合理地期望主库上的写入在从库上即时可用。尽管组复制是在实现 Paxos 算法的组通信系统 (GCS) 协议之上编写的,但组复制的某些部分是异步的,这意味着数据异步应用于从库,因此可能出现这样的情况,客户端 C1 在主库上写入“A = 2 WHERE A = 1”,立即连接到从库但读取到 “A = 1”。这是 MySQL 8.0.14 之前唯一可用的一致性级别。

可以选择在读取或写入时同步数据。如果在读取时进行同步,则当前客户端会话将等待一个给定点,即所有先前更新事务完成的时间点,然后才能开始执行。此方法仅影响当前会话,所有其它并发数据操作不受影响。如果在写入时进行同步,则写入会话将等待所有其它成员都写入其数据。由于组复制遵循事务的总顺序,这意味着需要等待其它成员执行队列中所有先前的写入及其本次写入。这两种可选方案都能确保前面例子中,即使立即连接到从库,客户端 C1 也将始终读取 “A = 2”。同步点的确定与系统工作负载直接相关。

  • 写时同步适用场景:
    • 组的写入比读取少的多,希望对读取进行负载均衡,又不对读取哪个服务器进行额外限制以避免读取旧数据。
    • 主要是只读数据的组,希望读写事务一旦提交就应用到所有成员,以便后续读取最新数据。
  • 读时同步适用场景:
    • 组的写入比读取多的多,希望对读取进行负载均衡,又不对读取哪个服务器进行额外限制以避免读取旧数据。
    • 希望工作负载中的特定事务始终从组中读取最新数据,以便每当更新敏感数据时强制读取最新值。

可以简单但不够严谨地理解为读多写时同步,写多读时同步,目的就是减少同步次数同时满足数据一致性要求。后面会看到读、写两个同步点以及两者的组合,对应 group_replication_consistency 系统变量的可选值,用作选择组复制一致性级别。

6.2 防止主库故障转移造成的过时读取

组复制群集自动检测故障并调整组成员视图,即成员资格配置。如果组以单主模式部署,当成员资格更改时,将执行检查以确定组中是否存在主库。如果没有,则在从库成员列表中选择一个作为新主库,这就是所谓的从库提升。用户期望一旦发生从库提升,新主库数据与旧主库数据处于完全相同的状态,在新主库上不能读取或写入旧数据。换句话说,当能够读取和写入新主库时,其上没有积压的复制事务。

从 MySQL 8.0.14 开始,从库提升后,用户可以指定新主库的行为。新增的group_replication_consistency系统参数用于控制新主库是采用之前版本的最终一致性,还是阻止读取和写入,直到完全应用积压事务。如果在具有group_replication_consistency='BEFORE_ON_PRIMARY_FAILOVER'设置的新主库上执行事务时,该新主库正在处理积压,则事务将被阻止,直到完全应用待处理的积压事务。这可确保在主库发生故障转移时,无论是自动触发还是手工触发,客户端始终会在新主库上看到最新值,因此防止了以下异常:

  • 对于只读和读写事务,没有过时读取。这可以防止新主库将过时读取外部化到应用程序。
  • 读写事务没有虚假回滚,因为与复制读写事务产生写-写冲突的事务仍处于待处理状态。
  • 读写事务没有读取偏差,例如:
-- 不会向t2插入从t1过时读取的旧数据
insert into t2 select a from t1; 

注意,该设置表明在可用性与一致性之间,用户更重视数据一致性。毕竟和最终一致性相比,此设置可能使应用程序产生等待。这意味着客户端必须能够在应用积压的情况下处理延迟。通常这种延迟应该很小,具体取决于积压的大小。当group_replication_consistency ='BEFORE_ON_PRIMARY_FAILOVER'时,并非所有读取都被阻止。例如,提升发生后,允许下面不修改数据的非阻塞查询:

  • SHOW commands
  • SET option
  • DO
  • EMPTY
  • USE
  • SELECTing from performance_schema database
  • SELECTing from table PROCESSLIST on database infoschema
  • SELECTing from sys database
  • SELECT command that don’t use tables
  • SELECT command that don’t execute user defined functions
  • STOP GROUP_REPLICATION command
  • SHUTDOWN command
  • RESET PERSIST

为了保证组不返回过时数据,组中所有成员都应该如下配置。从库提升后阻塞新事务,直到新主库应用所有积压。

set persist group_replication_consistency= 'before_on_primary_failover';

6.3 选择适当的一致性级别

从 MySQL 8.0.14 开始,新增的group_replication_consistency系统变量用于配置组复制的一致性级别,不同配置对组处理的只读 (RO) 和读写 (RW) 事务产生不同的影响。按增加事务一致性保证的顺序,该变量有 EVENTUAL、BEFORE_ON_PRIMARY_FAILOVER、BEFORE、AFTER、BEFORE_AND_AFTER 五个可选值,缺省为 EVENTUAL。从上一小节已经了解了 BEFORE_ON_PRIMARY_FAILOVER 的作用,下面介绍其它四个可选值。

6.3.1 EVENTUAL——缺省值

读、写事务都直接执行,不等待之前复制积压的事务,也不等待其它组成员应用这些事务。这是 MySQL 8.0.14 之前的组复制行为。

  • 流程图
img
  • 算法描述

    • 在组成员 M1 上开始事务 T1。
    • T1 执行到提交点,在该点事务数据已经发送到所有组成员,包括发起 T1 的成员 M1。
    • 每个成员检查 T1 是否和之前的事务存在冲突,如果是则回滚 T1,否则 T1 在 M1上提交,并且进入其它成员的事务队列,排队执行和提交。
    • 在成员M3接收到T1数据前开始事务 T2。在 M3 上,T2 将在 T1 之前执行,因此 T2 可能读取到过时数据。
  • 示例

# 多主环境
-- 新开所有会话
-- 1、node1上锁定表。
mysql> truncate table test.t1; 
mysql> lock table test.t1 read;
Query OK, 0 rows affected (0.00 sec)
-- 2、node2上执行插入成功,不会产生等待。
mysql> insert into test.t1 values (1); 
Query OK, 1 row affected (0.01 sec)
-- 3、node1上执行查询,由于表被锁定,插入事务处于等待状态,因此查询结果为insert前的数据。
-- 表解锁后,插入事务随之执行和提交,查询结果为insert后的数据。
mysql> select * from test.t1;
Empty set (0.00 sec)
mysql> unlock tables;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from test.t1;
+---+
| a |
+---+
| 1 |
+---+
1 row in set (0.00 sec)
6.3.2 BEFORE——读时同步

在开始执行之前,事务将等待所有先前的事务完成。这可确保此事务将在最新的数据快照上执行,而不管在哪个成员上执行。此一致性级别涵盖BEFORE_ON_PRIMARY_FAILOVER提供的一致性保证。

  • 流程图
img
  • 算法描述
    • 在组成员 M1 上使用 EVENTUAL 级别开始事务 T1。
    • T1 执行到提交点,在该点事务数据已经发送到所有组成员,包括发起 T1 的成员 M1。
    • 每个成员检查 T1 是否和之前的事务存在冲突,如果是则回滚T1,否则T1在M1上提交,并且进入其它成员的事务队列,排队执行和提交。
    • 在成员 M3 接收到 T1 数据前,使用 BEFORE 级别开始事务 T2。T2将向所有群成员发送消息,提供 T2 的全局顺序。
    • 当按顺序接收和处理消息时,M3 从消息流中获取组复制应用程序的 RECEIVED_TRANSACTION_SET,这是允许提交的远程事务集合。无论这些事务是否实际已经提交,它们都包含在此集合中。这个集合提供了在 T2 之前存在的远程事务。为了保证成员上的一致性读,需要跟踪远程事务。尽管提供 T2 全局顺序的消息已经发送给所有组成员,但只有 M3 需要对其进行操作,其它成员丢弃此消息而不进行任何其它操作。
    • 事务 T2 仅在提交组复制应用程序 RECEIVED_TRANSACTION_SET 中的所有事务后才开始在 M3 上执行。这确保 T2 不会读取和执行相对于其全局顺序过时的数据,这里的顺序为 T1,T2。此等待仅发生在执行具有 BEFORE 一致性事务的服务器上,本例中是 M3,所有其它成员不受此等待的影响。
  • 示例
-- 新开所有会话
-- 1、node1会话1上锁定表。
mysql>
truncate table test.t1; 
lock table test.t1 read;
-- 2、node2上执行插入成功,不会产生等待。
[root@node2 ~]# mysql -proot
mysql> insert into test.t1 values (1); 
Query OK, 1 row affected (0.01 sec)
-- 3、node1上新开会话2中在BEFORE级别下执行查询,由于表被锁定,插入事务处于等待状态,因此查询等待。
[root@node1 ~]# mysql -proot
mysql> set @@session.group_replication_consistency='BEFORE' ;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from test.t1;

-- 4、node1会话1中解锁表。
mysql> unlock tables;
Query OK, 0 rows affected (0.00 sec)
-- 5、node1上新开的会话2立即返回查询结果

表解锁后,插入事务随之执行和提交,等待的查询得以执行,结果为 insert 后的数据。因为单从输出结果中无法区分是立即返回结果,还是先挂起一段时间后再返回的结果,所以这里没贴出查询结果。

6.3.3 AFTER——写时同步

读写事务将等待其更改已应用于其它成员,对只读事务没有影响。此模式确保在本地成员上提交事务时,后续事务会读取最新值,而无论在哪个成员上执行。将此模式与主要只读操作的组一起使用,保证应用的读写事务在提交后随处可用。通过仅在读写事务上使用同步,减少了只读事务上的同步开销。此一致性级别涵盖 BEFORE_ON_PRIMARY_FAILOVER 提供的一致性保证。

  • 流程图
img
  • 算法描述
    • 在组成员 M1 上使用 AFTER 级别开始事务 T1。
    • T1 执行到提交点,在该点事务数据已经发送到所有组成员,包括发起 T1 的成员 M1。
    • 每个成员检查 T1 是否和之前的事务存在冲突,如果是则回滚 T1,否则执行第 4 步。
    • T1 在其它成员上排队执行。一旦事务进入准备阶段,即数据在等待提交指令的存储引擎上最终确定时,它将向所有成员发送 ACK 确认。
    • 一旦所有成员收到来自所有成员的确认,它们都提交事务。
    • 在成员 M3 上的 T1 事务处于准备和提交之间时,使用 EVENTUAL 级别开始事务 T2。此时T1还没有提交,所以 T2 将等待 T1 提交完成后再开始执行。这确保 T1 后的事务都能读取到T1的最新数据。
  • 示例
-- 新开所有会话
-- 1、node1上会话1中锁定表。
mysql>
truncate table test.t1; 
lock table test.t1 read;
-- 2、node2上在AFTER级别执行插入。因为表被锁定,插入事务处于等待状态。
mysql> set @@session.group_replication_consistency='AFTER' ;
mysql> insert into test.t1 values (1); 

-- 3、node1上会话2中执行查询。由于插入事务处于等待状态,因此查询等待。
[root@node1 ~]# mysql -proot
mysql> select * from test.t1;

-- 4、node1上,会话1中解锁表。
mysql> unlock tables;

表解锁后,node2 插入事务随之执行和提交,其后等待的 node1 查询得以执行,结果为 insert 后的数据。基于和前面 BEFORE 示例中相同的原因。

6.3.4 BEFORE_AND_AFTER——读写时都同步
  • 此事务开始执行时等待:

    • 1).所有该事务先前的事务都执行完成;
    • 2).该事务的变更已应用于其它成员。这可确保:
      • 2-1).此事务将在最新的数据快照上执行;
      • 2-2).一旦此事务完成,所有后续事务都会读取到包含其更改的数据库状态,无论它们在哪个成员上执行。

    此一致性级别涵盖 BEFORE_ON_PRIMARY_FAILOVER 提供的一致性保证。

  • 流程图

img

BEFORE_AND_AFTER 一致性级别的算法流程是将 BEFORE 和 AFTER 组合在一起。读写事务等待之前所有的事务完成,并且等待其在所有节点上的变更结束。只读事务需要等待之前所有的事务完成。

  • 示例
-- 新开所有会话
-- 1、node1上,会话1中锁定表,并执行更新。
mysql>
truncate table test.t1; 
lock table test.t1 write;
insert into test.t1 values (1); 
-- 2、node2上,会话1中锁定表,并执行更新。
mysql>
lock table test.t1 write;
insert into test.t1 values (2); 
-- 3、node1上,会话2中在BEFORE_AND_AFTER级别执行查询。因为同一服务器上的更新被锁定,只读查询需要等待。
mysql> set @@session.group_replication_consistency='BEFORE_AND_AFTER' ;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from test.t1;

-- 4、node1上,会话1中解锁表
mysql> unlock tables;
Query OK, 0 rows affected (0.00 sec)
-- 5、node1上,会话2中的查询返回最新结果,距查询开始已经等待了 2 min 9.09秒。
mysql> select * from test.t1;
+---+
| a |
+---+
| 1 |
| 2 |
+---+
2 rows in set (2 min 9.09 sec)
-- 6、node1上,会话2中执行更新。因为node2上的更新被锁定,读写事务需要等待。
mysql> insert into test.t1 values (3);

-- 7、node2上,会话1中解锁表。
mysql> unlock tables;
Query OK, 0 rows affected (0.00 sec)

表解锁后,node1 上会话 2 中执行的插入事务随之执行和提交。

BEFORE 可以用于读取和写入事务,AFTER只用于写事务。不同可选值提供了灵活性的一致性级别设置。

  • 场景 1:读多写少,不读取过期数据的情况下对读取进行负载平衡。选择 AFTER。
  • 场景 2:写多读少,不读取过期数据。选择 BEFORE。
  • 场景 3:希望工作负载中的特定事务始终从组中读取最新数据。选择 BEFORE。
  • 场景 4:复制组主要为只读,希望读写事务一旦提交就应用于任何地方,以便后续读取最新数据,并且不为只读事务产生同步开销。选择 AFTER。
  • 场景 5:复制组主要为只读,希望读写事务始终从组中读取最新数据,并在提交后随处应用,以便后续读取最新数据,并且不为只读事务产生同步开销。选择 BEFORE_AND_AFTER。

6.4 一致性级别范围

选择强制执行一致性级别的范围非常重要,如果将它们设置在全局范围内,一致性级别可能会对性能产生负面影响。可以在全局或会话级别设置group_replication_consistency系统变量:

-- 强制执行当前会话的一致性级别
set @@session.group_replication_consistency = 'before';
-- 强制执行所有会话的一致性级别
set @@global.group_replication_consistency = 'before';

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

  • 场景 6:只有一种更新,如设置对文档的访问权限,希望更改访问权限后,确保所有客户端都能看到正确的权限。其它更新没有强一致性要求。只需要在该事务上执行set @@session.group_replication_consistency = 'after',其它事务使用缺省的 EVENTUAL 级别。

  • 场景 7:在场景 6 中描述的同一系统上,每天需要读取最新数据进行一些分析处理。只需要在该特定事务上执行

    set @@ session.group_replication_consistency = 'before';
    

作为原则,如果只有一些特性事务需要强一致性,就在会话级别设置 group_replication_consistency。需要强调的一点是,所有事务在组复制中是完全排序的,因此即使发出以下命令仅设置当前会话的一致性级别为 ‘AFTER’:

set @@session.group_replication_consistency = 'after';

此事务也将等待其更改应用于所有组成员,即等待执行队列上的此事务和所有先前事务。在实践中,AFTER 一致性级别将等待所有先前事务和当前事务在其它成员执行完。只能在状态为 ONLINE 的组成员上设置 BEFORE、AFTER 和 BEFORE_AND_AFTER 的一致性级别,尝试在其它状态的成员上使用它们会导致会话错误。一致性级别不是 EVENTUAL 的事务等待的最长时间由 wait_timeout 系统变量指定,缺省为 8 小时。如果超时,则抛出ER_GR_HOLD_WAIT_TIMEOUT错误。

6.5 一致性级别的影响

可以根据对复制组其它成员的影响,对一致性级别进行分类。除了在事务流上排序之外,BEFORE 一致性级别仅影响本地成员。也就是说,它不需要与其它成员协调,也不会对其它事务产生影响,或者说 BEFORE 仅影响使用它的事务。AFTER 和 BEFORE_AND_AFTER 一致性级别对在其它成员上执行的并发事务具有副作用。这两个一致性级别可以使其它成员事务陷入等待。假设一个事务 T2 在具有 EVENTUAL 级别的成员 M2 上启动时,成员 M1 正在 AFTER 或 BEFORE_AND_AFTER 级别下执行一个事务T1,则 T2 必须等待T1在M2上提交后才能开始执行。对于其它成员也是如此,即使它们具有 EVENTUAL 一致性级别。就是说设置 AFTER 和 BEFORE_AND_AFTER 会影响所有 ONLINE 成员。

为了进一步说明这一点,在具有 node1、node2、node3 三个成员的组试验验证。三个成员初始的group_replication_consistency均为缺省值 EVENTUAL。

-- 数据准备
-- 新开所有会话
-- 所有节点group_replication_consistency缺省值为EVENTUAL
show variables like 'group_replication_consistency';
-- 开启一个事务,插入大量数据
drop database test;
create database test;
use test;
create table t1(a bigint auto_increment primary key);

delimiter //
create procedure p1(a int)
begin
   declare i int default 1;
   while i<=a do
      insert into t1 select null;
      set i=i+1;
end while;
end;
//
delimiter ;

use test;
truncate table t1;
call p1(2000000);
-- 等等执行结束,查看count=2000000
mysql> select min(a),max(a),count(*) from test.t1;
+--------+--------+----------+
| min(a) | max(a) | count(*) |
+--------+--------+----------+
|      2 | 699997 |  2000000 |
+--------+--------+----------+
1 row in set (0.00 sec)
#1、在成员node1上执行一个需要长时间提交的事务T1,这里删除一个具有100000行数据的大表。
set @@session.group_replication_consistency = 'after';
begin; 
delete from test.t1;
commit;

#2、上面SQL最后的commit命令执行过程中,在node2上开启一个事务T2。
select count(*) from test.t1 for update;
-- 下面是两次执行T2的输出:
mysql> select count(*) from num for update;
+----------+
| count(*) |
+----------+
|  2000000 |
+----------+
1 row in set (1.66 sec)

mysql> select count(*) from test.t1 for update;
+----------+
| count(*) |
+----------+
|        0 |
+----------+
1 row in set (30.00 sec)

第一次执行时,T1 还没有传递到 M2 时,T2 可以正常查询出删除前的表记录数。第二次执行时,T2 开始等待,因为此时 T1 已经在 M2 的事务队列中排队,并且排在 T2 之前。即使 T2 的一致性级别为 EVENTUAL,它也必须等待 T1 先完成提交。当等待了 30 秒后,T1 提交完毕,T2 开始执行,此时返回最新的表记录数 0。

7. Troubleshooting

# 如果状态是RECOVERING,说明没有同步
# 可能的原因:
	1、检查/etc/hosts
	2、检查/etc/my.cnf
	3、账号密码
# 发现处于 ERECOVERING
mysql> SELECT * FROM performance_schema.replication_group_members;
+---------------------------+--------------------------------------+-------------+-------------+--------------+-------------+----------------+----------------------------+
| CHANNEL_NAME              | MEMBER_ID                            | MEMBER_HOST | MEMBER_PORT | MEMBER_STATE | MEMBER_ROLE | MEMBER_VERSION | MEMBER_COMMUNICATION_STACK |
+---------------------------+--------------------------------------+-------------+-------------+--------------+-------------+----------------+----------------------------+
| group_replication_applier | 30c9d73b-6161-11ed-b523-000c2913fd72 | node2       |        3306 | RECOVERING   | SECONDARY   | 8.0.31         | XCom                       |
| group_replication_applier | 32710bb7-6161-11ed-b45b-000c29b2256f | node1       |        3306 | ONLINE       | PRIMARY     | 8.0.31         | XCom                       |
+---------------------------+--------------------------------------+-------------+-------------+--------------+-------------+----------------+----------------------------+
2 rows in set (0.00 sec)
# 查看报错日志,显示账号无法连接
# 原因是mysql8.0 密码安全认证不兼容
[root@node1 ~]# cat /var/log/mysqld.log
2022-11-11T05:40:20.660528Z 25 [ERROR] [MY-010584] [Repl] Slave I/O for channel 'group_replication_recovery': error connecting to master 'repl@node1:3306' - retry-time: 60 retries: 1 message: Authentication plugin 'caching_sha2_password' reported error: Authentication requires secure connection. Error_code: MY-002061
# 如下解决,主实例执行
SET SQL_LOG_BIN=0;
alter user repl@'%' identified with mysql_native_password by 'repl';
FLUSH PRIVILEGES;
SET SQL_LOG_BIN=1;
# 或者将该参数添加到配置文件中后,重启服务
default_authentication_plugin=mysql_native_password
# 其他实例,重启组复制
mysql>
STOP GROUP_REPLICATION;
START GROUP_REPLICATION;
mysql> SELECT * FROM performance_schema.replication_group_members;
+---------------------------+--------------------------------------+-------------+-------------+--------------+-------------+----------------+----------------------------+
| CHANNEL_NAME              | MEMBER_ID                            | MEMBER_HOST | MEMBER_PORT | MEMBER_STATE | MEMBER_ROLE | MEMBER_VERSION | MEMBER_COMMUNICATION_STACK |
+---------------------------+--------------------------------------+-------------+-------------+--------------+-------------+----------------+----------------------------+
| group_replication_applier | 2dd88a45-6161-11ed-b5d2-000c299aa451 | node3       |        3306 | ONLINE       | SECONDARY   | 8.0.31         | XCom                       |
| group_replication_applier | 30c9d73b-6161-11ed-b523-000c2913fd72 | node2       |        3306 | ONLINE       | SECONDARY   | 8.0.31         | XCom                       |
| group_replication_applier | 32710bb7-6161-11ed-b45b-000c29b2256f | node1       |        3306 | ONLINE       | PRIMARY     | 8.0.31         | XCom                       |
+---------------------------+--------------------------------------+-------------+-------------+--------------+-------------+----------------+----------------------------+
3 rows in set (0.00 sec)

8. 组复制的开关机

#1、单主模式
# 关
    # 先关闭从节点
    systemctl stop mysqld
    # 再关闭主节点
    systemctl stop mysqld
# 开
    # 先开主节点
    systemctl start mysqld
    mysql> 
    set global group_replication_bootstrap_group=on;
    start group_replication;
    set global group_replication_bootstrap_group=off;
    # 再开从节点
    systemctl start mysqld
    mysql> 
    start group_replication;
#2、多主模式(和单主模式相同)
# 关 (所有节点)
systemctl stop mysqld
# 开
    # 任意一个节点
    systemctl start mysqld
    mysql> 
    set global group_replication_bootstrap_group=on;
    start group_replication;
    set global group_replication_bootstrap_group=off;
    # 其他节点
    systemctl start mysqld
    mysql> 
    start group_replication;
select member_id,member_host,MEMBER_STATE,member_role from performance_schema.replication_group_members;

欢迎关注公众号:一介IT
Alt

  • 8
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一介IT

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值