1. 组复制插件架构图

MySQL组复制是一个MySQL插件 , 它基于常规的MySQL复制 , 利用了基于行格式的二进制日志和GTID等特性。下图是MySQL组复制的整体框架图。

733013-20180624092118200-692504893

以下是对该图中各组件的大致介绍 , 涉及到的术语先浏览一遍 , 后面会详细解释。

  • 从上图的最顶端开始 , 有一系列的API控制组复制插件如何和MySQL Server进行交互(图中灰色方框)。

  • 中间有一些接口可以使信息从MySQL Server流向组复制插件 , 反之亦然。这些接口将MySQL Server核心部分和插件隔离开来。在Server到插件的方向上 , 传递一些通知信息 , 例如server正在启动 , server正在恢复 , server已准备好接收连接 , server将要提交事务等等。另一方向 , 即插件到server的方向上 , 插件会通知server对事务进行提交 , 终止正在进行的事务 , 将事务放进relay-log中排队等等。

  • API往下是一些响应组件 , 当通知信息路由到它们身上时就响应。capture组件负责跟踪正在执行的事务的上下文信息。applier组件负责在本节点上执行远程传输来的事务。recovery组件负责管理分布式恢复过程 , 还负责在节点加入组时选择donor , 编排追赶过程以及对选择donor失败时的反应。

  • 继续向下 , replication协议模块包含了特定的复制协议逻辑。它负责探测冲突 , 在组中接收和传播事务。

  • 最后两层是组内通信系统(GCS)的API(第一个绿色方框) , 以及一个基于Paxos组通信引擎的实现(implementation)(第二个绿色方框)。

    • GCS API是组内通信API , 它是一个上层API , 它抽象了构建一个复制状态机所需的属性 , 它从插件的更上层解耦了消息传递层。

    • 组通信引擎负责处理组内成员的通信。

2. 单主模型和多主模型

MySQL组复制是MySQL 5.7.17开始引入的新功能 , 为主从复制实现高可用功能。它支持单主模型和多主模型两种工作方式(默认是单主模型)。

  • 单主模型 : 从复制组中众多个MySQL节点中自动选举一个master节点 , 只有master节点可以写 , 其他节点自动设置为read only。当master节点故障时 , 会自动选举一个新的master节点 , 选举成功后 , 它将设置为可写 , 其他slave将指向这个新的master

  • 多主模型 : 复制组中的任何一个节点都可以写 , 因此没有masterslave的概念 , 只要突然故障的节点数量不太多 , 这个多主模型就能继续可用。

3. 如何创建组

当第一个节点启动组复制功能之前(不是启动mysql实例 , 而是启动组复制插件的功能) , 一般会将这个节点设置为组的引导节点 , 这不是必须的 , 但除非特殊调试环境 , 没人会吃撑了用第二、第三个节点去引导组。

所谓引导组 , 就是创建组。组创建之后 , 其他节点才能加入到组中。

将某节点设置为引导节点的方式是在该节点上设置以下变量为ON :

set @@global.group_replication_bootstrap_group=on;

开启引导功能后 , 一般会立即开启该节点的组复制功能来创建组 , 然后立即关闭组引导功能。所以 , 在第一个节点上 , 这3个语句常放在一起执行 :

set @@global.group_replication_bootstrap_group=on;
start group_replication;
set @@global.group_replication_bootstrap_group=off;

当第一个节点成功加入到组中后 , 这个节点会被标记为ONLINE , 只有标记为ONLINE的节点 , 才是组中有效节点 , 可以向外提供服务、可以进行组内通信和投票。

如果配置的是单主模型(single-primary mode)的组复制 , 第一个加入组的节点会自动选为primary节点。如果配置为多主模型(multi-primary mode)的组复制 , 则没有master节点的概念。

4. 新节点如何加入组

新节点要加组 , 在配置好配置文件后 , 只需执行以下3个过程等待成功返回就可以了。

change master to 
        master_user='XXXX',
        master_password='YYYY'
        for channle 'group_replication_recovery';
​
install plugin group_replication soname 'group_replication.so';
start group_replication;

