目录
一、配置组复制模式
组复制可以以单主模式或多主模式运行,缺省采用单主模式。单主模式中只有一个可以读写的服务器,其它服务器只读。多主模式中,所有服务器均可读写。无论部署模式如何,组复制都不处理客户端故障转移,而必须由应用程序本身、连接器或中间件(如MySQL router)处理此问题。
1. 单主模式
在此模式下,组中具有单一可读写的主服务器,通常是第一个引导组的服务器,所有其它成员都为只读。这种设置自动发生。当主服务器失败时,会自动选择新的主服务器,如图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选择主库。
主服务器可由下面查询确认:
mysql> select member_id, member_host, member_role from performance_schema.replication_group_members;
+--------------------------------------+-------------+-------------+
| member_id | member_host | member_role |
+--------------------------------------+-------------+-------------+
| 5c93a708-a393-11e9-8343-005056a5497f | hdp4 | SECONDARY |
| 5f045152-a393-11e9-8020-005056a50f77 | hdp3 | PRIMARY |
| 8eed0f5b-6f9b-11e9-94a9-005056a57a4e | hdp2 | SECONDARY |
+--------------------------------------+-------------+-------------+
3 rows in set (0.00 sec)
2. 多主模式
多主模式中所有服务器角色等价,各服务器都能读写。客户端故障转移如图2所示。
多主模式下部署组复制时,将进行以下检查:
- 如果事务在SERIALIZABLE隔离级别下执行,则在与组同步时其提交失败。
- 如果事务在具有级联外键约束的表上执行,则在与组同步时事务无法提交。
通过将选项group_replication_enforce_update_everywhere_checks设置为FALSE,可以禁用这些检查。单主模式下此选项必须设置为FALSE。
3. 联机配置组复制模式
可以使用一组依赖于组操作协调器的函数在组复制运行时联机配置组,这些函数由版本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%";
(1)更改主服务器
使用group_replication_set_as_primary() 函数更改单主组中的主服务器,对于多主组此功能无效。只有主库才允许写入,因此如果该成员上正在运行异步通道复制,则在异步通道复制停止之前不允许切换。通过发出以下命令传递要成为该组新主服务器成员的server_uuid:
mysql> select group_replication_set_as_primary('8eed0f5b-6f9b-11e9-94a9-005056a57a4e');
+--------------------------------------------------------------------------+
| group_replication_set_as_primary('8eed0f5b-6f9b-11e9-94a9-005056a57a4e') |
+--------------------------------------------------------------------------+
| Primary server switched to: 8eed0f5b-6f9b-11e9-94a9-005056a57a4e |
+--------------------------------------------------------------------------+
1 row in set (0.02 sec)
(2)更改模式
group_replication_switch_to_multi_primary_mode()函数用于单主改多主:
mysql> select member_id, member_host, member_role from performance_schema.replication_group_members;
+--------------------------------------+-------------+-------------+
| member_id | member_host | member_role |
+--------------------------------------+-------------+-------------+
| 5c93a708-a393-11e9-8343-005056a5497f | hdp4 | PRIMARY |
| 5f045152-a393-11e9-8020-005056a50f77 | hdp3 | SECONDARY |
| 8eed0f5b-6f9b-11e9-94a9-005056a57a4e | hdp2 | 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 (0.01 sec)
mysql> select member_id, member_host, member_role from performance_schema.replication_group_members;
+--------------------------------------+-------------+-------------+
| member_id | member_host | member_role |
+--------------------------------------+-------------+-------------+
| 5c93a708-a393-11e9-8343-005056a5497f | hdp4 | PRIMARY |
| 5f045152-a393-11e9-8020-005056a50f77 | hdp3 | PRIMARY |
| 8eed0f5b-6f9b-11e9-94a9-005056a57a4e | hdp2 | PRIMARY |
+--------------------------------------+-------------+-------------+
3 rows in set (0.00 sec)
下面验证一下多主模式下对组复制的限制。
- 串行化隔离级别下报错。
mysql> show create table test.t1\G
*************************** 1. row ***************************
Table: t1
Create Table: CREATE TABLE `t1` (
`a` bigint(20) NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`a`)
) ENGINE=InnoDB 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
- 级联更新报错
mysql> create table stu(
-> sid int unsigned primary key auto_increment,
-> name varchar(20) not null);
stu(sid));
Query OK, 0 rows affected (0.02 sec)
mysql>
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));
Query OK, 0 rows affected (0.03 sec)
mysql> insert into stu (name) value ('zxf');
Query OK, 1 row affected (0.00 sec)
mysql>
mysql> insert into sc(sid,score) values ('1','98');
Query OK, 1 row affected (0.01 sec)
mysql> drop table sc;
Query OK, 0 rows affected (0.02 sec)
mysql> drop table stu;
Query OK, 0 rows affected (0.01 sec)
mysql> create table stu(
-> sid int unsigned primary key auto_increment,
-> name varchar(20) not null);
Query OK, 0 rows affected (0.02 sec)
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);
Query OK, 0 rows affected (0.02 sec)
mysql> insert into stu (name) value ('zxf');
Query OK, 1 row affected (0.00 sec)
mysql>
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>
group_replication_switch_to_single_primary_mode()函数用于多主改单主:
mysql> select group_replication_switch_to_single_primary_mode('8eed0f5b-6f9b-11e9-94a9-005056a57a4e');
+-----------------------------------------------------------------------------------------+
| group_replication_switch_to_single_primary_mode('8eed0f5b-6f9b-11e9-94a9-005056a57a4e') |
+-----------------------------------------------------------------------------------------+
| Mode switched to single-primary successfully. |
+-----------------------------------------------------------------------------------------+
1 row in set (0.01 sec)
如果未传入任何字符串,则新主节点的选择由配置的选举权重或server_uuid字典顺序控制。
4. 配置并发写实例数
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.00 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. 设置组的通信协议版本
从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.16 |
+------------------------------------------------+
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> 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.16");
+-----------------------------------------------------------------------------------+
| group_replication_set_communication_protocol("8.0.16") |
+-----------------------------------------------------------------------------------+
| The operation group_replication_set_communication_protocol completed successfully |
+-----------------------------------------------------------------------------------+
1 row in set (0.00 sec)
group_replication_set_communication_protocol()函数作为组操作实现,因此它将在组的所有成员上同时执行。组操作开始缓冲消息并等待传递已完成的任何传出消息,然后更改通信协议版本并发送缓冲的消息。如果成员在更改通信协议版本后加入该组,则组成员将使用新协议版本。
二、保证数据一致性
1. 组复制数据一致性简介
对分布式系统的一个重要需求是它能够提供一致性保证。就分布式一致性而言,无论是在正常还是失败修复的操作中,组复制始终是始终保持最终一致性。可以将影响数据一致性的事件分为两类:一是手动或由故障自动触发的控制操作;二是数据流。
(1)控制操作
与一致性相关的组复制操作包括:添加或移除组成员、网络故障保护和主库故障转移。添加或移除组成员的数据一致性保障已经在“分布式恢复”中描述。
- 网络故障写保护
当一个组成员离开复制组后,该成员不能继续接收更新事务,否则只能在本地提交,造成数据不一致。为了提高安全性,执行STOP GROUP_REPLICATION时,在服务器上启用超级只读模式。这将导致服务器在离开组时默认只读,无论它离开的原因是手动还是网络故障。
mysql> select member_id, member_host, member_role from performance_schema.replication_group_members;
+--------------------------------------+-------------+-------------+
| member_id | member_host | member_role |
+--------------------------------------+-------------+-------------+
| 5c93a708-a393-11e9-8343-005056a5497f | hdp4 | PRIMARY |
| 5f045152-a393-11e9-8020-005056a50f77 | hdp3 | PRIMARY |
| 8eed0f5b-6f9b-11e9-94a9-005056a57a4e | hdp2 | 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)
mysql> stop group_replication;
Query OK, 0 rows affected (7.92 sec)
mysql> select member_id, member_host, member_role from performance_schema.replication_group_members;
+--------------------------------------+-------------+-------------+
| member_id | member_host | member_role |
+--------------------------------------+-------------+-------------+
| 5f045152-a393-11e9-8020-005056a50f77 | hdp3 | |
+--------------------------------------+-------------+-------------+
1 row in set (0.00 sec)
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)
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_role from performance_schema.replication_group_members;
+--------------------------------------+-------------+-------------+
| member_id | member_host | member_role |
+--------------------------------------+-------------+-------------+
| 5c93a708-a393-11e9-8343-005056a5497f | hdp4 | PRIMARY |
| 5f045152-a393-11e9-8020-005056a50f77 | hdp3 | PRIMARY |
| 8eed0f5b-6f9b-11e9-94a9-005056a57a4e | hdp2 | PRIMARY |
+--------------------------------------+-------------+-------------+
3 rows in set (0.00 sec)
- 主库故障转移
单主模式中,当主库发生故障,一个从库被提升为主库时,对于积压事务与新事务有两种可选的处理方式:1)可以立即服务应用程序,无论复制积压的数量有多大;2)在处理积压事务之前限制访问。
第一种方案中,系统将花费最少的时间在主库故障之后通过选择新主库来保护稳定的组成员资格,然后在应用旧主库积压的事务时立即允许数据访问。这种方式能够保证写入一致性,但可能读取到过时的数据。使用第二种方法,系统将在主库故障后保护稳定的组成员资格,并以与第一种方案相同的方式选择新主库。但在这种情况下,该组将等待新主库应用所有积压事务,之后才允许数据访问。故障转移所需的时间与积压大小成正比,在均衡的复制组中,积压应该很小。
MySQL 8.0.14之前采用可用性最大化策略(第一种方法),并且不可配。MySQL 8.0.14及更高版本中可以使用group_replication_consistency变量配置组成员在主库故障转移期间提供的事务一致性保证级别。
(2)数据流
数据流影响组一致性的方式基于读取和写入。单主模式中,通常将传入的读/写事务进行拆分,写入路由到主库,读取均匀分配给从库。复制组对外应该表现为单个实体,因此可以合理地期望主库上的写入在从库上即时可用。尽管组复制是在实现Paxos算法的组通信系统(GCS)协议之上编写的,但组复制的某些部分是异步的,这意味着数据异步应用于从库,因此可能出现这样的情况,客户端C1在主库上写入“A = 2 WHERE A = 1”,立即连接到从库但读取到“A = 1”。这是MySQL 8.0.14之前唯一可用的一致性级别。
可以选择在读取或写入时同步数据。如果在读取时进行同步,则当前客户端会话将等待一个给定点,即所有先前更新事务完成的时间点,然后才能开始执行。此方法仅影响当前会话,所有其它并发数据操作不受影响。如果在写入时进行同步,则写入会话将等待所有其它成员都写入其数据。由于组复制遵循事务的总顺序,这意味着需要等待其它成员执行队列中所有先前的写入及其本次写入。这两种可选方案都能确保前面例子中,即使立即连接到从库,客户端C1也将始终读取“A = 2”。同步点的确定与系统工作负载直接相关。
写时同步适用场景:
- 组的写入比读取少的多,希望对读取进行负载均衡,又不对读取哪个服务器进行额外限制以避免读取旧数据。
- 主要是只读数据的组,希望读写事务一旦提交就应用到所有成员,以便后续读取最新数据。
读时同步适用场景:
- 组的写入比读取多的多,希望对读取进行负载均衡,又不对读取哪个服务器进行额外限制以避免读取旧数据。
- 希望工作负载中的特定事务始终从组中读取最新数据,以便每当更新敏感数据时强制读取最新值。
可以简单但不够严谨地理解为读多写时同步,写多读时同步,目的就是减少同步次数同时满足数据一致性要求。后面会看到读、写两个同步点以及两者的组合,对应group_replication_consistency系统变量的可选值,用作选择组复制一致性级别。
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';
3. 选择适当的一致性级别
group_replication_consistency系统变量用于配置组复制的一致性级别,不同配置对组处理的只读(RO)和读写(RW)事务产生不同的影响。按增加事务一致性保证的顺序,该变量有EVENTUAL、BEFORE_ON_PRIMARY_FAILOVER、BEFORE、AFTER、BEFORE_AND_AFTER五个可选值,缺省为EVENTUAL。从上一小节已经了解了BEFORE_ON_PRIMARY_FAILOVER的作用,下面介绍其它四个可选值。
(1)EVENTUAL——缺省值
读、写事务都直接执行,不等待之前复制积压的事务,也不等待其它组成员应用这些事务。这是MySQL 8.0.14之前的组复制行为。
- 流程图
- 算法描述
- 在组成员M1上开始事务T1。
- T1执行到提交点,在该点事务数据已经发送到所有组成员,包括发起T1的成员M1。
- 每个成员检查T1是否和之前的事务存在冲突,如果是则回滚T1,否则T1在M1上提交,并且进入其它成员的事务队列,排队执行和提交。
- 在成员M3接收到T1数据前开始事务T2。在M3上,T2将在T1之前执行,因此T2可能读取到过时数据。
- 示例
hdp3上锁定表。
mysql> lock table t1 read;
Query OK, 0 rows affected (0.00 sec)
hdp2上执行插入成功,不会产生等待。
mysql> insert into t1 values (1);
Query OK, 1 row affected (0.01 sec)
hdp3上执行查询,由于表被锁定,插入事务处于等待状态,因此查询结果为insert前的数据。表解锁后,插入事务随之执行和提交,查询结果为insert后的数据。
mysql> select * from t1;
Empty set (0.00 sec)
mysql> unlock tables;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from t1;
+---+
| a |
+---+
| 1 |
+---+
1 row in set (0.00 sec)
(2)BEFORE——读时同步
在开始执行之前,事务将等待所有先前的事务完成。这可确保此事务将在最新的数据快照上执行,而不管在哪个成员上执行。此一致性级别涵盖BEFORE_ON_PRIMARY_FAILOVER提供的一致性保证。
- 流程图
- 算法描述
- 在组成员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,所有其它成员不受此等待的影响。
- 示例
hdp3上,会话1中锁定表。
mysql> lock table t1 read;
Query OK, 0 rows affected (0.00 sec)
hdp2上执行插入成功,不会产生等待。
mysql> insert into t1 values (1);
Query OK, 1 row affected (0.01 sec)
hdp3上,会话2中在BEFORE级别下执行查询,由于表被锁定,插入事务处于等待状态,因此查询等待。
mysql> set @@session.group_replication_consistency='BEFORE' ;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from t1;
hdp3上,会话1中解锁表。
mysql> unlock tables;
Query OK, 0 rows affected (0.00 sec)
表解锁后,插入事务随之执行和提交,等待的查询得以执行,结果为insert后的数据。因为单从输出结果中无法区分是立即返回结果,还是先挂起一段时间后再返回的结果,所以这里没贴出查询结果。
(3)AFTER——写时同步
读写事务将等待其更改已应用于其它成员,对只读事务没有影响。此模式确保在本地成员上提交事务时,后续事务会读取最新值,而无论在哪个成员上执行。将此模式与主要只读操作的组一起使用,保证应用的读写事务在提交后随处可用。通过仅在读写事务上使用同步,减少了只读事务上的同步开销。此一致性级别涵盖BEFORE_ON_PRIMARY_FAILOVER提供的一致性保证。
- 流程图
- 算法描述
- 在组成员M1上使用AFTER级别开始事务T1。
- T1执行到提交点,在该点事务数据已经发送到所有组成员,包括发起T1的成员M1。
- 每个成员检查T1是否和之前的事务存在冲突,如果是则回滚T1,否则执行第4步。
- T1在其它成员上排队执行。一旦事务进入准备阶段,即数据在等待提交指令的存储引擎上最终确定时,它将向所有成员发送ACK确认。
- 一旦所有成员收到来自所有成员的确认,它们都提交事务。
- 在成员M3上的T1事务处于准备和提交之间时,使用EVENTUAL级别开始事务T2。此时T1还没有提交,所以T2将等待T1提交完成后再开始执行。这确保T1后的事务都能读取到T1的最新数据。
- 示例
hdp3上,会话1中锁定表。
mysql> lock table t1 read;
Query OK, 0 rows affected (0.00 sec)
hdp2上在AFTER级别执行插入。因为表被锁定,插入事务处于等待状态。
mysql> set @@session.group_replication_consistency='AFTER' ;
Query OK, 0 rows affected (0.00 sec)
mysql> insert into t1 values (1);
hdp3上,会话2中执行查询。由于插入事务处于等待状态,因此查询等待。
mysql> select * from t1;
hdp3上,会话1中解锁表。
mysql> unlock tables;
Query OK, 0 rows affected (0.00 sec)
表解锁后,插入事务随之执行和提交,其后等待的查询得以执行,结果为insert后的数据。基于和前面BEFORE示例中相同的原因,这里没贴出查询结果。
(4)BEFORE_AND_AFTER——读写时都同步
此事务开始执行时等待:1)所有该事务先前的事务都执行完成;2)该事务的变更已应用于其它成员。这可确保:1)此事务将在最新的数据快照上执行;2)一旦此事务完成,所有后续事务都会读取到包含其更改的数据库状态,无论它们在哪个成员上执行。此一致性级别涵盖BEFORE_ON_PRIMARY_FAILOVER提供的一致性保证。
- 流程图
BEFORE_AND_AFTER一致性级别的算法流程是将BEFORE和AFTER组合在一起。读写事务等待之前所有的事务完成,并且等待其在所有节点上的变更结束。只读事务需要等待之前所有的事务完成。
- 示例
hdp2上,会话1中锁定表,并执行更新。
mysql> lock table t1 write;
Query OK, 0 rows affected (0.00 sec)
mysql> insert into t1 values (1);
Query OK, 1 row affected (0.01 sec)
hdp3上,会话1中锁定表,并执行更新。
mysql> lock table t1 write;
Query OK, 0 rows affected (0.00 sec)
mysql> insert into t1 values (2);
Query OK, 1 row affected (0.00 sec)
hdp2上,会话2中在BEFORE_AND_AFTER级别执行查询。因为同一服务器上的更新被锁定,只读查询需要等待。
mysql> set @@session.group_replication_consistency='BEFORE_AND_AFTER' ;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from t1;
hdp2上,会话1中解锁表。
mysql> unlock tables;
Query OK, 0 rows affected (0.00 sec)
hdp2上,会话2中的查询返回最新结果,距查询开始已经等待了62.68秒。
mysql> select * from t1;
+---+
| a |
+---+
| 1 |
| 2 |
+---+
2 rows in set (1 min 2.68 sec)
hdp2上,会话2中执行更新。因为hdp3上的更新被锁定,读写事务需要等待。
mysql> insert into t1 values (3);
hdp3上,会话1中解锁表。
mysql> unlock tables;
Query OK, 0 rows affected (0.00 sec)
表解锁后,hdp2上会话2中执行的插入事务随之执行和提交。
BEFORE可以用于读取和写入事务,AFTER只用于写事务。不同可选值提供了灵活性的一致性级别设置。
- 场景1:读多写少,不读取过期数据的情况下对读取进行负载平衡。选择AFTER。
- 场景2:写多读少,不读取过期数据。选择BEFORE。
- 场景3:希望工作负载中的特定事务始终从组中读取最新数据。选择BEFORE。
- 场景4:复制组主要为只读,希望读写事务一旦提交就应用于任何地方,以便后续读取最新数据,并且不为只读事务产生同步开销。选择AFTER。
- 场景5:复制组主要为只读,希望读写事务始终从组中读取最新数据,并在提交后随处应用,以便后续读取最新数据,并且不为只读事务产生同步开销。选择BEFORE_AND_AFTER。
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错误。
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成员。
为了进一步说明这一点,在具有M1、M2、M3三个成员的组试验验证。三个成员初始的group_replication_consistency均为缺省值EVENTUAL。
(1)在成员M1上执行一个需要长时间提交的事务T1,这里删除一个具有2097152行数据的大表。
set @@session.group_replication_consistency = 'after';
begin;
delete from num;
commit;
(2)上面SQL最后的commit命令执行过程中,在M2上开启一个事务T2。
select count(*) from num for update;
下面是两次执行T2的输出:
mysql> select count(*) from num for update;
+----------+
| count(*) |
+----------+
| 2097152 |
+----------+
1 row in set (1.66 sec)
mysql> select count(*) from num for update;
+----------+
| count(*) |
+----------+
| 0 |
+----------+
1 row in set (30.59 sec)
第一次执行时,T1还没有传递到M2时,T2可以正常查询出删除前的表记录数。第二次执行时,T2开始等待,因为此时T1已经在M2的事务队列中排队,并且排在T2之前。即使T2的一致性级别为EVENTUAL,它也必须等待T1先完成提交。当等待了30秒后,T1提交完毕,T2开始执行,此时返回最新的表记录数0。
三、其它配置
1. 调整恢复
新成员加入复制组时,它会连接到一个合适的捐赠者(donor),从那里获取缺失的历史数据,直到它变为在线状态为止。此过程就是“MySQL 8 复制(七)——组复制基本原理”中详细讨论的分布式恢复。这里侧重如何设置分布式恢复相关的系统变量。
捐赠者是从组中当前在线成员中随机选择的,这样当多个成员进入组时,很大可能不会选择同一服务器作为捐赠者。如果新成员与捐赠者的连接失败,会自动尝试连接到另一个新的候选捐赠者。达到连接重试限制后,恢复过程将终止并显示错误。组复制提供了强大的错误检测机制,能够在整个恢复过程中应对失败。例如,当出现以下问题时,恢复都能检测到错误并尝试切换到新的捐赠者:
- 加入组的服务器已经包含的数据与恢复期间来自所选捐赠者的数据存在冲突。
- 赠者包含新增成员已经清除(purge)GTID的数据。
- 恢复的接收线程或应用线程失败。
(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)设置休眠时间
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. 网络分区
事务复制、组成员身份更改,以及一些使组保持一致的内部消息传递,都需要组成员达成共识。这要求大多数组成员就特定决定达成一致。当大多数组成员丢失时,组复制无法正常进行,因为无法保证多数或法定票数。例如,在一个5台服务器的复制组中,如果其中3台异常宕机,则大无法达到法定票数。事实上,剩下的两个服务器无法判断其它三台服务器是否已崩溃,或者网络分区是否已将这两台服务器单独隔离,因此无法自动重新配置该组。
另一方面,如果服务器自愿退出组,它们会指示组应该重新配置自己,这意味着离开的服务器告诉其它成员它要退出。这种情况下其它成员可以正确地重新配置组,保持成员的数据一致性并重新计算法定票数。在上述5个服务器离开3个的场景中,如果3个离开的服务器一个接一个地通知组它们要离开,那么成员资格能够从5调整到2,同时确保法定票数。法定票数丧失本身是不良计划的结果。无论故障是连续发生,一次性发生,还是零星发生,通常容忍 f 个故障机所需的服务器数量 n 为:n = 2 * f + 1。
下面演示一个网络分区的例子。如下所示一个三个成员的单主模式复制组,成员均为在线状态:
mysql> select member_id, member_host, member_state, member_role from performance_schema.replication_group_members;
+--------------------------------------+-------------+--------------+-------------+
| member_id | member_host | member_state | member_role |
+--------------------------------------+-------------+--------------+-------------+
| 5c93a708-a393-11e9-8343-005056a5497f | hdp4 | ONLINE | SECONDARY |
| 5f045152-a393-11e9-8020-005056a50f77 | hdp3 | ONLINE | SECONDARY |
| 8eed0f5b-6f9b-11e9-94a9-005056a57a4e | hdp2 | ONLINE | PRIMARY |
+--------------------------------------+-------------+--------------+-------------+
3 rows in set (0.00 sec)
现在用脚本同时杀掉hdp3、hdp4上的MySQL实例,脚本内容如下:
[mysql@hdp2~]$more kill_mysqld.sh
#!/bin/bash
ssh hdp3 "ps -ef | grep mysqld | grep -v grep | awk '{print \$2}' | xargs kill -9"
ssh hdp4 "ps -ef | grep mysqld | grep -v grep | awk '{print \$2}' | xargs kill -9"
在hdp2上再次查看成员状态:
mysql> select member_id, member_host, member_state, member_role from performance_schema.replication_group_members;
+--------------------------------------+-------------+--------------+-------------+
| member_id | member_host | member_state | member_role |
+--------------------------------------+-------------+--------------+-------------+
| 5c93a708-a393-11e9-8343-005056a5497f | hdp4 | UNREACHABLE | SECONDARY |
| 5f045152-a393-11e9-8020-005056a50f77 | hdp3 | UNREACHABLE | SECONDARY |
| 8eed0f5b-6f9b-11e9-94a9-005056a57a4e | hdp2 | ONLINE | PRIMARY |
+--------------------------------------+-------------+--------------+-------------+
3 rows in set (0.00 sec)
可以看到hdp2仍为在线状态,但hdp3、hdp4处于不可到达状态。而且,系统无法重新配置自己以改变成员资格,因为已经无法达到法定票数2。此时hdp2虽然在线,但无法执行任何事务,没有外部干预就无法继续提供服务。在这种特殊情况下,需要重置组成员资格以允许组复制继续进行。此时有两种选择,一是重启整个组复制:
-- hdp2上执行
stop group_replication;
set global group_replication_bootstrap_group=on;
start group_replication;
set global group_replication_bootstrap_group=off;
此方法实际上是重新初始化新的一个复制组,该复制组中只有hdp2一个成员(引导成员)。
第二种方法是使用group_replication_force_members变量强制指定组成员。组复制可以通过强制执行特定配置来重置组成员身份列表。本例中hdp2是唯一在线服务器,因此可以选择强制hdp2的成员资格配置。必须强调,使用group_replication_force_members应被视为最后的补救措施,必须非常小心地使用,并且只能用于失败服务器大于等于法定票数的场景。如果误用,可能会创建一个人工的裂脑情景或完全阻止整个系统。
首先要检查hdp2的组通信标识符,在hdp2上执行下面的查询获取此信息。
mysql> select @@group_replication_local_address;
+-----------------------------------+
| @@group_replication_local_address |
+-----------------------------------+
| 172.16.1.125:33061 |
+-----------------------------------+
1 row in set (0.00 sec)
然后设置group_replication_force_members变量值为上面的查询结果,强制组成员为hdp2:
mysql> set global group_replication_force_members="172.16.1.125:33061";
这会通过强制执行不同的配置来取消阻止该组。检查hdp2上的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 |
+--------------------------------------+-------------+--------------+-------------+
| 8eed0f5b-6f9b-11e9-94a9-005056a57a4e | hdp2 | ONLINE | PRIMARY |
+--------------------------------------+-------------+--------------+-------------+
1 row in set (0.00 sec)
强制执行新的成员资格配置时,必须保证所有强制退出该组的服务器确实已停止。在上面描述的场景中,如果hdp3、hdp4不可达(如断网)但是MySQL实例可用,则它们可能已经形成了自己的网络分区(它们是3个中的2个,因此占大多数)。这种情况下强制使用hdp2的组成员列表可能会产生人为的裂脑情况。因此,在强制执行新的成员资格配置前,要确保排除的服务器实例已关闭,然后再继续执行。 使用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 | 172.16.1.125:33061 |
+---------------------------------+--------------------+
1 row in set (0.01 sec)
mysql> stop group_replication;
Query OK, 0 rows affected (13.13 sec)
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;
Query OK, 0 rows affected (0.00 sec)
mysql> start group_replication;
Query OK, 0 rows affected (3.04 sec)
mysql> set global group_replication_bootstrap_group=off;
Query OK, 0 rows affected (0.00 sec)
无论使用哪种方法恢复了组复制,都可以在hdp3、hdp4重新可用后,将它们重新添加到新的复制组。
启动实例:
mysqld_safe --defaults-file=/etc/my.cnf &
启动组复制:
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 |
+--------------------------------------+-------------+--------------+-------------+
| 5c93a708-a393-11e9-8343-005056a5497f | hdp4 | ONLINE | SECONDARY |
| 5f045152-a393-11e9-8020-005056a50f77 | hdp3 | ONLINE | SECONDARY |
| 8eed0f5b-6f9b-11e9-94a9-005056a57a4e | hdp2 | ONLINE | PRIMARY |
+--------------------------------------+-------------+--------------+-------------+
3 rows in set (0.00 sec)