MySQL 组复制系列文章:
MySQL 组复制大纲
MySQL 组复制(1): 组复制技术简介
MySQL 组复制(2): 配置单主模型的组复制
MySQL 组复制(3): 配置多主模型的组复制
MySQL 组复制(4): 组复制理论透彻分析
这一篇对 MySQL 组复制做个详细的整理和解释, 是 MySQL 组复制官方手册的整理版和总结.
1. 组复制插件架构图
MySQL 组复制是一个 MySQL 插件, 它基于常规的 MySQL 复制, 利用了基于行格式的二进制日志和 GTID 等特性. 下图是 MySQL 组复制的整体框架图.
以下是对该图中各组件的大致介绍, 涉及到的术语先浏览一遍, 后面会详细解释.
从上图的最顶端开始, 有一系列的 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.
多主模型: 复制组中的任何一个节点都可以写, 因此没有 master 和 slave 的概念, 只要突然故障的节点数量不太多, 这个多主模型就能继续可用.
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_recovery
.Recovery 组件会从 donor 上复制所有新节点缺失的数据对应的 binlog, 然后应用在自身. 当新节点数据和组中已经保持一致, 则新节点成功加入到组, 它将标记为 ONLINE.
实际上, 这只是恢复的一个阶段, 还有第二阶段, 有了上面的基础, 下面的解释就容易理解了.
在新节点准备开始加入组的时候, Recovery 组件有两个功能:(1). 选择 donor, 并从 donor 处复制缺失的数据;(2). 监控组中新产生的事务并放进自己的事务缓存队列中.
例如, 在填补缺失的数据时, 客户端向组中的可写节点插入了一条数据, 这个数据不会被新节点的异步复制抓走, 新节点会监控到这个事务. 如下图:
所以将恢复过程概括一下: 新节点的 recovery 组件通过异步复制通道从 donor 处获取 p1 之前的所有数据, 并监控组中新生成的事务放入自己的事务缓存队列中. 当新节点将 p1 之前的数据恢复完成后, 将执行缓存队列中的事务, 直到缓存中的事务数量减为 0 后, 才表示新节点已经完全赶上了组中的数据, 这是它才可以标识为 ONLINE, 并真正成为组中的一员.
这里有个问题需要考虑: 事务数量相同, 为什么新节点能赶上组? 第一个原因是事务不会源源不断地生成, 它总有停顿的时候; 第二个原因归功于基于行格式的 binlog(row-based binlog), 除了发起事务的那个节点, 只要事务被大多数节点同意了, 所有节点都根据 binlog 行中设置的值直接修改数据, 而不会完整地执行事务逻辑. 换句话说, 事务发起节点修改数据的速度没有其他节点快, 尽管它们是同时提交的.
5. 如何执行单个事务?
假设组中已经有 5 个节点 (s1,s2,s3,s4,s5) 了, 这些节点目前全都是 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) 对这个事务进行应用. 如果大多数节点未达成一致, 则这个事务会回滚, 日志不会写入到日志中.
以下为 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. 如何执行并发事务: 冲突检测?
多个事务分两种: 线性的事务和并发的事务. 线性的多个事务和执行单个事务没有什么区别, 无非是一个一个事务从上到下执行下去. 并发事务就要多方面考虑.
如果有多个客户端同时发起了事务 t1,t2,t3,t4, 这 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 序列
只要同一个节点执行的事务, 它的前缀是一样的, 只是后缀会不断增长. 例如, 3 个 s1 节点的事务, 它们 gtid 的后缀可能会是 146,147,148.3 个 s2 节点的事务, 它们的 gtid 后缀可能会是 1000033,1000034,1000035.
注意:
每个事务除了 gtid, 还有一个分布式的 xid. 这个 id 也必须全局唯一, 但同一个事务在不同节点上的体现值不一样. 例如, 在 s1 节点上, 事务的 xid=200, 应用到 s2 节点上, 这个事务的 xid 会改为适应自己节点上的 id, 可能值为 xid=222.
另外, 这个 xid 并不能保证顺序. 例如 s1 上执行开启事务 t1 执行 DML, 但不提交, 然后在 s2 节点上开启事务 t2 执行 DML 并提交, 最后提交 s1 上的事务, 那么在 s1 上, t1 的 xid 虽然值更小, 但在 binlog 中却排在 t2 后面.
回到正题, 并发事务 t1,t2,t3,t4 如何执行:
(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 行是 2 个 DDL 语句代表的事件, 后 5 行是一个 DML 语句代表的事件, 它是一个分布式事务. 不难看出, DDL 语句从头到尾就只记录了它的 GTID 和语句部分.
换句话说, DDL 语句不会进行冲突检测. 这会出现两个可怕的陷阱:
陷阱一:
如果多个节点并发执行多个 DDL 语句, 后执行的 DDL 如果能正确执行, 其实是在对前面的 DDL 进行覆盖. 如果不能正确执行, 将报错, 例如第一个 DDL 语句的作用是删除一个字段, 第二个 DDL 语句是修改这个字段的数据类型, 这时第二个 DDL 语句将失去操作目标而报错.
陷阱二:
如果多个节点并发执行 DDL 和 DML 语句, 将可能出错. 例如第一个 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 和视图更新事件.
在成员加组, 离组时会触发视图更改, 它不是 DDL,DCL,DML, 但却也会当作一个事件写入 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_id 为 id1, 那么 id1 之前的事务需要全部复制到新节点上, id1 之后的事务是加组之后的事务, 不需要复制给新节点(前文已经解释过, 在赶上组中数据之前, 这部分事务会放进新节点的事务缓存队列).
其实, binlog 中的 view_change 事件还充当另一个角色: 组内其余节点感知新节点已经成功加组或成功离组. 例如新成员加组的情况, 当 view_id 写入 binlog, 表示这个新节点已经标记为 ONLINE, 其它节点知道它已经在线了, 信息广播的时候也会广播给这个新节点, 这个新节点也会占有一个法定票数.
9. 成员加组, 离组的细节
新成员加组的情况, 其实前文已经解释过了, 这里做个小回顾: 触发视图更改事件, 同时找一个 donor, 从这个 donor 上通过 recovery 通道异步复制它所缺失的数据, 并监控新生成的事务放进事务缓存队列中, 当事务缓存队列中的事务数量为 0, 将视图更改事件写入到 binlog 中, 并将该节点标记为 ONLINE, 此时它才算是成功加入到组中.
成员离组的情况分为两种: 自愿离组和非自愿离组. 这两种离组方式造成的影响不同.
9.1 成员非自愿离组
除了自愿离组的情况(自愿离组见下一小节), 所有离组的情况都是非自愿离组. 比如节点宕机, 断网等等.
节点非自愿离组时, 故障探测机制会检测到这个问题, 于是向组中报告这个问题. 然后会触发组视图成员自动配置, 需要大多数节点同意新视图.
非自愿离组时, 组的大小不会改变, 无论多少个节点的组, 节点非自愿退出后, 组大小还是 5, 只不过这些离组的节点被标记为非 ONLINE. 但注意, 组的视图配置会改变, 因为离组的节点状态需要标记为非 ONLINE.
非自愿离组时, 会丢失法定票数. 所以, 当非自愿离组节点数量过多时, 导致组中剩余节点数量达不到大多数的要求, 组就会被阻塞.
举个例子, 5 节点的组, 非自愿退出 1 个节点 A 后, 这个组的大小还是 5, 但是节点 A 在新的视图中被标记为 unreachable 或其他状态. 当继续非自愿退出 2 个节点后, 组中只剩下 2 个 ONLINE 节点, 这时达不到大多数的要求, 组就会被阻塞.
阻塞后的组只能手动干预, 例如重启整个组. 也可以对当前已阻塞的组强制更改组成员. 例如, 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 即可.
当组内其它节点收到了压缩的消息后, 会进行解压, 然后读取其中内容.
下图描述了压缩和解压发生的时间点:
一般情况下, 无需去修改压缩的阈值, 除非出现了性能严重不足的情况.
11. 监控复制组
组内的很多信息都可以被监控, 它们都记录在 mysql 的 performance_schema 架构中.
这部分没什么理论性的内容, 详细内容见我的翻译: 监控 MySQL 组复制.
12. 使用组复制的要求和局限性
在真正开始使用组复制之前, 第一件事不是去学会如何搭建组复制环境, 不是学一大堆的理论, 而是了解并记住它的要求和局限性, 否则一不小心就会酿成大祸.
详细内容见我的翻译: MySQL 组复制的限制和局限性.
13. 如何排错?
在使用组复制过程中, 限制多多, 要求多多, 难免的问题也多多, 有些是比较严重的问题, 有些是小问题. 于是, 如何进行排错?
我个人有几点总结:
看报错信息.
看错误日志.
show binlog events 仔细比对各节点的 GTID.
好像都是些废话啊. 我在配置单主模型的组复制和配置多主模型的组复制中非常详细介绍了配置组复制的步骤, 除此之外还给出了几个我遇到过的几个问题和解决方案.
14. 组复制的流程控制
这部分浏览即可, 一切都是自动的, 人为可控度不高, 几乎也不需要人为修改.
组复制只有在组内所有节点都收到了事务, 且大多数成员对该事务所在顺序及其他一些相关内容都达成一致时才会进行事务的提交.
如果写入组的事务总数量都在组中任何成员的写入能力范围之内, 则可良好运行下去. 如果某节点或某些节点的写吞吐量较其他节点更差, 则这些节点可能会落后.
当组中有一些成员落后了, 可能会带来一些问题, 典型的是读取这些落后节点上的数据都是比较旧的. 根据节点为什么落后的原因, 组内其他成员可能会保存更多或更少的上下文, 以便满足落后成员潜在的数据传输请求.
好在复制协议中有一种机制, 可以避免在应用事务时, 快速成员和慢速成员之间拉开的距离太大. 这就是所谓的流程控制 (flow control) 机制. 该机制试图实现以下几个目标:
保证成员足够接近, 使得成员之间的缓冲和非同步只是一个小问题;
快速适应不断变化的情况, 例如不同的工作量, 更多的写节点;
给每个成员分配公平的可用写容量;
不要因为避免浪费资源而过多地减少吞吐量.
可由两个工作队列来决定是否节流:(1)认证队列 (certification);(2) 二进制日志上的应用队列 (applier). 当这两个队列中的任何一个超出队列最大值(阈值) 时, 将会触发节流机制. 只需配置:(1)是否要对 ceritier 或 applier 或两者做 flow control;(2)每个队列的阈值是多少.
flow control 依赖于两个基本的机制:
监控组内每个成员收集到的有关于吞吐量和队列大小的一些统计数据, 以便对每个成员可以承受的最大写入压力做有根据的猜测;
每当有成员的写入量试图超出可用容量公平份额, 就对其节流限制.
来源: https://www.cnblogs.com/f-ck-need-u/p/9225442.html