虽然操作很少 , 但这里涉及到一个很重要的过程 : 恢复过程。

如果一个新的节点要加入组 , 它首先要将自己的数据和组内的数据保持同步 , 同步的过程实际上是从组中获取某个节点的Binlog , 然后应用到自己身上 , 填补自己缺失的那部分数据 , 这是通过异步复制完成的。而新节点和组中数据保持同步的过程称为恢复过程(或者称为分布式恢复过程) , 它由组复制插件架构图中的"Recovery"组件来完成。

当新节点启动了它自己的组复制功能时 , 它将根据自己配置文件中 group_replication_group_seeds 选项的值来选一个节点作为同步时的数据供应节点 , 这个节点称为"donor"(中文意思就是"供应者")。例如 :

loose-group_replication_group_seeds="192.168.100.21:20001,192.168.100.22:20002"

上面的配置中 , 两个节点都可以成为donor , 它们称为"种子节点(seed)"。新节点从前向后逐一选择 , 当和第一个donor交互失败 , 就会选择第二个donor , 直到选完最后一个donor , 如果还失败 , 将超时等待一段时间后再从头开始选择。建议每个节点的配置文件中 , 都将组中所有节点写入种子节点列表中。

当选择好一个donor后 , 新节点会和这个donor建立一个数据恢复的通道group_replication_recoveryRecovery组件会从donor上复制所有新节点缺失的数据对应的binlog , 然后应用在自身。当新节点数据和组中已经保持一致 , 则新节点成功加入到组 , 它将标记为ONLINE

实际上 , 这只是恢复的一个阶段 , 还有第二阶段 , 有了上面的基础 , 下面的解释就容易理解了。

在新节点准备开始加入组的时候 , Recovery组件有两个功能 : (1).选择donor , 并从donor处复制缺失的数据;(2).监控组中新产生的事务并放进自己的事务缓存队列中。

例如 , 在填补缺失的数据时 , 客户端向组中的可写节点插入了一条数据 , 这个数据不会被新节点的异步复制抓走 , 新节点会监控到这个事务。如下图 :

733013-20180624104122235-2044988706

所以将恢复过程概括一下 : 新节点的recovery组件通过异步复制通道从donor处获取p1之前的所有数据 , 并监控组中新生成的事务放入自己的事务缓存队列中。当新节点将p1之前的数据恢复完成后 , 将执行缓存队列中的事务 , 直到缓存中的事务数量减为0后 , 才表示新节点已经完全赶上了组中的数据 , 这是它才可以标识为ONLINE , 并真正成为组中的一员。

这里有个问题需要考虑 : 事务数量相同 , 为什么新节点能赶上组 ? 第一个原因是事务不会源源不断地生成 , 它总有停顿的时候;第二个原因归功于基于行格式的binlog(row-based binlog) , 除了发起事务的那个节点 , 只要事务被大多数节点同意了 , 所有节点都根据binlog行中设置的值直接修改数据 , 而不会完整地执行事务逻辑。换句话说 , 事务发起节点修改数据的速度没有其他节点快 , 尽管它们是同时提交的。

5. 如何执行单个事务?

假设组中已经有5个节点(s1s2s3s4s5)了 , 这些节点目前全都是ONLINE状态 , 这个状态表示能正确向外提供服务、能正确进行组内通信、能正确投票。假设s1是单主模型的主节点。

当在节点s1上执行了以下事务A1 :

start transaction;
insert into t values(3);
commit;

s1称为事务的发起节点。s1首先会执行这个事务到commit , 在真正commit之前有一个before_commit的过程 , 这个过程会将binlog传播给组内其它节点。当组内其它节点的receiver线程(其实就是io_thread , 但组复制中不这样称呼)收到binlog后 , 将对这个事务进行决策 , 如果组内大多数节点(因为组内有5个节点 , 所以至少3个节点才满足大多数的要求)对这个事务达成一致 , 则这个事务会真正提交(决定后、提交前会先将buffer binlog写到disk binlog) , s2-s5会将收到的binlog也写入到自己的disk binlog中 , 并通过applier线程(sql_thread)对这个事务进行应用。如果大多数节点未达成一致 , 则这个事务会回滚 , 日志不会写入到日志中。

