这里演示的是如何配置MySQL组复制的多主模型(multi-primary)。在配置上 , 多主模型的组复制和单主模型基本没区别。

1. 组复制: 单主和多主模型

MySQL组复制支持单主模型和多主模型 , 它们都能保证MySQL数据库的高可用。

  1. 单主模型下:

    • 只有一个主节点 , 该主节点负责所有的写操作 , 其他节点作为slave节点提供读取服务(会自动设置为read-only)。

    • 在主节点故障 , 单主模型会自动选举新的主节点。选举后 , 剩余节点将指向该节点。但是 , 客户端还是会有部分请求路由到故障的主节点上 , 因此需要想办法解决这样的问题。这不是MySQL该考虑解决的问题 , 而是客户端应用程序、数据库中间件(常见的: ProxySQLMySQL Routermycatamoebacobar等)该解决的问题。

    • 只要非自愿离组的故障节点(自愿、非自愿离组 , 请参见配置单主模型的组复制)不超过大多数 , 组复制就不会被阻塞。

  2. 多主模型下:

    • 没有masterslave的概念。所有的节点都可以读、写数据。

    • 因为所有节点都能提供读写服务 , 所以性能较之单主模型要好一些。

    • 配置多主模型的工作方式 , 比单主模型的限制更多。

    • 节点非自愿故障后 , 除了影响一点性能 , 不会对组复制造成影响。除非故障的节点数过多 , 使得剩余在线节点达不到"大多数"的要求。

2. 单主和多主模型配置文件区别

以下是单主模型组复制的配置文件:

[mysqld]
datadir=/var/lib/mysql
socket=/var/lib/mysql/mysql.sock
​
server-id=131                     # 必须
gtid_mode=on                       # 必须
enforce_gtid_consistency=on        # 必须
log-bin=/var/lib/mysql/master-bin           # 必须
binlog_format=row                  # 必须
binlog_checksum=none               # 必须
master_info_repository=TABLE       # 必须
relay_log_info_repository=TABLE    # 必须
relay_log=/var/lib/mysql/relay-log          # 必须 , 如果不给 , 将采用默认值
log_slave_updates=ON               # 必须
sync-binlog=1                      # 建议
log-error=/var/lib/mysql/error.log
pid-file=/var/lib/mysql/mysqld.pid
​
transaction_write_set_extraction=XXHASH64         # 必须
loose-group_replication_group_name="aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"  # 必须
loose-group_replication_start_on_boot=off        # 建议设置为OFF
loose-group_replication_member_weigth = 40   # 非必需 , mysql 5.7.20才开始支持该选项
loose-group_replication_local_address="ABCDEFGHIJK"   # 必须 , 下一行也必须
loose-group_replication_group_seeds="abcdefg"

以上时5.7版本的配置, 8.0.42版本的配置推荐使用以下配置

[mysqld]
datadir=/var/lib/mysql
socket=/var/lib/mysql/mysql.sock
​
server-id=131                     # 必须
gtid_mode=on                       # 必须
enforce_gtid_consistency=on        # 必须
log-bin=/var/lib/mysql/master-bin           # 必须
#binlog_format=row                  # 新版本修改内容, 8.0版本以后已经被弃用,默认便是row格式, binlog的其他两个格式已经被弃用了
binlog_checksum=none               # 必须
#master_info_repository=TABLE       # 新版本已经弃用, 强制使用 TABLE 模式,将元数据存储在 mysql.slave_master_info 表中,以提高数据安全性和复制可靠性。
#relay_log_info_repository=TABLE    # 已经弃用, 新版本强制使用 TABLE 模式,将元数据存储在 mysql.slave_relay_log_info 表中,以提高数据一致性和崩溃恢复能力, 
relay_log=/var/lib/mysql/relay-log          # 必须 , 如果不给 , 将采用默认值
log_replica_updates=ON               # 新版本已经从log_slave_updates替换为log_replica_updates
sync-binlog=1                      # 建议
log-error=/var/lib/mysql/error.log
pid-file=/var/lib/mysql/mysqld.pid
​
transaction_write_set_extraction=XXHASH64         # 已经弃用, 未来版本可能直接强制使用 XXHASH64 算法,无需用户手动配置。
loose-group_replication_recovery_use_ssl= ON
loose-group_replication_group_name="aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"  # 必须
loose-group_replication_start_on_boot=off        # 建议设置为OFF
loose-group_replication_member_weigth = 40   # 非必需 , mysql 5.7.20才开始支持该选项
loose-group_replication_local_address="ABCDEFGHIJK"   # 必须 , 下一行也必须
loose-group_replication_group_seeds="abcdefg"

多主模型和单主模型的配置文件基本相同 , 除了需要加入:

group_replication_enforce_update_everywhere_checks=ON  # 非必需 , 但强烈建议,开启多主模式的参数。当该参数设置为 ON 时,Group Replication会强制对所有事务执行严格的跨节点一致性检查,确保事务在所有节点上都能安全执行,避免因多主写入导致的数据冲突或不一致。
group_replication_single_primary_mode=OFF  # 必须 , 表示关闭单主模型 , 即使用多主

需要注释权重行 , 因为多主模型下没有master的概念 , 所以无需选举的权重值。

# loose-group_replication_member_weigth = 40

此外 , 除非业务依赖于默认的repeatable read , 否则建议将事务隔离级别设置为read committed , 且不能设置为serializable级别(强制要求)。所以 , 如果允许 , 还可以加上:

transaction_isolation = 'read-committed' 

3. 配置多主模型

本文打算配置5个节点的多主模型复制组。

具体环境细节如下:

节点名称

系统版本

MySQL版本

客户端接口(eth0)

组内通信接口(eth0)

数据状态

s1

CentOS8

MySQL 8.0.42

192.168.194.131

192.168.194.131

全新实例

s2

CentOS8

MySQL 8.0.42

192.168.194.135

192.168.194.135

全新实例

s3

CentOS8

MySQL 8.0.42

192.168.194.141

192.168.194.141

全新实例

每个节点向外提供MySQL服务和组内通信都使用同一个接口。

  1. 修改主机名, 添加DNS解析

    因为组内每个节点都使用主机名进行解析其他成员的地址 , 所以必须配置好主机名 , 并保证每个节点都能正确解析主机名。

    s1上执行:

    # s1上 : 
    hostnamectl set-hostname --static node01
    hostnamectl -H root@192.168.194.135 set-hostname node02
    hostnamectl -H root@192.168.194.141 set-hostname node03
    ​
    # 写/etc/hosts
    # s1上 : 
    cat >>/etc/hosts<<eof
    192.168.194.131 node01
    192.168.194.135 node02
    192.168.194.141 node03
    eof
    scp /etc/hosts 192.168.194.135:/etc
    scp /etc/hosts 192.168.194.141:/etc
  2. 提供配置文件

    以下是s1节点配置文件

    [mysqld]
    datadir=/var/lib/mysql
    socket=/var/lib/mysql/mysql.sock
    ​
    server-id=131                      # 必须
    gtid_mode=on                       # 必须
    enforce_gtid_consistency=on        # 必须
    log-bin=/var/lib/mysql/master-bin           # 必须
    #binlog_format=row                  # 必须
    binlog_checksum=none               # 必须
    #master_info_repository=TABLE       # 必须
    #relay_log_info_repository=TABLE    # 必须
    relay_log=/var/lib/mysql/relay-log          # 必须 , 如果不给 , 将采用默认值
    log_replica_updates=ON               # 必须
    sync-binlog=1                      # 建议
    log-error=/var/lib/mysql/error.log
    pid-file=/var/lib/mysql/mysqld.pid
    ​
    ​
    transaction_isolation = 'read-committed'   # 建议项
    ​
    transaction_write_set_extraction=XXHASH64         # 必须
    loose-group_replication_group_name="aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"  # 必须
    loose-group_replication_enforce_update_everywhere_checks=ON  # 非必需 , 但强烈建议
    loose-group_replication_single_primary_mode=OFF  # 必须 , 关闭单主模型 , 即使用多主
    loose-group_replication_start_on_boot=off        # 建议设置为OFF
    loose-group_replication_local_address="192.168.194.131:33061"   # 必须
    # 下一行也必须 , 这里我将所有节点都添加到种子节点列表中
    loose-group_replication_group_seeds="192.168.194.131:33061,192.168.194.135:33061,192.168.194.141:33061"

    s2s3节点的配置文件和s1类似 , 但server_idloose-group_replication_local_address必须改成各节点对应的值。

    s2的配置(未包括和s1相同的配置):

    server_id=135
    loose-group_replication_local_address="192.168.194.135:33061"

    s3的配置(未包括和s1相同的配置):

    server_id=141
    loose-group_replication_local_address="192.168.194.141:33061"

    配置结束后 , 启动mysqld实例。

    systemctl start mysqld
  3. 创建复制用户 , 并设置恢复通道"group_replication_recovery"。

    我这里将s1作为组内的第一个节点。所以只需在s1上创建复制用户即可 , 以后其他节点加入组时 , 会将该操作复制走。

    s1上执行:

    mysql> create user repl@'192.168.194.%' identified by 'P@ssword1!';
    mysql> grant replication slave on *.* to repl@'192.168.194.%';

    设置恢复阶段的异步复制通道:

    s1上执行:

    mysql> change master to 
                master_user='repl',
                master_password='P@ssword1!'
                for channel 'group_replication_recovery';

    注意: 后面的操作中 , 如果没有明确指定在s2s3上执行 , 那么都是在s1上执行的。有些操作是不允许在多个节点上都执行的。

  4. s1上安装组复制插件 , 并引导创建复制组。

    安装组复制插件, 在s1上执行

    mysql> install plugin group_replication soname 'group_replication.so';

    s1节点组的引导节点 , 在s1上执行:

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

    执行完上面的语句后 , 本实验所需的复制组就已经被节点s1创建了。以后s2-s5节点就可以陆续地加入到组中。

    在其他节点加组之前 , 先看下组中的节点s1是否已ONLINE。

    mysql> select * from performance_schema.replication_group_members\G
    *************************** 1. row ***************************
    CHANNEL_NAME: group_replication_applier
       MEMBER_ID: a659234f-6aea-11e8-a361-000c29ed4cf4
     MEMBER_HOST: s1.longshuai.com
     MEMBER_PORT: 3306
    MEMBER_STATE: ONLINE
  5. 向组中加入新节点: s2s3

    s2s3上都执行:

    change master to 
                master_user='repl',
                master_password='P@ssword1!'
                for channel 'group_replication_recovery';
    
    install plugin group_replication soname 'group_replication.so';

    然后依次在s2s3上执行下面的语句开启组复制功能 , 开启该功能后 , 将自动加入到组中。但注意 , 要依次执行 , 在每个start语句返回成功后再去下一个节点执行:

    start group_replication;
  6. 查看组中成员s1、s2、s3是否全都ONLINE

    在任意一个节点上执行:

    mysql> select * from performance_schema.replication_group_members\G
    *************************** 1. row ***************************
                  CHANNEL_NAME: group_replication_applier
                     MEMBER_ID: 5f0ecba8-1fa3-11f0-890b-000c292962df
                   MEMBER_HOST: node01
                   MEMBER_PORT: 3306
                  MEMBER_STATE: ONLINE
                   MEMBER_ROLE: PRIMARY
                MEMBER_VERSION: 8.0.42
    MEMBER_COMMUNICATION_STACK: XCom
    *************************** 2. row ***************************
                  CHANNEL_NAME: group_replication_applier
                     MEMBER_ID: 80682587-1faf-11f0-96b3-000c29df6514
                   MEMBER_HOST: node03
                   MEMBER_PORT: 3306
                  MEMBER_STATE: ONLINE
                   MEMBER_ROLE: PRIMARY
                MEMBER_VERSION: 8.0.42
    MEMBER_COMMUNICATION_STACK: XCom
    *************************** 3. row ***************************
                  CHANNEL_NAME: group_replication_applier
                     MEMBER_ID: dd641f81-1fae-11f0-8c33-000c297993ef
                   MEMBER_HOST: node02
                   MEMBER_PORT: 3306
                  MEMBER_STATE: ONLINE
                   MEMBER_ROLE: PRIMARY
                MEMBER_VERSION: 8.0.42
    MEMBER_COMMUNICATION_STACK: XCom
    3 rows in set (0.00 sec)

