目录
一、组复制性能
1. 概述
组复制的基本保证是,只有在组中的大多数节点接收到事务并且就并发事务的相对顺序达成一致之后,才会提交事务。其对事务的基本处理流程为:
- 在提交之前拦截服务器接收到的客户端事务,并构建事务写入集。该写入集唯一标识了对应事务所修改的数据集合。
- 将写入集发送给组成员,并就并发事务的应用顺序达成一致。
- 如果事务与之前的事务不存在冲突则提交,否则回滚该事务。即相互冲突的事务第一个成功提交,其它回滚。
影响组复制性能的组件主要有三个:组通信层、认证和二进制日志应用程序。
(1)组通信层
组复制实现了一个基于Paxos协议的组通信层,以允许多个服务器在事务提交顺序上达成一致。发送给组通信层的消息在每个组成员中以相同的顺序接收。Paxos协议很复杂,但为了理解组复制中实现变体对性能的影响,需要知道处理一个典型消息的过程:
- 从消息源向每个其它组成员发送带有用户事务的消息。
- 向消息源发送每个接收者的投票信息,当达到多数票后,客户端将消息视为已传递。
- 将消息源所接收的信息发送给其它所有组成员。
由此可见,组复制性能的关键因素是网络吞吐量(可以在网络通道中容纳多少并行消息)和节点到节点的延迟(需要多长时间才能与发送方达成一致)。Paxos的组复制实现包括许多优化,如并行处理多条消息,并将多个消息打包成一个消息,以便在可能的时候发送到每个节点。从用户角度来看到的结果是:
- 在现有网络带宽下,发送者将尽可能多的事务发送到其它节点。节点间的实际带宽是网络带宽除以组成员数量。
- 在准备好提交之后,每个事务将被延迟至少从发送者到接收者的一个中间网络往返时间。
(2)认证
从组通信层接收到的事务消息会被放入一个队列进行认证。在每个节点中运行的算法判断认证结果以决定如何处理事务。若认证通过,事务被认为在面向客户端的服务器(事务发起方)上完成,然后在本地中继日志中排队,以便在其它组成员上执行后续操作。反之如果认证失败,事务将根据需要被丢弃或回滚。
认证是在每个节点接收到正确排序的事务消息后独立完成的,因此不需要进一步的网络同步活动。认证算法在一个专用线程中运行,该线程遵循由组通信层建立的事务顺序,如果事务是远程的,则将其写入中继日志。因为认证每个事务都需要时间,所以该过程可能成为争用点。影响认证吞吐量的主要因素是中继日志带宽,如果用于本地中继日志的存储子系统(即认证事务排队等待应用程序线程的存储子系统)无法跟上写入请求,其事务认证队列开始增长,事务应用开始落后于发起事务的成员。从下面的查询可以获得每个组成员认证队列大小的近似值:
select member_id, count_transactions_in_queue from performance_schema.replication_group_member_stats;
多主组复制中,由于多个组成员都可以执行写事务,gtid_executed集合的复杂性可能成为另一个影响认证性能的因素。当多个成员同时向组写入时,认证数据库上执行的大量gtid_executed集合可能变得难以处理,因为其中可能出现许多间隙,并且无法按照GTID范围进行压缩。这也可以看作是成员节点之间的吞吐量平衡性降低了。
(3)二进制日志应用程序
将事务写入中继日志后,它们就可以像异步或半同步复制一样,由复制的二进制日志应用程序执行。然而,组复制的二进制日志应用程序有一个应该注意的细微差别。一旦组成员对事务进行了认证,认证程序会立即将事务更新行标记为已更改,但实际该事务可能会在中继日志中保留一段时间等待应用。多主模式中,在此期间尝试更改这些行的本地事务都被回滚。
与MySQL传统主从异步复制同理,具有足够线程的MTS(multi-threaded slave)应用程序来处理接收到的工作负载,有助于增加从库的吞吐量。对于异步复制,LOGICAL_CLOCK调度程序使用二进制日志组提交机制来确定哪些事务可以在从库上并行执行。组提交能提高从库并发度。但是,存储子系统速度太快或客户端并发量太小都会使提交组中的事务变少(一个组提交不能包含来自同一客户端的多个事务),从而降低了效率。尤其是在级联复制中,因为复制线程通常比用户线程少的多,这意味着较低级别从库的并行度更低。
使用选项binlog_group_commit_sync_delay或binlog_group_commit_sync_no_delay_count可以增加提交组中的事务数量,以抵消存储速度过快的影响。而基于写集(WRITESET)的依赖跟踪允许更高的并行度,特别是在低并发工作负载下。基于写集的依赖项跟踪可能重新排序来自同一会话的事务,这意味着来自同一会话的两个事务在主从库的提交顺序可能不同。以牺牲一点性能为代价,可以选择强制使用slave_preserve_commit_order选项来保持从库上会话历史与主库保持一致。
组复制考虑了用于验证每个成员中事务的写入集,并根据认证顺序和事务更改的数据行构建依赖项。如果两个事务在其写入集中存在相同的行,则它们不能在从库中并行运行,因此后一个更新这些行的事务将依赖于前一个事务。
关于多线程复制的详细讨论,参见“MySQL 8 复制(六)——拓扑与性能”。
简单说,组复制性能受限于组通信层、认证和二进制日志应用程序三个主要组件:
- 缺乏网络带宽会降低在途消息数量,而高的网络往返时间会相应地增加用户事务延迟。
- 缺乏中继日志存储带宽会延迟认证,因此要注意认证队列的增长。
- 二进制日志应用程序瓶颈除了会增加复制延迟外,还可能增加多主场景中的事务中止。
对于每个组件,组复制提供了若干选项,适当配置这些选项可以从底层计算资源中获取最佳性能。后面小节中将详细介绍这些选项。
MySQL官方描述的组复制性能影响因素,参见http://mysqlhighavailability.com/tuning-mysql-group-replication-for-fun-and-profit/。
2. 测试规划
在讨论具体选项配置前,希望使用一个统一的测试方法,对比不同配置值对复制性能产生的影响,测量的指标包括主、从库的每秒事务数(TPS)和从库的复制延迟时长。测试目的在于对比不同配置情况下组复制的性能,而不是针对测量绝对值进行优化。这里使用的思路是:主库加压,同时从库复制。记录加压前后的GTID,得到需要执行的总事务数。主库的执行时间等于加压时间。等待从库应用完所有事务,记录从库的执行时间。用总事务数除以相应执行时间得到主、从库的TPS。加压使用tpcc-mysql基准测试工具。
(1)测试环境
单主模式下一主两从的组复制基本信息如下:
单主库:172.16.1.125
从库1:172.16.1.126
从库2:172.16.1.127
MySQL版本:8.0.16
操作系统:CentOS Linux release 7.2.1511 (Core)
硬件配置:
三个服务器均为Linux虚拟机,双核双 2.10GHz CPU,8G物理内存,100G硬盘空间,1000MB/s 网卡。MySQL实例基本配置:
[mysqld]
server_id=1125 # 两个从库为1126、1127
gtid_mode=ON
enforce-gtid-consistency=true
innodb_buffer_pool_size=4G
disabled_storage_engines="MyISAM,BLACKHOLE,FEDERATED,ARCHIVE,MEMORY"
log_bin=binlog
log_slave_updates=ON
binlog_format=ROW
binlog_checksum=NONE
master_info_repository=TABLE
relay_log_info_repository=TABLE
plugin-load=group_replication.so
group_replication_group_name="aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"
group_replication_start_on_boot=off
group_replication_local_address= "172.16.1.125:33061" # 两个从库为"172.16.1.126:33061"、"172.16.1.127:33061"
group_replication_group_seeds= "172.16.1.125:33061,172.16.1.126:33061,172.16.1.127:33061"
group_replication_bootstrap_group=off
为保证不同配置的测试环境相同,每次修改配置后重启三个MySQL实例。开始测试前,使用下面的脚本重启组复制:
-- 主
change master to master_user='repl', master_password='123456' for channel 'group_replication_recovery';
set global group_replication_bootstrap_group=on;
start group_replication;
set global group_replication_bootstrap_group=off;
select * from performance_schema.replication_group_members;
-- 从
stop slave;
reset slave all;
change master to master_user='repl', master_password='123456' for channel 'group_replication_recovery';
start group_replication;
(2)测试脚本
每次测试只需要执行同一个脚本文件tpcc_test.sh,内容如下:
[mysql@hdp2~/tpcc-mysql-master]$more tpcc_test.sh
# 初始化tpcc数据
mysql -uwxy -p123456 -h172.16.1.125 < tpcc_test.sql
# 开始GTID
read start_gtid < <(mysql -uwxy -p123456 -e "show variables like 'gtid_executed';" --skip-column-names | awk '{print $2}' | sed "s/\\\n//g")
# 等待从库执行完初始化复制
mysql -uwxy -p123456 -h172.16.1.126 -e "select wait_for_executed_gtid_set('$start_gtid');" > /dev/null &
mysql -uwxy -p123456 -h172.16.1.127 -e "select wait_for_executed_gtid_set('$start_gtid');" > /dev/null
# 开始时间
start_time=`date '+%s'`
# 主库执行压测,10个仓库,32个并发线程,预热1分钟,压测5分钟
tpcc_start -h172.16.1.125 -d tpcc_test -u wxy -p "123456" -w 10 -c 32 -r 60 -l 300 > tpcc_test.log 2>&1
# 结束GTID
read end_gtid < <(mysql -uwxy -p123456 -e "show variables like 'gtid_executed';" --skip-column-names | awk '{print $2}' | sed "s/\\\n//g")
# 等待从库执行完复制
mysql -uwxy -p123456 -h172.16.1.126 -e "select wait_for_executed_gtid_set('$end_gtid'); select unix_timestamp(now());" --skip-column-names | awk 'NR>1' > end_time1 &
mysql -uwxy -p123456 -h172.16.1.127 -e "select wait_for_executed_gtid_set('$end_gtid'); select unix_timestamp(now());" --skip-column-names | awk 'NR>1' > end_time2
# 结束时间
end_time1=`cat end_time1`
end_time2=`cat end_time2`
# 复制执行时长
elapsed1=$(($end_time1 - $start_time))
elapsed2=$(($end_time2 - $start_time))
# 执行的事务数
read start end < <(mysql -uwxy -p123456 -e "select gtid_subtract('$end_gtid','$start_gtid');" --skip-column-names | awk -F: '{print $2}' | awk -F- '{print $1,$2}')
trx=$(($end - $start + 1))
# 计算主库、从库的TPS
Master_TPS=`expr $trx / 360`
Slave1_TPS=`expr $trx / $elapsed1`
Slave2_TPS=`expr $trx / $elapsed2`
# 打印输出
echo "TRX: $trx"
echo "Master TPS: $Master_TPS"
echo "Elapsed1: $elapsed1" "Slave1 TPS: $Slave1_TPS"
echo "Elapsed2: $elapsed2" "Slave2 TPS: $Slave2_TPS"
关于tpcc工具的安装和tpcc_test.sql文件的生成及作用,参见https://wxy0327.blog.csdn.net/article/details/94614149#1.%20%E6%B5%8B%E8%AF%95%E8%A7%84%E5%88%92。每条指令前的注释已经明确表明其用途,这里做两点补充说明:
- MySQL组复制要求表具有主键。tpcc测试库中的history表没有主键,因此需要手工编辑tpcc_test.sql文件,在其建表的create table语句中增加主键定义。这完全是为了满足组复制的要求,因此只要将所有字段一起定义成联合主键即可,即使出现主键重复的错误,也不会影响测试继续进行。况且这里要得到的只是对比结果,而测试结果的绝对值并无多大参考意义。
- 使用MySQL 5.7.5新增的wait_for_executed_gtid_set函数,等待从库应用完事务,返回复制结束时间。本测试中采用一主两从的组复制拓扑,需要并行但分别取得两个从库的结束时间,因此使用 & 符号,将第一条执行wait_for_executed_gtid_set的命令在后台执行。这样可以获得两个从库各自的执行时间,而后面的命令会在它们都执行完后才开始执行,而不论它们谁先执行完。
(3)执行缺省配置测试
获得缺省配置的测试结果,作为后面不同配置的对比基准。测试结果如下:
TRX: 176527
Master TPS: 490
Elapsed1: 708 Slave1 TPS: 249
Elapsed2: 922 Slave2 TPS: 191
缺省配置下,主库360秒内执行了 176527 个事务,TPS为490。从库1执行了708秒,比主库延时了348秒,TPS为249。而从库2更慢,执行了922秒,比主库延时了562秒,TPS仅为191。
3. 消息压缩
当网络带宽成为瓶颈时,消息压缩可以在组通信层提高吞吐量。如图1所示,压缩发生在组通信系统API级别,因此事务负载可以在被发送到组之前被压缩,并且在成员接收事务时进行解压缩。压缩使用LZ4算法,阈值缺省为1000000字节,由group_replication_compression_threshold参数控制,即只压缩大于该参数指定字节数的事务,设置为0将禁用压缩。收到消息后,组成员检查消息是否被压缩。如果需要,该成员在将事务交付给上层之前解压缩该事务。组复制不要求组中的所有服务器都同时启用压缩。
将group_replication_compression_threshold设置为0、100、1024时的测试结果如下表:
group_replication_compression_threshold |
TRX |
Master TPS |
Slave1 Elapsed |
Slave1 TPS |
Slave2 Elapsed |
Slave2 TPS |
0 |
175932 |
488 |
727 |
241 |
914 |
192 |
100 |
175384 |
487 |
715 |
233 |
895 |
195 |
1024 |
176601 |
490 |
743 |
237 |
904 |
195 |
禁用压缩、压缩大于100字节的事务、压缩大于1K字节的事务,与缺省压缩大于1000000字节的事务相比,在我的测试环境中,主、从库的TPS、从库复制延时并无显著差异。结论是此测试环境和负载压力下,网络传输不是瓶颈,因此后面的测试保持group_replication_compression_threshold的缺省值不变。
4. 组通信线程循环
组通信线程(Group Communication Thread,GCT)在加载组复制插件后循环运行。GCT从组插件接收消息,处理仲裁和故障检测相关任务,发送活动消息,并处理服务器和组之间的双向事务传输。当队列中没有消息时,GCT进入睡眠状态。通过在实际进入睡眠状态之前将等待时间设置为稍长,即进行主动等待,在某些情况下是有益的,因为GCT频繁休眠可能浪费可用的CPU时间。要强制GCT执行活动等待,使用group_replication_poll_spin_loops选项。此选项控制GCT在进入睡眠状态前尝试接收消息的次数,也可说是控制通信线程的贪婪程度,缺省值为0。在网速很慢的环境中,适当配置该选项可以降低事务延迟。
这里没有针对group_replication_poll_spin_loops参数进行不同配置的实验,原因有二:一是在我的实验环境下网络不是瓶颈,这点从上面的压缩选项测试中已经得到佐证。二是即便在慢网环境中,按MySQL官方文档的说法,该选项也只是作为一种微调手段。
5. 写入集
在前面测试执行过程中,通过执行查询观察认证队列中的事务数量:
select member_id, count_transactions_in_queue from performance_schema.replication_group_member_stats;
发现count_transactions_in_queue的值始终很小,大于0的时候非常少,而且最大也不过十几。从组复制的流程可知,认证不存在积压情况。但是从库延迟如此之大,网络无瓶颈,认证不积压,剩下就是针对二进制日志应用程序做调整了。从前面的组复制性能概述中,自然想到了MTS和WRITESET,于是修改以下配置选项继续测试。
- 主库启用写入集增加组提交并行度
binlog_transaction_dependency_tracking = WRITESET
transaction_write_set_extraction = XXHASH64
- 从库启用8线程MTS
slave_parallel_type = LOGICAL_CLOCK
slave_parallel_workers = 8
slave_preserve_commit_order=1
测试结果如下:
TRX: 175459
Master TPS: 487
Elapsed1: 478 Slave1 TPS: 367
Elapsed2: 589 Slave2 TPS: 297
可以看到,总事务数和主库的TPS没有基本不变,但两个从库的TPS却增加了50%以上,吞吐量明显得到提升。但即便如此,两个从库的复制延迟还是分别将近2分和4分钟。说明在此压力负载下,从库无力追赶上主库。如果一定要缩减主从之间的复制延迟时间,就要启用下面介绍限流措施。
MySQL官方提供的写入集与多线程下组复制基准测试,参见https://mysqlhighavailability.com/zooming-in-on-group-replication-performance/。
6. 流控
组复制可确保事务仅在组中的大多数成员收到事务,并同意所有并发发送事务之间的相对顺序后提交。如果对组的写入总量不超过组中任何成员的写入容量,则此方法很有效。如果某些成员的写入吞吐量低于其它成员,特别是少于主库,那些成员可能会开始落后于主库。必须强调的是,MySQL组复制并不要求成员保持同步,复制延迟只会增加本地中继日志中挂起的事务数。
成员落后于组会带来一些问题,例如:对这些成员的读取可能会使旧数据外化到应用程序;组中其它成员可能必须保存更多复制上下文,以便能够满足来自慢成员的数据传输请求等等。组复制协议中存在一种被称为流量控制的机制,可以避免快速和慢速成员之间的应用事务存在太大距离。它试图达到的主要目标是保持成员的已执行事务足够接近。
流量控制依赖于两种基本机制:
- 监控服务器,收集所有组成员的吞吐量和队列大小统计数据,对每个成员可以承受的最大写入量进行有根据的猜测。
- 限制服务器,避免写入超出其可用容量限制。
(1)探测和统计
监控机制的工作原理是让每个成员部署一组探针来收集有关其工作队列和吞吐量的信息。成员会定期将该信息传播给组,以便与其它成员共享这些统计数据。此类探针分散在整个插件中,允许建立以下指标:
- 认证队列大小。
- 复制应用程序队列大小。
- 经认证的事务总数。
- 成员中应用的远程事务总数。
- 本地事务总数。
监控数据每秒与组中其它成员共享一次。一旦成员收到其它成员的统计信息,它将计算在上一个监控周期内认证、应用和本地执行的事务数量。
(2)限流
根据在组中所有服务器上收集的统计数据,限制机制决定成员能够提交新事务的速率。流量控制考虑了两个工作队列:认证队列和二进制日志应用程序队列,因此用户可以选择在认证器或复制应用程序执行流量控制,或者两者都执行。使用以下变量控制相应队列的大小:
- group_replication_flow_control_mode:指定是否开启流控,可取值为quota和disable。缺省值为quota,表示启用流控限额,如果指定为disable则禁用流控。
- group_replication_flow_control_certifier_threshold:如果认证队列中的事务数超过该变量值触发认证流控,缺省值为25000,值域为0-2147483647。
- group_replication_flow_control_applier_threshold:如果复制应用队列中的事务数超过该变量值触发应用流控,缺省值为25000,值域为0-2147483647。
如果认证队列或二进制日志应用程序队列大小超过用户定义的阈值,则限制机制将强制执行写入配额。配额将本监控周期的事务数比上一周期减少10%,以允许触发问题的队列减小其大小。为了避免吞吐量的大幅跳跃,增加吞吐量同样仅允许增长上个周期的10%。当前的限制机制不会影响低于配额的事务,但会延迟完成那些超过它的事务,直到监控期结束。
从前面的测试中已知,认证队列始终很小,基本不存在积压,复制延迟主要是由从库的复制应用程序缓慢造成的。因此调整主库的group_replication_flow_control_applier_threshold变量以对应用队列进行流控,缩小主从复制延迟。该变量设置为100、1000、10000时的测试结果如下表:
group_replication_flow_control_applier_threshold |
TRX |
Master TPS |
Slave1 Elapsed |
Slave1 TPS |
Slave2 Elapsed |
Slave2 TPS |
100 |
98469 |
273 |
361 |
272 |
361 |
272 |
1000 |
106442 |
295 |
361 |
294 |
363 |
293 |
10000 |
162866 |
452 |
461 |
353 |
556 |
292 |
从表中看到:
- 当group_replication_flow_control_applier_threshold=100时,两个从库基本没有延迟,但整个组的TPS只有273左右。
- 当group_replication_flow_control_applier_threshold=1000时,两个从库的延迟依然很小,整个组的TPS为294,提升了不到8%。
- 当group_replication_flow_control_applier_threshold=10000时,从库1、从库2分别延迟了100秒和200秒,主库、从库1、从库2的TPS分别是452、353、292。
在本测试环境的压力下,解读表中的结果可得以下结论:
- 组复制的吞吐量和主从复制延时无法兼得。
- 要保持主从复制低延时,组复制的吞吐量取决去最小TPS的节点,符合木桶原理。
- group_replication_flow_control_applier_threshold大于某一阈值时,主从延迟将变得非常明显。
7. 其它配置
(1)消息分段
当组复制组成员之间发送异常大的消息时,可能会导致某些组成员报告为失败并从组中移除。这是因为组通信引擎使用的单个线程被处理消息占用时间太长,使得某些组成员将接收状态报告为失败。从MySQL 8.0.16开始,默认情况下,大消息会自动拆分为单独发送的消息片段,并由接收方重新组装为原消息。
系统变量group_replication_communication_max_message_size指定组复制通信的最大消息大小,超过该大小的消息将被分段。缺省值为10485760字节(10MB),允许的最大值与slave_max_allowed_packet系统变量的最大值相同,即1073741824字节(1GB)。group_replication_communication_max_message_size的值不能大于slave_max_allowed_packet的值,因为复制应用程序线程无法处理大于slave_max_allowed_packet的消息片段。要关闭消息分段,可将group_replication_communication_max_message_size设置为0。
当所有组成员收到并重新组装消息的所有片段时,认为消息传递已完成。分段消息包括其标头中的信息,这些信息在消息传输期间加入,以使成员恢复之前发送的早期消息片段。如果组成员无法恢复消息,则将其从组中移除。为了使复制组使用消息分段,所有组成员必须为MySQL 8.0.16或更高版本,并且组使用的复制通信协议版本也必须设置为8.0.16或更高版本。
如果复制组因某些成员不支持而无法使用消息分段时,系统变量group_replication_transaction_size_limit可用于限制组接收的最大事务。在MySQL 8.0中,缺省值为150000000(约143MB),大于该值的事务将被回滚。
(2)XCom缓存
作为组复制协议的一部分,用于组复制的通信引擎(XCom,Paxos变体)包括用于组成员之间交换的消息及其元数据的高速缓存,用于与其它组成员通信超时后重新连接时进行恢复。从MySQL 8.0.16开始,可以使用group_replication_message_cache_size系统变量设置XCom的消息缓存大小,缺省值和最小值为1GB。如果达到缓存大小限制,XCom将删除已交付的最旧条目。
如果重新连接成员时需要恢复消息,但该消息已从消息高速缓存中删除,则该成员无法重新连接。当使用了group_replication_member_expel_timeout系统变量(在MySQL 8.0.13中引入)指定可疑成员从组中移除之前的延迟时间,更有可能发生这种情况。组复制通信系统(GCS)通过警告消息告知用户何时从消息缓存中删除当前无法访问的成员恢复时可能需要的消息。此警告消息记录在所有活动组成员上,表明高速缓存大小可能不足以支持移除成员之前的超时时间。在这种情况下,应参考group_replication_member_expel_timeout系统变量指定的时间段内的预期消息量来增加高速缓存大小,以便高速缓存包含成员成功返回所需的所有错过的消息。
可以使用以下语句查询性能模式表memory_summary_global_by_event_name:
select * from performance_schema.memory_summary_global_by_event_name where event_name like 'memory/group_rpl/gcs_xcom::xcom_cache'\G
这将返回消息缓存的内存使用情况统计信息,包括当前缓存条目数和缓存的当前大小。如果减小缓存大小限制,XCom将删除已确定并交付的最旧条目,直到当前大小低于限制。在此删除过程正在进行时,XCom可能会暂时超出缓存大小限制。
(3)故障检测
组复制的故障检测机制旨在识别不再与该组通信的组成员,并在它们可能发生故障时将其移除。通常,所有小组成员定期与所有其它小组成员交换消息。如果组成员在5秒内没有收到来自特定成员的任何消息,会引起对该成员的怀疑。当怀疑超时时,假定被怀疑的成员失败,并将其移出该组。被移除成员从其它成员看到的成员列表中删除,但它不知道自己已被移出该组,因此它将自己视为在线而其它成员无法访问。如果该成员实际上没有失败(例如网络闪断)并且能够恢复与其它成员的通信,则它会收到已被从组中移除的信息。默认情况下,如果怀疑某个成员失败,则会发生以下行为:
- 当怀疑被创建时,它会立即超时(group_replication_member_expel_timeout缺省值为0),因此成员只要被怀疑就会被立即移除。该成员可能会在超时后存活几秒钟,因为对过期怀疑的检查会定期进行。
- 如果被驱逐的成员恢复通信并意识到被移除,它不会尝试重新加入该组。
- 当被驱逐的成员接受其移除时,它会切换到超级只读模式(super_read_only)并等待管理员干预。
这些缺省设置优先考虑组能够正确处理请求。但在较慢或不稳定的网络情况下,可能经常需要管理员干预来修复被移除的成员。可以使用组复制配置选项永久或临时更改这些行为:
- 可以使用可从MySQL 8.0.13新增的group_replication_member_expel_timeout系统变量,在产生怀疑和移除可疑成员之间留出更多时间,最大为3600秒。处于此状态的可疑成员不可访问,但不会从组的成员列表中删除。创建怀疑之前的5秒检测周期是不可配置的。
- 可以使用可从MySQL 8.0.16新增的group_replication_autorejoin_tries系统变量指定成员被移除后尝试重新加入该组的次数。缺省值0表示成员不尝试重新加入组,并继续执行由group_replication_exit_state_action系统变量指定的操作(缺省为READ_ONLY)。每次重试间隔5分钟,最多可以进行2016次重试。在自动重新加入过程中,被移除成员保持超级只读模式,并在其复制组视图上显示ERROR状态。
- 可以使用group_replication_exit_state_action系统变量选择未能重新加入(或不尝试)的被移除成员的行为,可选值为ABORT_SERVER或READ_ONLY,缺省值为READ_ONLY。ABORT_SERVER表示关闭MySQL服务器,READ_ONLY表示切换为超级只读模式。与自动重新加入过程一样,如果成员进入超级只读模式,则读取过时旧数据的可能性会增加。指示成员自行关闭意味着MySQL Server实例不可用,必须重新启动。无论设置了什么退出操作,都需要管理员干预,因为已经耗尽其自动重新加入尝试(或从未有过)并且已被移出组的成员不允许在不重新启动组复制的情况下重新加入。
- 如果希望避免由于不适当的配置产生裂脑情况,可以使用系统变量group_replication_unreachable_majority_timeout设置成员在与大多数组成员失去联系后等待的秒数。在此之后,将回滚该成员和少数组中其它成员处理的所有待处理事务,并且该组中的服务器将变为ERROR状态,然后执行group_replication_exit_state_action指定的退出操作。
8. 主从、半同步、组复制性能对比测试
现在将关注点从组复制性能本身,转移到主从、半同步、组复制三种MySQL复制的横向性能对比上。我们最为关心的是不同复制方式对主库TPS的影响。测试环境不变,不同的只是更改相应的复制配置。三种复制均为一主两从,具体测试步骤描述如下。(1)MySQL实例基本配置
[mysqld]
server_id=1125 # 两个从库为1126、1127
gtid_mode=ON
enforce-gtid-consistency=true
innodb_buffer_pool_size=4G
disabled_storage_engines="MyISAM,BLACKHOLE,FEDERATED,ARCHIVE,MEMORY"
log_bin=binlog
log_slave_updates=ON
binlog_format=ROW
binlog_checksum=NONE
master_info_repository=TABLE
relay_log_info_repository=TABLE
# 主库启用写集
binlog_transaction_dependency_tracking = WRITESET
transaction_write_set_extraction = XXHASH64
# 从库使用8线程MTS
slave_parallel_type = LOGICAL_CLOCK
slave_parallel_workers = 8
slave_preserve_commit_order=1
(2)启动传统主从复制
-- 在从库执行
change master to
master_host = '172.16.1.125',
master_port = 3306,
master_user = 'repl',
master_password = '123456',
master_auto_position = 1;
start slave;
(3)执行主从复制测试
./tpcc_test.sh
(4)配置半同步复制
# 主库增加配置:
plugin-load="rpl_semi_sync_master=semisync_master.so"
rpl_semi_sync_master_enabled=1
# 从库增加配置:
plugin-load="rpl_semi_sync_slave=semisync_slave.so"
rpl_semi_sync_slave_enabled=1
然后重启三个MySQL实例。
(5)执行半同步复制测试
./tpcc_test.sh
(6)组复制配置
去掉上一步的半同步配置,并增加组复制配置:
plugin-load=group_replication.so
group_replication_group_name="aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"
group_replication_start_on_boot=off
group_replication_local_address= "172.16.1.125:33061" # 两个从库为"172.16.1.126:33061"、"172.16.1.127:33061"
group_replication_group_seeds= "172.16.1.125:33061,172.16.1.126:33061,172.16.1.127:33061"
group_replication_bootstrap_group=off
然后重启三个MySQL实例。之后:
-- 主库执行
reset master;
reset slave all;
change master to master_user='repl', master_password='123456' for channel 'group_replication_recovery';
set global group_replication_bootstrap_group=on;
start group_replication;
set global group_replication_bootstrap_group=off;
-- 从库执行
reset master;
stop slave;
reset slave all;
change master to master_user='repl', master_password='123456' for channel 'group_replication_recovery';
start group_replication;
(7)执行组复制测试
./tpcc_test.sh
三种复制测试结果如下表所示。
复制方式 |
TRX |
Master TPS |
Slave1 Elapsed |
Slave1 TPS |
Slave2 Elapsed |
Slave2 TPS |
主从 |
196808 |
546 |
360 |
546 |
435 |
452 |
半同步 |
189303 |
525 |
361 |
524 |
414 |
457 |
组复制 |
166032 |
461 |
462 |
359 |
561 |
295 |
从测试结果可以看到,相同条件下:
- 半同步复制主库的TPS是传统主从复制的96%,组复制主库的TPS是传统主从复制的84%。
- 在复制延迟上,从库1的传统主从和半同步复制基本无延迟,从库2的传统主从延迟还大于半同步复制;而两个从库的组复制延迟都很大(100秒和200秒)。
- 传统主从复制与半同步复制没有提供缩小复制延迟的机制;组复制可以通过流控机制,减少从库的复制延迟,代价是将组复制整体的吞吐量拉低到组中吞吐量最差节点的水平。
由于组复制会带来整体吞吐量的下降,大多数情况下,使用传统主从异步复制即可。如果在读写分离场景下要求读取一致性,可以考虑半同步复制。与异步复制相比,半同步复制的吞吐量并没有显著衰减。只有在首要需求为多主可写、自动处理事务冲突、主库失败自动转移等情况下,再考虑使用组复制。
MySQL官方提供的组复制性能基准测试,参见http://mysqlhighavailability.com/an-overview-of-the-group-replication-performance/。
二、组复制要求与限制
目前的MySQL组复制对于存储引擎、网络带宽、表设计,以及服务器实例的配置还存在诸多要求与限制,尤其是多主模式,使用时更要格外注意。下面是MySQL官方文档中指出的已知问题,在实际应用中使用组复制前,有必要了解它们以帮助做出正确的选择。在采坑前做到未雨绸缪总是有益的。
1. 组复制要求
(1)基础架构
- 数据必须存储在InnoDB事务存储引擎中。事务以乐观方式执行,然后在提交时检查冲突。如果存在冲突,为了保持整个组的一致性,将回滚一些事务,因此需要事务存储引擎。此外,InnoDB还提供了一些附加功能,可以在与组复制一起操作时更好地管理和处理冲突。使用其它存储引擎(包括临时MEMORY存储引擎)可能会导致组复制出错。可以通过在组成员上设置disabled_storage_engines系统变量来阻止使用其它存储引擎,例如:
disabled_storage_engines="MyISAM,BLACKHOLE,FEDERATED,ARCHIVE,MEMORY"
- 组复制的每个表必须具有主键,或者具有等效的非空唯一键。它们作为表中每一行的唯一标识符是必需的,这使得系统能够通过准确识别每个事务已修改的行来确定哪些事务存在冲突。
- 网络性能会影响组的性能,网络延迟和网络带宽都会影响组复制性能及稳定性。因此组复制中的MySQL服务器实例应该部署在彼此非常接近的集群环境中,使得所有组成员之间始终保持双向通信。如果阻止服务器实例的收发消息(例如通过防火墙限制),则该成员无法在组中运行,并且组成员(包括有问题的成员)可能无法报告受影响的服务器实例的正确成员状态。从MySQL 8.0.14开始,可以使用IPv4或IPv6网络基础结构,或两者的混合,用于远程组复制服务器之间的TCP通信。
(2)服务器实例配置
必须在作为组成员的服务器实例上配置以下选项:
- 设置--log-bin [= log_file_name] 激活二进制日志,MySQL 8中缺省启用此选项。与其它MySQL复制方式一样,组复制需要复制二进制日志内容,因此需要打开二进制日志才能运行。
- 设置--log-slave-updates 记录从库更新。服务器需要记录通过复制应用程序应用的二进制日志。组中的服务器需要记录它们收到的所有事务并从组中应用。这是必需的,因为分布式恢复依赖组中成员的二进制日志进行。因此,每个事务的副本都需要存在于每个服务器上,即使对于那些未在服务器本身上启动的事务也是如此。MySQL 8中缺省启用此选项。
- 设置--binlog-format = row 将二进制日志设为行格式。组复制依赖于基于行的复制格式,以在组成员之间一致地传播更改。它依赖于基于行的基础结构来提取必要信息,以检测在组中不同服务器并发执行的事务之间的冲突。
- 设置--binlog-checksum = NONE 关闭二进制日志校验。由于复制事件校验和的设计限制,组复制无法使用它们,因此必须禁用。
- 设置--gtid-mode = ON 开启全局事务标识符。组复制使用全局事务标识符来准确跟踪在每个服务器实例上已提交的事务,从而能够推断哪些服务器执行的事务可能与其它地方已提交的事务冲突。换句话说,显式事务标识符是框架的基本部分,以便能够确定哪些事务可能发生冲突。
- 设置--master-info-repository = TABLE和--relay-log-info-repository = TABLE,将复制信息资料库存储到表中。复制应用程序需要将主库信息和中继日志元数据写入mysql.slave_master_info和mysql.slave_relay_log_info系统表。这可确保组复制插件具有一致的可复制性和复制元数据的事务管理。从MySQL 8.0.2开始,这些选项缺省值为TABLE,而从MySQL 8.0.3开始,不推荐使用FILE设置。
- 设置--transaction-write-set-extraction = XXHASH64,以便在以将数据行记录到二进制日志时,服务器也会收集写入集。写入集基于每行的主键,唯一标识已更改的行,用于检测事务冲突。MySQL 8中缺省启用此选项。
- 推荐设置slave_parallel_workers为大于零的值,启用组成员上的多线程复制应用程序,最多可以指定1024个并行复制应用程序线程。设置slave_preserve_commit_order = 1,确保并行事务的最终提交与原始事务的顺序相同,这是组复制所需的,它依赖于所有组成员以相同顺序接收和应用已提交事务,以保证并行事务的数据一致性。slave_preserved_commit_order = 1需要设置slave_parallel_type = LOGICAL_CLOCK,该策略指定用于决定允许哪些事务在从库上并行执行的策略。设置slave_parallel_workers = 0会禁用并行复制,并为从库提供单个应用程序线程,而不是协调器线程。使用该设置,slave_parallel_type和slave_preserve_commit_order选项无效并被忽略。
2. 组复制限制
组复制存在以下已知限制:
- 使用MINIMAL选项(--upgrade = MINIMAL)的MySQL服务器升级后,无法启动组复制,该选项不会升级复制内部所依赖的系统表。
- 认证过程没有考虑间隙锁。除非应用程序中依赖REPEATABLE READ语义,否则MySQL建议将READ COMMITTED隔离级别与组复制一起使用。InnoDB在READ COMMITTED中不使用间隙锁,它将InnoDB中的本地冲突检测与组复制执行的分布式冲突检测统一化。
- 认证过程不考虑表锁(LOCK TABLES和UNLOCK TABLES)或命名锁(GET_LOCK)。
- 复制不支持SERIALIZABLE隔离级别。将事务隔离级别设置为SERIALIZABLE会将使组拒绝提交事务。
- 使用多主模式时,不支持针对同一对象但在不同服务器上执行的并发数据定义语句(DDL)和数据操作语句(DML)。
- 多主模式不支持具有多级外键依赖关系的表,特别是具有已定义CASCADING外键约束的表。MySQL建议在多主模式组复制中设置group_replication_enforce_update_everywhere_checks = ON,以避免未检测到的冲突。
- 当组复制以多主模式运行时,SELECT .. FOR UPDATE语句可能导致死锁。
- 全局复制过滤器不能在为组复制配置的MySQL服务器实例上使用。
- MySQL服务器自MySQL 8.0.16起可以支持TLSv1.3协议,并且需要使用OpenSSL 1.1.1或更高版本编译MySQL。组复制当前不支持TLSv1.3,如果使用此支持编译服务器,则在组通信引擎中明确禁用它。
如果单个事务太大,以至于在5秒钟内无法通过网络在组成员之间复制消息,则可能会怀疑成员失败,然后被移出组。由于内存分配问题,大型事务也可能导致系统速度变慢。要避免这些问题,使用以下缓解措施:
- 尽可能尝试限制事务规模。例如,将与LOAD DATA一起使用的文件拆分为较小的块。
- 使用系统变量group_replication_transaction_size_limit指定组接收的最大事务大小。超过此大小的事务将回滚,不会发送到组。在MySQL 8.0中,此系统变量缺省值为150000000字节(大约143 MB)。
- 从MySQL 8.0.13开始,可以使用系统变量group_replication_member_expel_timeout来允许在怀疑失败的成员在被移出之前有更多的时间。可以在最初的5秒检测期后最多延长一个小时。
- 从MySQL 8.0.16开始,大型消息会自动分段,这意味着大型消息不会触发引发怀疑的5秒检测周期,除非此时存在其它网络问题。为了使复制组使用分段,所有组成员必须处于MySQL 8.0.16或更高版本,并且组使用的组复制通信协议版本必须允许分段。如果MySQL版本不支持消息分段,可以使用系统变量group_replication_communication_max_message_size来调整最大消息大小,缺省值为10485760字节(10 MB),或通过指定零值来关闭分段。