需要注意的是 , 决策通过后 , 事务发起节点的commit操作和其它节点对binlog的应用没有强烈的先后关系 , 各节点收到"决策通过"的消息后 , 是独立完成事务的。所以 , 有些节点可能会有延迟。当延迟在允许的范围内 , 不会有什么问题 , 但如果某个节点严重落后于其他节点(拖后腿) , 组复制会放慢整个组的处理速度 , 也可能会做一些限流 , 关于这个问题的处理 , 叫做flow control

以下为binlog中两个事务的对应的日志内容 , 每个事务的binlog是在成功决定提交之后才写入的 :

+------------+------------------------------------------------------------------------+
| Gtid       | SET @@SESSION.GTID_NEXT= 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:4000007'|
| Query      | BEGIN                                                                  |
| Table_map  | table_id: 111 (mut_gr.t1)                                              |
| Write_rows | table_id: 111 flags: STMT_END_F                                        |
| Xid        | COMMIT /* xid=152 */                                                   |
| Gtid       | SET @@SESSION.GTID_NEXT= 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:3000010'|
| Query      | BEGIN                                                                  |
| Table_map  | table_id: 111 (mut_gr.t1)                                              |
| Write_rows | table_id: 111 flags: STMT_END_F                                        |
| Xid        | COMMIT /* xid=153 */                                                   |
+------------+------------------------------------------------------------------------+
​

问题是 , 组内所有节点数据和状态都是同步的 , 为什么还要对事务进行决策 ? 在主节点上判断事务能否执行、能否提交不就可以了吗 ? 对于单主模型下执行正常的事务来说确实如此。但是多主模型下多个节点可执行并发事务 , 而且组复制内部也有些特殊的事件(例如成员加组、离组) , 这些都需要得到大多数节点的同意才能通过 , 所以必须先进行决策。后文会逐一解释这些情况。

6. 如何执行并发事务: 冲突检测 ?

多个事务分两种 : 线性的事务和并发的事务。线性的多个事务和执行单个事务没有什么区别 , 无非是一个一个事务从上到下执行下去。并发事务就要多方面考虑。

如果有多个客户端同时发起了事务t1t2t3t4 , 这4个事务会因为GTID特性而具有全局唯一性并有序。例如 :

'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:146'      # 第1个节点的gtid序列
'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:1000033'  # 第2个节点的gtid序列
'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:2000010'  # 第3个节点的gtid序列
'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:3000009'  # 第4个节点的gtid序列
'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:4000007'  # 第5个节点的gtid序列
​

只要同一个节点执行的事务 , 它的前缀是一样的 , 只是后缀会不断增长。例如 , 3s1节点的事务 , 它们gtid的后缀可能会是1461471483s2节点的事务 , 它们的gtid后缀可能会是100003310000341000035

注意:

  1. 每个事务除了gtid , 还有一个分布式的xid。这个id也必须全局唯一 , 但同一个事务在不同节点上的体现值不一样。例如 , 在s1节点上 , 事务的xid=200 , 应用到s2节点上 , 这个事务的xid会改为适应自己节点上的id , 可能值为xid=222

  2. 另外 , 这个xid并不能保证顺序。例如s1上执行开启事务t1执行DML , 但不提交 , 然后在s2节点上开启事务t2执行DML并提交 , 最后提交s1上的事务 , 那么在s1上 , t1xid虽然值更小 , 但在binlog中却排在t2后面。

回到正题 , 并发事务t1t2t3t4如何执行 :

  1. 如果是单主模型 , 那么这4个事务都会路由到主节点上 , 如果这4个事务修改的数据互不冲突、互不影响 , 那么它们都会成功应用到所有节点上。如果这4个事务之间有冲突 , 那么先提交的事务将获胜(事实上 , 这时的冲突事务会被阻塞)。

  2. 如果是多主模型 , 如果这4个事务路由到同一个节点上执行 , 这和单主模型的情况一样。如果路由到不同节点 , 且并发执行 , 如果无冲突 , 则一切都OK , 如果并发事务由冲突 , 那么先提交的事务获胜。此时 , 后提交的冲突事务 , 实际上是在修改过期的数据。

