1. 半同步复制
默认情况下 , MySQL的复制是异步的 , master将新生成的binlog发送给各slave后 , 无需等待slave的ack回复(slave将接收到的binlog写进relay log后才会回复ack) , 直接就认为这次DDL/DML成功了。
半同步复制(semi-synchronous replication)是指master在将新生成的binlog发送给各slave时 , 只需等待一个(默认)slave返回的ack信息就返回成功。

MySQL 5.7对半同步复制作了大改进 , 新增了一个master线程。在MySQL 5.7以前 , master上的binlog dump线程负责两件事 : dump日志给slave的io_thread;接收来自slave的ack消息。它们是串行方式工作的。在MySQL 5.7中 , 新增了一个专门负责接受ack消息的线程ack collector thread。这样master上有两个线程独立工作 , 可以同时发送binlog到slave和接收slave的ack。
还新增了几个变量 , 其中最重要的是 rpl_semi_sync_master_wait_point , 它使得MySQL半同步复制有两种工作模型。解释如下。
2. 半同步复制的两种类型
从MySQL 5.7.2开始 , MySQL支持两种类型的半同步复制。这两种类型由变量 rpl_semi_sync_master_wait_point (MySQL 5.7.2之前没有该变量)控制 , 它有两种值 : AFTER_SYNC和AFTER_COMMIT。在MySQL 5.7.2之后 , 默认值为AFTER_SYNC , 在此版本之前 , 等价的类型为AFTER_COMMIT。
这个变量控制的是master何时提交、何时接收ack以及何时回复成功信息给客户端的时间点。
AFTER_SYNC模式 :master将新的事务写进binlog(buffer) , 然后发送给slave, 再sync到自己的binlog file(disk)。之后才允许接收slave的ack回复 , 接收到ack之后才会提交事务 , 并返回成功信息给客户端。AFTER_COMMIT模式 :master将新的事务写进binlog(buffer) , 然后发送给slave, 再sync到自己的binlog file(disk) , 然后直接提交事务。之后才允许接收slave的ack回复 , 然后再返回成功信息给客户端。
画图理解就很清晰。(前提 : 已经设置了sync_binlog=1 , 否则binlog刷盘时间由操作系统决定)


