MySQL半同步复制技术详解

详解MySQL半同步复制技术,提高数据安全性和一致性,配置部署及参数说明。

原文标题:数据库系列之MySQL半同步复制技术

原文作者:牧羊人的方向

冷月清谈:

本文介绍了MySQL半同步复制技术的原理、配置部署以及相关参数说明。MySQL半同步复制技术保证了主库在提交事务前至少有一个从库已接收到binlog,从而提高数据安全性。5.7版本引入了无损复制的AFTER_SYNC模式,避免了主库宕机导致的数据丢失。文章还详细讲解了`sync_binlog`、`sync_relay_log`等关键参数的配置,并通过模拟测试验证了半同步复制的实际效果。此外,还列举了其他重要的环境变量和状态变量,方便读者进行监控和管理。

怜星夜思:

1、文章中提到了半同步复制在超时后会转为异步复制,那么在实际应用中,该如何设置`rpl_semi_sync_master_timeout`参数的值呢?过大或过小会有什么影响?
2、MySQL 8.0 也支持半同步复制,它与 5.7 版本的半同步复制相比有哪些改进?在选择版本时应该如何考虑?
3、除了半同步复制,MySQL 还支持其他哪些复制技术?它们各自的优缺点是什么?在什么场景下更适合使用哪种复制技术?

原文内容

本文简要介绍了MySQL半同步复制技术及配置部署,以加深对MySQL半同步复制技术的理解。

1、MySQL半同步复制
半同步复制是指主库要等待至少一个从库节点将binlog文件flush到relay log中的返回即认为数据同步正常。

在mysql5.7版本以前,半同步技术使用的是after_commit模式,也就是主库先提交事务,再接收到从库的ACK后将commit结果返回给客户端。在这种模式下,当客户端事务在主库提交后,在得到从库确认(slave ack)的过程中主库宕机了,如果事务还没有发送到从库,从库切换为主库后,这部分在主库上已经提交的事务就不见了,出现数据丢失。因此在5.7版本以后引入了无损复制,也就是AFTER_SYNC模式,主库等待从库反馈接收到relay log的ack后,再提交事务并返回commit OK的结果给客户端。即使主库出现crash,所有在主库上已经提交的事务都已经同步到slave的relay log中。

1) 发送binlog和接收ack异步化

相较于之前版本的mysql半同步技术中,将dump线程的发送和接收工作分为两个线程来处理,这样可以同时发送binlog到slave和接收slave的ack信息,提升了性能。

2) Binlog互斥锁

之前版本的半同步复制技术在主提交binlog的写会话和dump thread读binlog的操作都会对binlog添加互斥锁,导致binlog文件的读写是串行化的,存在并发度的问题。5.7版本中对binlog lock进行了优化,移除了dump thread的binlog互斥锁,并加入了安全边际保证binlog的读安全。

3) SYNC_BINLOG配置

  • 默认为sync_binlog=0,表示mysql不控制binlog的刷新,binlog sync磁盘由操作系统负责。这个时候性能是最好的,但是风险也是最大的,因为一旦系统Crash,在binlog_cache中的所有binlog信息都会被丢失。

  • 当不为0的时候,其数值为定期sync磁盘的binlog commit group数,表示每sync_binlog次事务提交,MySQL调用文件系统的刷新操作将缓存刷下去。

  • sync_binlog值不等于1的时候事务在FLUSH阶段就传输binlog到从库了,而值为1时,binlog同步操作是在SYNC阶段后,表示每次事务提交,MySQL都会把binlog刷下去,是最安全但是性能损耗最大的设置。

  • 当sync_binlog值大于1的时候,sync binlog操作可能并没有使binlog落盘。如果没有落盘,事务在提交前,Master掉电,然后恢复,那么这个时候该事务被回滚。但是Slave上可能已经收到了该事务的events并且执行,这个时候就会出现Slave事务比Master多的情况,主备同步会失败。

所以如果要保持主备一致,需要设置sync_binlog为1。这种设置对高并发系统来说,对系统写入性能会有一定的影响。