问题是如何进行事务的冲突检测 ? 使用组复制有一大堆的限制 : 必须使用InnoDB引擎、必须开启gtid模式、表中必须有主键等等。如果创建了MyISAM表 , 或者创建没有主键的表 , 没关系 , 它会复制走 , 因为它们是DDL语句 , MySQL中没有DDL事务 , 但是不能再向这样的表中插入数据 , 这是强制的 , 如果插入数据将报错。

ERROR 3098 (HY000): The table does not comply with the requirements by an external plugin.

其实这3个要求都是为了冲突检测做准备:

  • 使用InnoDB是为了保证所有数据写入都是事务性的 , 只有事务型的操作才能回滚 , 例如并发事务中检测到了冲突时需要回滚。

  • 必须开启GTID模型是为了保证让事务具有唯一的事务ID , 在组内传播出去后不会重复执行。GTID对组复制的影响是方方面面的 , 有了GTID , 无论新节点还是曾经退出的旧节点 , 当它们再次加入到组中时都能保证不会与组中现有数据冲突。

  • 表中必须有主键是为了冲突检测。这个下面做详细解释。

当在某节点发起事务后 , 这个节点的replication协议模块(该模块的位置见文章开头的架构图)会收集该事务将要修改行的写集(write-set,术语) , 写集是根据每行的主键进行hash计算的 , 是否记得在组复制的配置文件中有一行 :

transaction_write_set_extraction=XXHASH64

这就是指定以"XXHASH64"算法将主键hash为写集。然后该协议模块将binlog和写集一起传播出去 , 并被其它节点的replication协议模块收到。当其它节点收到后 , 会对写集进行验证 , 这个过程称为certify , 它由certifier线程完成。如果验证通过 , 则为此事务投上自己的一票 , 多数节点通过后交给applier组件写入数据。如果验证不通过 , 意味着出现了事务冲突 , 这时将直接通告全组并回滚。注意 , 只要检测到了冲突 , 所有节点都会回滚 , 组复制不会为冲突事件进行投票决策。这里所投的票是非冲突方面的决策。

如果多个节点上出现了并发事务 , 因为写集是根据表中每行的主键值来计算的 , 所以不同事务如果修改同一行数据 , 它的写集会相等 , 这表示并发事务出现了冲突。这时会将消息报告出去 , 并进行投票 , 投票的结果是先提交者获胜 , 失败的事务将回滚。

如果两个事务经常出现冲突 , 建议将这两个事务路由到同一节点上执行。

关于事务冲突问题 , 主要是多主模型下的问题 , 单主模型下只有单个主节点可写 , 不会出现事务冲突问题。单主模型下 , 如果两个事务修改的是同一行 , 第二个事务将会因为独占锁冲突而被阻塞。

7. 小心DDL语句

MySQL中 , 没有DDL事务 , 所有的DDL语句都无法保证原子性 , 无法回滚。但DDL语句毕竟会对表中数据产生影响 , 它是一个事件 , 必须被复制走。

先看一下DDL语句和DML语句在binlog中的记录格式的区别 : (show binlog events in 'BINLOG_FILE'; , 为了排版 , 删掉了几个字段)

+-----------+-------------------------------------------------------------------+
| Gtid      | SET @@SESSION.GTID_NEXT= 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:2' |
| Query     | create database gr_test                                           |
| Gtid      | SET @@SESSION.GTID_NEXT= 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:3' |
| Query     | use `gr_test`; create table t4(id int primary key)                |
| Gtid      | SET @@SESSION.GTID_NEXT= 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:4' |
| Query     | BEGIN                                                             |
| Table_map | table_id: 117 (gr_test.t4)                                        |
| Write_rows| table_id: 117 flags: STMT_END_F                                   |
| Xid       | COMMIT /* xid=63 */                                               |
+-----------+-------------------------------------------------------------------+

上面的结果中 , 前4行是2DDL语句代表的事件 , 后5行是一个DML语句代表的事件 , 它是一个分布式事务。不难看出 , DDL语句从头到尾就只记录了它的GTID和语句部分。