4. 测试多主模型的写负载

多主模型下 , 所有节点都可以进行读、写操作。但请注意 , 组复制的几个要求: 表必须为innodb表(虽然创建myisam表不报错 , 但修改数据会报错)、每个表必须有主键、不能有级联的外键等。

在任意节点上执行以下写操作进行测试:

create database mut_gr;
create table mut_gr.t1(id int primary key);
insert into mut_gr.t1 values(1),(2),(3),(4);

在任意节点上继续执行写操作:

insert into mut_gr.t1 values(5);

查看数据是否都已经同步到给节点上

5. 组复制模式

5.1 参数切换组复制模式

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

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

5.1.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;

5.1.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)

5.1.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> 

5.1.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;

5.1.5 参数持久化

#根据场景使用多主或单主模式
#on或off如下两个参数
#如下示例设置是开启多主模式
[root@node1 ~]# vim /etc/my.cnf
[mysqld]
#关闭单主模式的参数
group_replication_single_primary_mode=off
#开启多主模式的参数
group_replication_enforce_update_everywhere_checks=on

5.2 函数配置组复制模式

除了以上使用参数配置复制模式,也可以用新版本的函数配置模式。可以使用一组依赖于组操作协调器的函数在组复制运行时联机配置组,这些函数由版本 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%";

5.2.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)

5.2.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)

5.2.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字典顺序控制。

5.3 配置并发写实例数

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)

5.4 设置组的通信协议版本

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

5.5 恢复其他配置

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

  • 加入组的服务器已经包含的数据与恢复期间来自所选捐赠者的数据存在冲突。

  • 赠者包含新增成员已经清除 (purge) GTID 的数据。

  • 恢复的接收线程或应用线程失败。

5.5.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)

5.5.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变量配置的秒数。

5.5.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)

参考链接

MySQL高可用之组复制技术(3): 配置多主模型的组复制 - 骏马金龙 - 博客园

MySQL复制(八)组复制 - 一介IT - 博客园


熊熊