4) SYNC_RELAY_LOG配置

  • 设置为0时,表示mysql不控制relay log的刷新,由操作系统决定何时写入。当系统奔溃时,缓存中的这部分binlog数据会丢失

  • 设置为1时,slave的I/O线程每次接收到master发送过来的binlog日志都要写入系统缓冲区,然后刷入relay log中继日志里

  • 默认为10000,即每10000次sync_relay_log事件会刷新到磁盘

sync_relay_log设置为1的时候,事务响应时间会受到影响,对于涉及数据比较多的事务延迟会增加很多。

2、半同步复制的部署配置
2.1 环境准备

要想使用半同步复制,必须满足以下几个条件:

1)MySQL 5.5及以上版本

2)变量have_dynamic_loading为YES (查看命令:show variables like "have_dynamic_loading";)

mysql> show variables like "have_dynamic_loading";
+----------------------+-------+

| Variable_name | Value |
+----------------------+-------+

| have_dynamic_loading | YES |
+----------------------+-------+

1 row in set (0.14 sec)

3)主从复制已经存在

2.2 加载插件

1)首先加载插件

因用户需执行INSTALL PLUGIN, SET GLOBAL, STOP SLAVE和START SLAVE操作,所以用户需有SUPER权限。半同步复制是一个功能模块,库要能支持动态加载才能实现半同步复制! (安装的模块存放路径为/usr/local/mysql/lib/plugin)

  • 主数据库节点执行:

mysql>  INSTALL PLUGIN rpl_semi_sync_master SONAME 'semisync_master.so';

[要保证/usr/local/mysql/lib/plugin/目录下有semisync_master.so文件 (默认编译安装后就有)]
[root@tango-centos01 plugin]# ls -l semi*
-rwxr-xr-x. 1 mysql mysql 709470 Aug 29 2020 semisync_master.so
-rwxr-xr-x. 1 mysql mysql 152501 Aug 29 2020 semisync_slave.so
---------------------------------------------------------------------------------------
如果要卸载(前提是要关闭半同步复制功能),就执行
mysql> UNINSTALL PLUGIN rpl_semi_sync_master;
  • 从数据库节点执行:

mysql> INSTALL PLUGIN rpl_semi_sync_slave SONAME 'semisync_slave.so';

[要保证/usr/local/mysql/lib/plugin/目录下有semisync_slave.so文件 (默认编译安装后就有)]
[root@tango-centos01 plugin]# ls -l semi*
-rwxr-xr-x. 1 mysql mysql 709470 Aug 29 2020 semisync_master.so
-rwxr-xr-x. 1 mysql mysql 152501 Aug 29 2020 semisync_slave.so
---------------------------------------------------------------------------------------
如果要卸载(前提是要关闭半同步复制功能),就执行
mysql> UNINSTALL PLUGIN rpl_semi_sync_slave;
  • 查看插件是否加载成功的两种方式

##1show plugins
mysql> show plugins;
+----------------------------+----------+--------------------+--------------------+---------+
| Name | Status | Type | Library | License |
| rpl_semi_sync_master | ACTIVE | REPLICATION | semisync_master.so | GPL |

##2、查询INFORMATION_SCHEMA.PLUGINS
mysql> SELECT PLUGIN_NAME, PLUGIN_STATUS FROM INFORMATION_SCHEMA.PLUGINS WHERE PLUGIN_NAME LIKE '%semi%';
+----------------------+---------------+
| PLUGIN_NAME | PLUGIN_STATUS |
+----------------------+---------------+
| rpl_semi_sync_master | ACTIVE |
+----------------------+---------------+
1 row in set (0.02 sec)
2.3 启动半同步复制
2.3.1 启动半同步复制

在安装完插件后,半同步复制默认是关闭的,这时需设置参数来开启半同步

1)主数据库节点执行:

mysql> SET GLOBAL rpl_semi_sync_master_enabled = 1;

2)从数据库节点执行:

mysql> SET GLOBAL rpl_semi_sync_slave_enabled = 1;

以上的启动方式是在登录mysql后的命令行操作,也可写在my.cnf配置文件中(推荐这种启动方式)。

##1、主数据库的my.cnf配置文件中添加:
plugin-load=rpl_semi_sync_master=semisync_master.so
rpl_semi_sync_master_enabled=1