换句话说 , DDL语句不会进行冲突检测。这会出现两个可怕的陷阱 :

  1. 陷阱一 :

    如果多个节点并发执行多个DDL语句 , 后执行的DDL如果能正确执行 , 其实是在对前面的DDL进行覆盖。如果不能正确执行 , 将报错 , 例如第一个DDL语句的作用是删除一个字段 , 第二个DDL语句是修改这个字段的数据类型 , 这时第二个DDL语句将失去操作目标而报错。

  2. 陷阱二 :

    如果多个节点并发执行DDLDML语句 , 将可能出错。例如第一个DDL语句是truncate语句 , 它将删除表中所有数据 , 第二个DML语句则是update语句 , 显然第二个DML语句在truncate后已经失去了更新目标。

所以 , 不要执行并发的DDL+DDL语句 , 也不要执行并发的DDL+DML语句。

8. binlog中的特殊事件 : 视图更新

binlog中 , 除了DDL语句、DCL语句(grant,revoke)语句、DML语句生成的事件 , 还有一种因组复制而存在的特殊事件 : 视图更新事件(view change)。

这个视图是什么视图 ? 在组复制插件中 , 有一个内置的服务称为"成员管理服务"(group membership service)。这个服务负责维护组内的成员关系 , 并向外展示当前组内成员列表。这个成员关系 , 或者当前成员列表就是所谓的成员视图。

成员管理服务动态维护成员成员视图 , 每当有成员加组、离组时 , 都会自动触发成员视图更新的行为 , 这意味着让成员管理服务去重新配置当前的组成员。例如 , 一个新成员加组成功时 , 这个组的大小加1 , 离组时组大小减1。

加组、离组的过程稍许复杂 , 在解释它们之前 , 先解释下视图id和视图更新事件。

在成员加组、离组时会触发视图更改 , 它不是DDLDCLDML , 但却也会当作一个事件写入binlog , 这样才能在组内传播 , 让其它节点对视图更改进行投票。这个事件称为"视图更改事件"。

例如 , 以下是binlog中某次视图更改对应的事件。

View_change   | view_id=15294216022242634:2

在上面的示例中 , 有一个view_id , 它是视图标识符 , 用来惟一地标识一个视图。它的格式由两部分组成 : 第一部分是创建组时随机生成的 , 在组停止(关闭)之前一直保持不变;第二部分是一个单调递增的整数 , 每次视图发生更改都递增一次。例如 , 以下是4个节点加入组时的binlog事件。

View_change   | view_id=15294216022242634:1     # 第一个节点创建组
View_change   | view_id=15294216022242634:2     # 第二个节点加入组
View_change   | view_id=15294216022242634:3     # 第三个节点加入组
View_change   | view_id=15294216022242634:4     # 第四个节点加入组

需要注意的是 , 加组失败时也会记录到binlog中 , 视图更改事件就像DDL/DCL事件一样 , 是非事务型的 , 不具有原子性和回滚性 , 只要发生了就会记录到binlog中 , 即使加组失败或者离组失败。

使用这种混合视图id的原因是 , 可以明确地标记当成员加入或离开时发生的组成员配置更改 , 也能标记所有成员离开组后没有任何信息保留在视图中。实际上 , 单纯使用单调递增的整数作为标识符会导致在组重启后重用视图ID , 但显然这会破坏恢复过程所依赖的二进制日志标记的唯一性。总而言之 , 第一部分标识这个组是从什么时候启动的 , 第二部分标识组在什么时间点发生了更改。

在组通信层(见本文开头的架构图) , 视图更改以及它们关联的视图id是加组之前和之后的区分边界。通过视图id , 可以知道某个事务是视图阶段的。例如 , 一个新节点加入到组 , 对应的view_idid1 , 那么id1之前的事务需要全部复制到新节点上 , id1之后的事务是加组之后的事务 , 不需要复制给新节点(前文已经解释过 , 在赶上组中数据之前 , 这部分事务会放进新节点的事务缓存队列)。