再来分析下这两种模式的优缺点。
AFTER_SYNC:对于所有客户端来说 , 它们看到的数据是一样的 , 因为它们看到的数据都是在接收到
slave的ack后提交后的数据。这种模式下 , 如果
master突然故障 , 不会丢失数据 , 因为所有成功的事务都已经写进slave的relay log中了 ,slave的数据是最新的。
AFTER_COMMIT:不同客户端看到的数据可能是不一样的。对于发起事务请求的那个客户端 , 它只有在
master提交事务且收到slave的ack后才能看到提交的数据。但对于那些非本次事务的请求客户端 , 它们在master提交后就能看到提交后的数据 , 这时候master可能还没收到slave的ack。如果
master收到ack回复前 ,slave和master都故障了 , 那么将丢失这个事务中的数据。
在MySQL 5.7.2之前 , 等价的模式是 AFTER_COMMIT , 在此版本之后 , 默认的模式为 AFTER_SYNC , 该模式能最大程度地保证数据安全性 , 且性能上并不比 AFTER_COMMIT 差。
3.半同步复制插件介绍
MySQL的半同步是通过加载google为MySQL提供的半同步插件 semisync_master.so 和 semisync_slave.so 来实现的。其中前者是master上需要安装的插件 , 后者是slave上需要安装的插件。
MySQL的插件位置默认存放在$basedir/lib/plugin目录下。例如 , yum安装的mysql-server , 插件目录为/usr/lib64/mysql/plugin。
[root@master ~]# find / -type f -name "semisync*"
/usr/lib64/mysql/plugin/semisync_master.so
/usr/lib64/mysql/plugin/semisync_replica.so
/usr/lib64/mysql/plugin/semisync_slave.so
/usr/lib64/mysql/plugin/semisync_source.so
/usr/lib64/mysql/plugin/debug/semisync_master.so
/usr/lib64/mysql/plugin/debug/semisync_replica.so
/usr/lib64/mysql/plugin/debug/semisync_slave.so
/usr/lib64/mysql/plugin/debug/semisync_source.so因为要加载插件 , 所以应该保证需要加载插件的MySQL的全局变量 have_dynamic_loading已经设置为YES(默认值就是YES) , 否则无法动态加载插件。
mysql> select @@global.have_dynamic_loading;
+-------------------------------+
| @@global.have_dynamic_loading |
+-------------------------------+
| YES |
+-------------------------------+
1 row in set (0.00 sec)
3.1 MySQL中安装插件的方式
安装插件有两种方式 : 1.在mysql环境中使用INSTALL PLUGIN语句临时安装;2. 在配置文件中配置永久生效。
在Mariadb 10.3.3和更高版本中 , 半同步复制功能内置于MariaDB服务器中 , 不再由插件提供。这意味着这些版本不支持安装插件。
INSTALL安装插件的语法为 :
Syntax:
INSTALL PLUGIN plugin_name SONAME 'shared_library_name'
UNINSTALL PLUGIN plugin_name例如 , 使用INSTALL语句在master上安装 semisync_master.so 插件。
mysql> install plugin rpl_semi_sync_master soname 'semisync_master.so';配置文件中加载插件的方式为 :
[mysqld]
plugin-load='plugin_name=shared_library_name'例如 , 配置文件中加载semisync_master.so插件。
[mysqld]
plugin-load="rpl_semi_sync_master=sermisync_master.so"如果需要加载多个插件 , 则插件之间使用分号分隔。例如 , 在本节的slave1既是slave , 又是master , 需要同时安装两个半同步插件。
[mysqld]
plugin-load="rpl_semi_sync_master=semisync_master.so;rpl_sync_slave=semisync_slave.so"安装插件后 , 应该使用show plugins来查看插件是否真的激活。
mysql> show plugins;
+----------------------+--------+-------------+--------------------+---------+
| Name | Status | Type | Library | License |
+----------------------+--------+-------------+--------------------+---------+
......
| rpl_semi_sync_master | ACTIVE | REPLICATION | semisync_master.so | GPL |
+----------------------+--------+-------------+--------------------+---------+或者查看information_schema.plugins表获取更详细的信息。
mysql> select * from information_schema.plugins where plugin_name like "%semi%"\G
*************************** 1. row ***************************
PLUGIN_NAME: rpl_semi_sync_master
PLUGIN_VERSION: 1.0
PLUGIN_STATUS: ACTIVE
PLUGIN_TYPE: REPLICATION
PLUGIN_TYPE_VERSION: 4.0
PLUGIN_LIBRARY: semisync_master.so
PLUGIN_LIBRARY_VERSION: 1.11
PLUGIN_AUTHOR: Oracle Corporation
PLUGIN_DESCRIPTION: Source-side semi-synchronous replication.
PLUGIN_LICENSE: GPL
LOAD_OPTION: ON
1 row in set (0.01 sec)插件装载完成后 , 半同步功能还未开启 , 需要手动设置它们启动 , 或者写入配置文件永久生效。
# 开启master的半同步
mysql> set @@global.rpl_semi_sync_master_enabled=1;
# 开启slave半同步
mysql> set @@globale.rpl_semi_sync_slave_enabled=1;或者配合插件加载选项一起写进配置文件永久开启半同步功能。
[mysqld]
rpl_semi_sync_master_enabled=1
[mysqld]
rpl_semi_sync_slave_enabled=13.2 半同步插件相关的变量
安装了 semisync_master.so 和 semisync_slave.so后 , 这两个插件分别提供了几个变量。
mysql> show global variables like "%semi%";
+-------------------------------------------+------------+
| Variable_name | Value |
+-------------------------------------------+------------+
| rpl_semi_sync_master_enabled | ON |
| rpl_semi_sync_master_timeout | 10000 |
| rpl_semi_sync_master_trace_level | 32 |
| rpl_semi_sync_master_wait_for_slave_count | 1 |
| rpl_semi_sync_master_wait_no_slave | ON |
| rpl_semi_sync_master_wait_point | AFTER_SYNC |
| rpl_semi_sync_slave_enabled | ON |
| rpl_semi_sync_slave_trace_level | 32 |
+-------------------------------------------+------------+
8 rows in set (0.00 sec)下面还多给了两个和半同步相关的状态变量的解释 , 可以通过show status like %semi%;查看它们。
master相关的变量 :Rpl_semi_sync_master_clients: (状态变量)master所拥有的半同步复制slave的主机数量。
Rpl_semi_sync_master_status: (状态变量)master当前是否以半同步复制状态工作(ON) ,OFF表示降级为了异步复制。rpl_semi_sync_master_enabled:master上是否启用了半同步复制。rpl_semi_sync_master_timeout: 等待slave的ack回复的超时时间 , 默认为10秒。rpl_semi_sync_master_trace_level: 半同步复制时master的调试级别。rpl_semi_sync_master_wait_for_slave_count:master在超时时间内需要收到多少个ack回复才认为此次DML成功 , 否则就降级为异步复制。该变量在MySQL5.7.3才提供 , 在此之前的版本都默认为收到1个ack则确认成功 , 且不可更改。MySQL 5.7.3之后该变量的默认值也是1。rpl_semi_sync_master_wait_no_slave: 值为ON(默认)或者OFF。ON表示master在超时时间内如果未收到指定数量的ack消息 , 则会一直等待下去直到收满ack, 即一直采用半同步复制方式 , 不会降级;OFF表示如果在超时时间内未收到指定数量的ack, 则超时时间一过立即降级为异步复制。更官方的解释是 : 当设置为
ON时 , 即使状态变量Rpl_semi_sync_master_clients中的值小于rpl_semi_sync_master_wait_for_slave_count,Rpl_semi_sync_master_status依旧为ON;当设置为OFF时 , 如果clients的值小于count的值 , 则Rpl_semi_sync_master_status立即变为OFF。通俗地讲 , 就是在超时时间内 , 如果slave宕机的数量超过了应该要收到的ack数量 ,master是否降级为异步复制。该变量在
MySQL 5.7.3之前似乎没有效果 , 因为默认设置为ON时 , 超时时间内收不到任何ack时仍然会降级为异步复制。rpl_semi_sync_master_wait_point: 控制master上commit、接收ack、返回消息给客户端的时间点。值为AFTER_SYNC和AFTER_COMMIT, 该选项是MySQL5.7.2后引入的 , 默认值为AFTER_SYNC, 在此版本之前 , 等价于使用了AFTER_COMMIT模式。关于这两种模式 , 见前文对两种半同步类型的分析。
slave相关的变量 :rpl_semi_sync_slave_enabled:slave是否开启半同步复制。rpl_semi_sync_slave_trace_level:slave的调试级别。
4.配置半同步复制
需要注意的是,"半同步"是同步/异步类型的一种情况,既可以实现半同步的传统复制,也可以实现半同步的GTID复制。其实半同步复制是基于异步复制的,它是在异步复制的基础上通过加载半同步插件的形式来实现半同步性的。
本文实现半同步传统复制。如果要实现半同步GTID复制,也只是在gtid复制的基础上改改配置文件而已。