##2、从数据库的my.cnf配置文件中添加:
plugin-load=rpl_semi_sync_slave=semisync_slave.so
rpl_semi_sync_slave_enabled=1
2.3.2 重启从数据库节点上的IO线程

在从节点tango-centos-02和tango-centos-03上执行以下命令:

mysql> STOP SLAVE IO_THREAD;
Query OK, 0 rows affected (0.05 sec)

mysql> START SLAVE IO_THREAD;
Query OK, 0 rows affected (0.01 sec)

重启后,slave会在master上注册为半同步复制的slave角色。这时候,主的mysql.err中会打印如下信息:

2021-05-27T19:19:23.081169Z 7 [Note] While initializing dump thread for slave with UUID <de623c4b-eb35-11ea-b1e4-000c2959d3e3>, found a zombie dump thread with the same UUID. Master is killing the zombie dump thread(3).
2021-05-27T19:19:23.081285Z 7 [Note] Start binlog_dump to master_thread_id(7) slave_server(103), pos(mysql-bin.000025, 194)
2021-05-27T19:19:23.081302Z 7 [Note] Start semi-sync binlog_dump to slave (server_id: 103), pos(mysql-bin.000025, 194)
2021-05-27T19:19:23.081571Z 3 [Note] Stop asynchronous binlog_dump to slave (server_id: 103)
2.3.3 查看半同步是否在运行

1)主数据库节点

mysql>  show status like 'Rpl_semi_sync_master_status';
+-----------------------------+-------+

| Variable_name | Value |
+-----------------------------+-------+

| Rpl_semi_sync_master_status | ON |
+-----------------------------+-------+

1 row in set (0.06 sec)

2)从数据库节点

mysql> show status like 'Rpl_semi_sync_slave_status';
+----------------------------+-------+

| Variable_name | Value |
+----------------------------+-------+

| Rpl_semi_sync_slave_status | ON |
+----------------------------+-------+

1 row in set (0.01 sec)

这两个变量常用来监控主从是否运行在半同步复制模式下。至此,MySQL半同步复制环境就部署完成了!

需要注意下,其实Mysql半同步复制并不是严格意义上的半同步复制。当半同步复制发生超时时(由rpl_semi_sync_master_timeout参数控制,单位是毫秒,默认为10000,即10s),会暂时关闭半同步复制,转而使用异步复制。当master dump线程发送完一个事务的所有事件之后,如果在rpl_semi_sync_master_timeout内,收到了从库的响应,则主从又重新恢复为半同步复制。

mysql>  show variables like "rpl_semi_sync_master_timeout";
+------------------------------+-------+

| Variable_name | Value |
+------------------------------+-------+

| rpl_semi_sync_master_timeout | 10000 |
+------------------------------+-------+

1 row in set (0.00 sec)
3、半同步复制测试

1)主数据库节点 (从节点在执行"stop slave"之前)

mysql> create database tango;
Query OK, 1 row affected (0.04 sec)

mysql> create table tango.tb01(id int,name char(10));
Query OK, 0 rows affected (0.12 sec)

mysql> insert into tango.tb01 values(1,'bj');
Query OK, 1 row affected (0.22 sec)

2)从数据库节点执行"stop slave"

mysql> stop slave;

##1、观察主库
mysql> insert into tango.tb01 values(3,'gz');
Query OK, 1 row affected (10.00 sec)

mysql> show status like 'Rpl_semi_sync_master_status';
+-----------------------------+-------+

| Variable_name | Value |
+-----------------------------+-------+

| Rpl_semi_sync_master_status | OFF |
+-----------------------------+-------+

1 row in set (0.01 sec)

##2、观察从库
mysql> show status like 'Rpl_semi_sync_slave_status';
+----------------------------+-------+

| Variable_name | Value |
+----------------------------+-------+

| Rpl_semi_sync_slave_status | OFF |
+----------------------------+-------+

1 row in set (0.01 sec)

3)接着在从数据库节点执行"start slave"

mysql> start slave;

##1、观察主库
mysql> insert into tango.tb01 values(4,'sh');
Query OK, 1 row affected (0.02 sec)

mysql> show status like 'Rpl_semi_sync_master_status';
+-----------------------------+-------+