其实 , binlog中的view_change事件还充当另一个角色 : 组内其余节点感知新节点已经成功加组或成功离组。例如新成员加组的情况 , 当view_id写入binlog , 表示这个新节点已经标记为ONLINE , 其它节点知道它已经在线了 , 信息广播的时候也会广播给这个新节点 , 这个新节点也会占有一个法定票数。

9. 成员加组, 离组的细节

新成员加组的情况 , 其实前文已经解释过了 , 这里做个小回顾 : 触发视图更改事件 , 同时找一个donor , 从这个donor上通过recovery通道异步复制它所缺失的数据 , 并监控新生成的事务放进事务缓存队列中 , 当事务缓存队列中的事务数量为0 , 将视图更改事件写入到binlog中 , 并将该节点标记为ONLINE , 此时它才算是成功加入到组中。

成员离组的情况分为两种 : 自愿离组和非自愿离组。这两种离组方式造成的影响不同。

9.1 成员非自愿离组

除了自愿离组的情况(自愿离组见下一小节) , 所有离组的情况都是非自愿离组。比如节点宕机 , 断网等等。

  1. 节点非自愿离组时 , 故障探测机制会检测到这个问题 , 于是向组中报告这个问题。然后会触发组视图成员自动配置 , 需要大多数节点同意新视图。

  2. 非自愿离组时 , 组的大小不会改变 , 无论多少个节点的组 , 节点非自愿退出后 , 组大小还是5 , 只不过这些离组的节点被标记为非ONLINE。但注意 , 组的视图配置会改变 , 因为离组的节点状态需要标记为非ONLINE

  3. 非自愿离组时 , 会丢失法定票数。所以 , 当非自愿离组节点数量过多时 , 导致组中剩余节点数量达不到大多数的要求 , 组就会被阻塞。

  4. 举个例子 , 5节点的组 , 非自愿退出1个节点A后 , 这个组的大小还是5 , 但是节点A在新的视图中被标记为unreachable或其他状态。当继续非自愿退出2个节点后 , 组中只剩下2ONLINE节点 , 这时达不到大多数的要求 , 组就会被阻塞。

阻塞后的组只能手动干预 , 例如重启整个组。也可以对当前已阻塞的组强制更改组成员。例如 , 5个节点的组 , 当前只剩下2个节点为ONLINE , 那么可以强制更新这个组 , 让这个组只包含这两个成员 , 也就是说强制更改组的大小。

SET GLOBAL group_replication_force_members="192.168.100.21:10000,192.168.100.22:10001";

这种方法必须只能作为最后的手段 , 一个操作不当 , 就会导致脑裂。为什么会导致脑裂 ?

因为非自愿离开的成员可能并非下线了 , 而是出现了网络分区或其它原因将这个节点给隔离了。这样一来 , 这个节点会自认为自己是组中的唯一成员 , 它不知道还有另一个甚至多个同名的组存在。虽然被隔离的节点因为不满足大多数的要求而被阻塞 , 但如果将这些隔离的组之一、之二等强制更改组大小 , 那么它们都会解除阻塞 , 允许写入新数据 , 从而出现数据不一致、脑裂等各种恶劣事件。

所以 , 当多个节点非自愿离组导致组被阻塞后 , 最安全的方法是重启整个复制组。或者将所有被隔离的节点都完全下线 , 然后强制更改剩下的组让其解除阻塞 , 最后再将下线的节点重新加入到这个组中。

9.2 成员自愿离组

只有一种情况是节点自愿离组的情况 : 执行stop group_replication;语句。

自愿离组时 , 待离组成员会触发视图更改事件 , 通知组内其它成员 : 老孙我现在要去寻仙拜师了 , 猴儿们别挂念你孙爷爷。然后组内的其它节点就当这个成员从未出现过一样 , 它的离开除了对组复制性能造成一点影响之外 , 没有其它任何影响。

节点自愿离组时 , 不会丢失法定票数。所以无论多少个节点自愿离组 , 都不会出现"达不到大多数"的要求而阻塞组。

举个例子 , 5个节点的组 , 陆陆续续地依次自愿退出了4个节点 , 无论哪个节点退出 , 都会触发视图更改事件更改组的大小 , 这样一来 , 永远也不会出现组被阻塞的问题。