环境
因为都是全新的实例环境,所以无需考虑基准数据和binlog坐标的问题。如果开始测试前,已经在master上做了一些操作,或者创建了一些新数据,那么请将master上的数据恢复到各slave上,并获取master binlog的坐标,
半同步复制配置文件
Master节点配置文件[mysqld] datadir=/var/lib/mysql socket=/var/lib/mysql/mysql.sock log-error=/var/log/mysqld.log pid-file=/var/run/mysqld/mysqld.pid log-bin=/var/lib/mysql/master-bin sync-binlog=1 server-id=30 plugin-load="rpl_semi_sync_master=semisync_master.so" rpl_semi_sync_master_enabled=1slave1的配置文件,注意slave1同时还充当着slave2和slave3的master的角色。在mysql8里面含master的参数已经全部替换为source,slave也已经更换为replica[mysqld] datadir=/var/lib/mysql socket=/var/lib/mysql/mysql.sock log-error=/var/log/mysqld.log pid-file=/var/run/mysqld/mysqld.pid log-bin=/var/lib/mysql/master-bin sync-binlog=1 server-id=35 relay-log=/var/lib/mysql/slave-bin log-replica-updates plugin-load="rpl_semi_sync_source=semisync_source.so;rpl_semi_sync_replica=semisync_replica.so" rpl_semi_sync_replica_enabled=1 rpl_semi_sync_source_enabled=1以下是
slave2和slave3的配置文件,它们配置文件除了server-id外都一致。[mysqld] datadir=/var/lib/mysql socket=/var/lib/mysql/mysql.sock log-error=/var/log/mysqld.log pid-file=/var/run/mysqld/mysqld.pid relay-log=/var/lib/mysql/slave-bin server-id=36 #slave3 的id为37 plugin-load="rpl_semi_sync_slave=semisync_slave.so" rpl_semi_sync_slave_enabled=1 read-only=on配置主从复制
创建专门用于复制的用户
mysql> create user repl@'192.168.71.%' identified by 'P@ssword1!'; Query OK, 0 rows affected (10.04 sec) mysql> grant replication slave on *.* to repl@'192.168.71.%'; Query OK, 0 rows affected (0.02 sec)因为
master和所有的slave都是全新的实例,所以slave上指定的binlog坐标可以从任意位置开始。不过刚才master上创建了一个用户,也会写binlog,所以建议还是从master的第一个binlog的position=4开始。 以下是slave1上的change master to参数change master to master_host='192.168.71.30', master_port=3306, master_user='repl', master_password='P@ssword1!', master_log_file='master-bin.000001', master_log_pos=4, get_source_public_key=1;以下是
slave2和slave3的change master to参数:change master to master_host='192.168.71.35', master_port=3306, master_user='repl', master_password='P@ssword1!', master_log_file='master-bin.000001', master_log_pos=4, get_source_public_key=1;启动复制进程。启动各
slave上的两个SQL线程。mysql> start slave; Query OK, 0 rows affected, 1 warning (0.03 sec)
5. 半同步复制的状态信息
例如以下是开启了半同步复制后的master上的semisync相关变量。
mysql> show global variables like "%semi%";
+-------------------------------------------+------------+
| Variable_name | Value |
+-------------------------------------------+------------+
| rpl_semi_sync_master_enabled | ON |
| rpl_semi_sync_master_timeout | 10000 |
| rpl_semi_sync_master_trace_level | 32 |
| rpl_semi_sync_master_wait_for_slave_count | 1 |
| rpl_semi_sync_master_wait_no_slave | ON |
| rpl_semi_sync_master_wait_point | AFTER_SYNC |
+-------------------------------------------+------------+
6 rows in set (0.02 sec)关于半同步复制,还有几个状态变量很重要。
例如,以下是master上关于semi_sync的状态变量信息。
mysql> show status like "%semi%";
+--------------------------------------------+-------+
| Variable_name | Value |
+--------------------------------------------+-------+
| Rpl_semi_sync_master_clients | 1 | # 注意行1
| Rpl_semi_sync_master_net_avg_wait_time | 0 |
| Rpl_semi_sync_master_net_wait_time | 0 |
| Rpl_semi_sync_master_net_waits | 1 |
| Rpl_semi_sync_master_no_times | 1 |
| Rpl_semi_sync_master_no_tx | 2 |
| Rpl_semi_sync_master_status | ON | # 注意行2
| Rpl_semi_sync_master_timefunc_failures | 0 |
| Rpl_semi_sync_master_tx_avg_wait_time | 0 |
| Rpl_semi_sync_master_tx_wait_time | 0 |
| Rpl_semi_sync_master_tx_waits | 0 |
| Rpl_semi_sync_master_wait_pos_backtraverse | 0 |
| Rpl_semi_sync_master_wait_sessions | 0 |
| Rpl_semi_sync_master_yes_tx | 0 |
+--------------------------------------------+-------+
14 rows in set (0.01 sec)除了上面标注"注意行"的变量,其他都无需关注,而且其中有一些是废弃了的状态变量。
Rpl_semi_sync_master_clients是该master所连接到的slave数量。
Rpl_semi_sync_master_status是该master的半同步复制功能是否开启。在有些时候半同步复制会降级为异步复制,这时它的值为OFF。
以下是slave1上关于semi_sync的状态变量信息。
mysql> show status like "%semi%";
+--------------------------------------------+-------+
| Variable_name | Value |
+--------------------------------------------+-------+
| Rpl_semi_sync_replica_status | ON | # 注意行1
| Rpl_semi_sync_source_clients | 2 | # 注意行2
| Rpl_semi_sync_source_net_avg_wait_time | 0 |
| Rpl_semi_sync_source_net_wait_time | 0 |
| Rpl_semi_sync_source_net_waits | 1 |
| Rpl_semi_sync_source_no_times | 1 |
| Rpl_semi_sync_source_no_tx | 2 |
| Rpl_semi_sync_source_status | ON | # 注意行3
| Rpl_semi_sync_source_timefunc_failures | 0 |
| Rpl_semi_sync_source_tx_avg_wait_time | 0 |
| Rpl_semi_sync_source_tx_wait_time | 0 |
| Rpl_semi_sync_source_tx_waits | 0 |
| Rpl_semi_sync_source_wait_pos_backtraverse | 0 |
| Rpl_semi_sync_source_wait_sessions | 0 |
| Rpl_semi_sync_source_yes_tx | 0 |
+--------------------------------------------+-------+
15 rows in set (0.01 sec)此外,从MySQL的错误日志、show slave status也能获取到一些半同步复制的状态信息。
6. 测试半同步复制(等待、降级问题)
前面已经搭建好了半同步复制结构。
下面来测试半同步复制降级为异步复制的问题,借此来观察一些semisync的状态变化。
首先,只停掉slave2或slave3中其中一个io线程的话,slave1是不会出现降级的,因为默认的半同步复制只需等待一个ack回复即可返回成功信息。
如果同时停掉slave2和slave3的io线程,当master更新数据后,slave1在10秒(默认)之后将降级为异步复制。如下:
在slave2和slave3上执行:
mysql> stop slave io_thread;在master上面执行
create database test1;
create table test1.t(id int);
insert into test1.t values(33);
create database test2;
create table test2.t(id int);
insert into test2.t values(33);
create database test3;
create table test3.t(id int);
insert into test3.t values(33);在slave1上查看(在上面的步骤之后的10秒内查看):
mysql> show status like "%semi%";
+--------------------------------------------+-------+
| Variable_name | Value |
+--------------------------------------------+-------+
| Rpl_semi_sync_replica_status | ON |
| Rpl_semi_sync_source_clients | 0 |
| Rpl_semi_sync_source_net_avg_wait_time | 0 |
| Rpl_semi_sync_source_net_wait_time | 0 |
| Rpl_semi_sync_source_net_waits | 4 |
| Rpl_semi_sync_source_no_times | 3 |
| Rpl_semi_sync_source_no_tx | 8 |
| Rpl_semi_sync_source_status | ON |
| Rpl_semi_sync_source_timefunc_failures | 0 |
| Rpl_semi_sync_source_tx_avg_wait_time | 0 |
| Rpl_semi_sync_source_tx_wait_time | 0 |
| Rpl_semi_sync_source_tx_waits | 0 |
| Rpl_semi_sync_source_wait_pos_backtraverse | 0 |
| Rpl_semi_sync_source_wait_sessions | 1 |
| Rpl_semi_sync_source_yes_tx | 0 |
+--------------------------------------------+-------+
15 rows in set (0.00 sec)可以看到在这一小段时间内,slave1还是半同步复制。此时用show slave status查看slave1。
mysql> show slave status \G
*************************** 1. row ***************************
Slave_IO_State: Waiting for source to send event
Master_Host: 192.168.71.30
Master_User: repl
Master_Port: 3306
Connect_Retry: 60
Master_Log_File: master-bin.000001
Read_Master_Log_Pos: 2646
Relay_Log_File: slave-bin.000002
Relay_Log_Pos: 2218
Relay_Master_Log_File: master-bin.000001
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
Replicate_Do_DB:
Replicate_Ignore_DB:
Replicate_Do_Table:
Replicate_Ignore_Table:
Replicate_Wild_Do_Table:
Replicate_Wild_Ignore_Table:
Last_Errno: 0
Last_Error:
Skip_Counter: 0
Exec_Master_Log_Pos: 1999
Relay_Log_Space: 3070
Until_Condition: None
Until_Log_File:
Until_Log_Pos: 0
Master_SSL_Allowed: No
Master_SSL_CA_File:
Master_SSL_CA_Path:
Master_SSL_Cert:
Master_SSL_Cipher:
Master_SSL_Key:
Seconds_Behind_Master: 7
Master_SSL_Verify_Server_Cert: No
Last_IO_Errno: 0
Last_IO_Error:
Last_SQL_Errno: 0
Last_SQL_Error:
Replicate_Ignore_Server_Ids:
Master_Server_Id: 30
Master_UUID: 964d81d1-bbc7-11ef-88a1-fa163eae9ce2
Master_Info_File: mysql.slave_master_info
SQL_Delay: 0
SQL_Remaining_Delay: NULL
Slave_SQL_Running_State: Waiting for dependent transaction to commit
Master_Retry_Count: 10
Master_Bind:
Last_IO_Error_Timestamp:
Last_SQL_Error_Timestamp:
Master_SSL_Crl:
Master_SSL_Crlpath:
Retrieved_Gtid_Set:
Executed_Gtid_Set:
Auto_Position: 0
Replicate_Rewrite_DB:
Channel_Name:
Master_TLS_Version:
Master_public_key_path:
Get_master_public_key: 1
Network_Namespace:
1 row in set, 1 warning (0.00 sec)但10秒之后再查看。
mysql> show status like "%semi%";
+--------------------------------------------+-------+
| Variable_name | Value |
+--------------------------------------------+-------+
| Rpl_semi_sync_replica_status | ON |
| Rpl_semi_sync_source_clients | 0 |
| Rpl_semi_sync_source_net_avg_wait_time | 0 |
| Rpl_semi_sync_source_net_wait_time | 0 |
| Rpl_semi_sync_source_net_waits | 1 |
| Rpl_semi_sync_source_no_times | 2 |
| Rpl_semi_sync_source_no_tx | 5 |
| Rpl_semi_sync_source_status | OFF |
| Rpl_semi_sync_source_timefunc_failures | 0 |
| Rpl_semi_sync_source_tx_avg_wait_time | 0 |
| Rpl_semi_sync_source_tx_wait_time | 0 |
| Rpl_semi_sync_source_tx_waits | 0 |
| Rpl_semi_sync_source_wait_pos_backtraverse | 0 |
| Rpl_semi_sync_source_wait_sessions | 0 |
| Rpl_semi_sync_source_yes_tx | 0 |
+--------------------------------------------+-------+
15 rows in set (0.00 sec)发现slave1已经关闭半同步功能了,也就是说降级为异步复制了。
此时查看slave1的错误日志。
2024-12-16T18:31:18.638932Z 21 [Warning] [MY-011153] [Repl] Timeout waiting for reply of binlog (file: master-bin.000003, pos: 2250), semi-sync up to file master-bin.000003, position 2055.它先记录了当前slave2/slave3中已经同步到slave1的哪个位置。然后将Semi-sync复制切换为OFF状态,即降级为异步复制。
在下次slave2或slave3启动IO线程时,slave1将自动切换回半同步复制,并发送那些未被复制的binlog。
参考链接
深入MySQL复制(三) : 半同步复制 - 骏马金龙 - 博客园