| Variable_name | Value |
+-----------------------------+-------+

| Rpl_semi_sync_master_status | ON |
+-----------------------------+-------+

1 row in set (0.00 sec)

##2、观察从库
mysql> show status like 'Rpl_semi_sync_slave_status';
+-----------------------------+-------+

| Variable_name | Value |
+-----------------------------+-------+

| Rpl_semi_sync_slave_status | ON |
+-----------------------------+-------+

1 row in set (0.00 sec)

以上验证分为三个阶段:

  1. 在Slave执行stop slave之前,主的insert操作很快就能返回。

  2. 在Slave执行stop slave后,主的insert操作需要10.00s才返回,而这与rpl_semi_sync_master_timeout参数的时间相吻合。这时,查看两个状态的值,均为“OFF”了。同时,主的mysql.err中打印如下信息:
2021-05-27T20:06:08.218032Z 7 [ERROR] Semi-sync master failed on net_flush() before waiting for slave reply
2021-05-27T20:06:08.218073Z 7 [Note] Stop semi-sync binlog_dump to slave (server_id: 103)
2021-05-27T20:06:08.218172Z 7 [Note] Aborted connection 7 to db: 'unconnected' user: 'repl' host: '192.168.112.103' (Found net error)
2021-05-27T20:06:18.218929Z 5 [Warning] Timeout waiting for reply of binlog (file: mysql-bin.000025, pos: 1377), semi-sync up to file mysql-bin.000025, position 1097.
2021-05-27T20:06:18.219076Z 5 [Note] Semi-sync replication switched OFF.
  1. 在Slave执行start slave后,主的insert操作很快就能返回,此时,两个状态的值也变为“ON”了。同时,主的mysql.err中会打印如下信息:

2021-05-27T20:07:26.327396Z 8 [Note] Start binlog_dump to master_thread_id(8) slave_server(102), pos(mysql-bin.000025, 817)
2021-05-27T20:07:26.327450Z 8 [Note] Start semi-sync binlog_dump to slave (server_id: 102), pos(mysql-bin.000025, 817)
2021-05-27T20:07:26.424521Z 0 [Note] Semi-sync replication switched ON at (mysql-bin.000025, 1377)
2021-05-27T20:07:33.418389Z 9 [Note] Start binlog_dump to master_thread_id(9) slave_server(103), pos(mysql-bin.000025, 1097)
2021-05-27T20:07:33.418439Z 9 [Note] Start semi-sync binlog_dump to slave (server_id: 103), pos(mysql-bin.000025, 1097)
4、其他变量说明
4.1 环境变量(show variables like '%Rpl%';)
mysql>  show variables like '%Rpl%';
+-------------------------------------------+------------+

| 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_stop_slave_timeout | 31536000 |
+-------------------------------------------+------------+

7 rows in set (0.00 sec)
  • rpl_semi_sync_master_wait_for_slave_count:MySQL 5.7.3引入的,该变量设置主master需要等待多少个slave应答,才能返回给客户端,默认为1。

  • rpl_semi_sync_master_wait_no_slave:

    • ON,默认值,当状态变量Rpl_semi_sync_master_clients中的值小于rpl_semi_sync_master_wait_for_slave_count时,Rpl_semi_sync_master_status依旧显示为ON。

    • OFF,当状态变量Rpl_semi_sync_master_clients中的值于rpl_semi_sync_master_wait_for_slave_count时,Rpl_semi_sync_master_status立即显示为OFF,即异步复制。

简单来说,如果mysql架构是1主2从,2个从都采用了半同步复制,且设置的是rpl_semi_sync_master_wait_for_slave_count=2,如果其中一个挂掉了,对于rpl_semi_sync_master_wait_no_slave设置为ON的情况,此时显示的仍然是半同步复制,如果rpl_semi_sync_master_wait_no_slave设置为OFF,则会立刻变成异步复制。