非自愿离组触发的视图更改不会更改组的大小 , 离组节点过多 , 会无法达到大多数的要求而阻塞组;自愿离组触发的视图更改会更改组的大小 , 无论多少个节点自愿离组 , 剩下的节点总是组的全部 , 当然能满足组的大多数要求 , 因此绝不会阻塞组。

10. 压缩组内的信息

使用组复制需要有一个低延迟、高带宽的网络环境 , 因为业务越繁忙 , 组内节点数量越多 , 组内要传递的消息就越多。如果突然遇到一个非常大的事务(例如load data infile中的数据非常多) , 可能会让组复制非常慢。

所以 , 如果业务中经常有大事务 , 或者网络带宽资源不足 , 可以考虑开启组内信息压缩功能。

例如 , 以下设置开启了压缩功能 , 压缩的阈值为2M左右。

STOP GROUP_REPLICATION;
SET GLOBAL group_replication_compression_threshold= 2097152;
START GROUP_REPLICATION;

当一个事务的消息超过2M时 , 就会将这个消息进行压缩。默认情况下已经开启了压缩功能 , 其阈值为1000000字节(大致1MB)。如果要关闭压缩功能 , 将阈值设置为0即可。

当组内其它节点收到了压缩的消息后 , 会进行解压 , 然后读取其中内容。

下图描述了压缩和解压发生的时间点 :

733013-20180625144825871-1688227968

一般情况下 , 无需去修改压缩的阈值 , 除非出现了性能严重不足的情况。

11.如何排错 ?

在使用组复制过程中 , 限制多多 , 要求多多 , 难免的问题也多多 , 有些是比较严重的问题 , 有些是小问题。于是 , 如何进行排错 ?

我个人有几点总结 :

  1. 看报错信息。

  2. 看错误日志。

  3. show binlog events仔细比对各节点的GTID

好像都是些废话啊。我在配置单主模型的组复制配置多主模型的组复制中非常详细介绍了配置组复制的步骤 , 除此之外还给出了几个我遇到过的几个问题和解决方案。

12.组复制的流程控制

这部分浏览即可 , 一切都是自动的 , 人为可控度不高 , 几乎也不需要人为修改。

组复制只有在组内所有节点都收到了事务 , 且大多数成员对该事务所在顺序及其他一些相关内容都达成一致时才会进行事务的提交。

如果写入组的事务总数量都在组中任何成员的写入能力范围之内 , 则可良好运行下去。如果某节点或某些节点的写吞吐量较其他节点更差 , 则这些节点可能会落后。

当组中有一些成员落后了 , 可能会带来一些问题 , 典型的是读取这些落后节点上的数据都是比较旧的。根据节点为什么落后的原因 , 组内其他成员可能会保存更多或更少的上下文 , 以便满足落后成员潜在的数据传输请求。

好在复制协议中有一种机制 , 可以避免在应用事务时 , 快速成员和慢速成员之间拉开的距离太大。这就是所谓的流程控制(flow control)机制。该机制试图实现以下几个目标 :

  1. 保证成员足够接近 , 使得成员之间的缓冲和非同步只是一个小问题;

  2. 快速适应不断变化的情况 , 例如不同的工作量、更多的写节点;

  3. 给每个成员分配公平的可用写容量;

  4. 不要因为避免浪费资源而过多地减少吞吐量。

可由两个工作队列来决定是否节流 : (1)认证队列(certification);(2)二进制日志上的应用队列(applier)。当这两个队列中的任何一个超出队列最大值(阈值)时 , 将会触发节流机制。只需配置 : (1)是否要对ceritierapplier或两者做flow control;(2)每个队列的阈值是多少。

flow control依赖于两个基本的机制 :

  1. 监控组内每个成员收集到的有关于吞吐量和队列大小的一些统计数据 , 以便对每个成员可以承受的最大写入压力做有根据的猜测;

  2. 每当有成员的写入量试图超出可用容量公平份额 , 就对其节流限制。


参考链接

MySQL高可用之组复制(4) : 详细分析组复制理论 - 骏马金龙 - 博客园


熊熊