4.2 状态变量(show status like '%Rpl_semi%';)
mysql> show status like '%Rpl_semi%';
+--------------------------------------------+-------+
| Variable_name | Value |
+--------------------------------------------+-------+
| Rpl_semi_sync_master_clients | 2 |
| Rpl_semi_sync_master_net_avg_wait_time | 0 |
| Rpl_semi_sync_master_net_wait_time | 0 |
| Rpl_semi_sync_master_net_waits | 10 |
| Rpl_semi_sync_master_no_times | 1 |
| Rpl_semi_sync_master_no_tx | 1 |
| Rpl_semi_sync_master_status | ON |
| Rpl_semi_sync_master_timefunc_failures | 0 |
| Rpl_semi_sync_master_tx_avg_wait_time | 876 |
| Rpl_semi_sync_master_tx_wait_time | 4383 |
| Rpl_semi_sync_master_tx_waits | 5 |
| Rpl_semi_sync_master_wait_pos_backtraverse | 0 |
| Rpl_semi_sync_master_wait_sessions | 0 |
| Rpl_semi_sync_master_yes_tx | 5 |
+--------------------------------------------+-------+
14 rows in set (0.01 sec)
  • Rpl_semi_sync_master_clients:当前半同步复制从的个数,如果是一主多从的架构,并不包含异步复制从的个数。

  • Rpl_semi_sync_master_no_tx:从节点没有接收到的commit数

  • Rpl_semi_sync_master_yes_tx:从节点成功接收到的commit数

参考资料:

  1. https://www.cnblogs.com/kevingrace/p/10228694.html

  2. https://www.cnblogs.com/zero-gg/p/9057092.html

8.0开始 loss-less semi-sync 正式GA了,这个特性很香。如果追求数据零丢失,可以直接上8.0,省心。而且8.0还有很多其他改进,例如对 JSON 数据类型的支持,以及对 SQL 语法的增强,这些都能提升开发效率。

我觉得可以结合监控工具来设置rpl_semi_sync_master_timeout。比如,可以监控主库的Rpl_semi_sync_master_statusRpl_semi_sync_master_no_times这两个状态变量。如果发现频繁切换到异步复制,或者Rpl_semi_sync_master_no_times的值持续升高,就说明超时时间设置得过小,需要适当调大。

可以根据 RTO 和 RPO 来选择复制技术。异步复制的RPO较大,RTO较小;半同步复制的RPO较小,RTO适中;组复制的RPO最小,RTO也较小。根据业务对数据丢失和服务中断时间的容忍程度,选择合适的复制技术。

选择版本时还要考虑兼容性问题。如果现有应用依赖于 5.7 版本的某些特性,那么升级到 8.0 需要进行充分的测试,确保不会出现兼容性问题。另外,8.0 版本的资源消耗也可能更高,需要评估服务器的硬件配置是否满足要求。

MySQL主要支持异步复制、半同步复制和组复制三种复制技术。异步复制性能最好,但数据一致性较差;半同步复制平衡了性能和数据一致性;组复制则提供了最高的数据一致性,但性能开销也最大。具体选择哪种技术,需要根据业务需求和系统架构进行权衡。

设置rpl_semi_sync_master_timeout值的时候,可以参考网络延迟。比如,如果主从之间的网络延迟通常在10ms左右,那么可以将超时时间设置为50ms或100ms。这样即使出现短暂的网络波动,也不会立即切换到异步复制。当然,还需要结合业务的容忍度来确定最终的值。

MySQL 8.0 对半同步复制进行了一些优化,例如引入了AFTER_SYNC模式,避免了数据丢失的风险。此外,还改进了组提交的性能,降低了半同步复制对性能的影响。如果对数据一致性要求较高,建议优先考虑 8.0 版本。

如果对数据一致性要求不高,例如网站的访问日志记录,可以使用异步复制。如果对数据一致性要求较高,例如金融交易系统,则可以考虑使用半同步复制或组复制。如果需要更高的可用性和容错性,则可以考虑使用组复制。

关于rpl_semi_sync_master_timeout的设置,确实需要仔细斟酌。如果设置过小,很容易导致频繁切换到异步复制,降低数据安全性。但如果设置过大,又可能在网络出现问题时,导致主库长时间阻塞,影响应用的可用性。个人建议根据实际网络情况和业务对数据一致性的要求进行调整,可以先从默认的10秒开始测试,然后根据监控数据逐步调整。