记录备忘
参考博文:https://blog.csdn.net/wzy0623/article/details/90267132
目录
一、概述
二、原理
三、演进
3.1 线程的改进
3.1.1 旧版本
3.1.2 改进
3.2 二进制日志互斥锁改进
3.2.1 旧版本
3.2.2 改进
四、一致性分析
4.1 rpl_semi_sync_master_wait_point的配置
4.1.1 配置为WAIT_AFTER_COMMIT
4.1.2 配置为WAIT_AFTER_SYNC
4.2 sync_binlog的配置
4.3 sync_relay_log的配置
五、半同步相关配置
5.1 控制类参数
5.2 状态类参数
六、半同步实验
6.1 环境规划
6.2 配置异步复制
6.3 配置半同步复制
6.3.1 安装插件
6.3.2 启用半同步复制
七、测试
7.1 正常提交事务
7.2 回滚事务
7.3 主库等待超时
7.3.1 rpl_semi_sync_master_wait_no_slave为ON
7.3.2 rpl_semi_sync_master_wait_no_slave为OFF
主从复制主要分三种类型:异步、半同步、同步。
异步复制:主库提交事务时,将事件写入它的二进制日志,而从库在准备就绪时请求它们。主库无需等待从库的ACK回复,直接提交事务并返回客户端。异步复制不确保所有事件都能到达从库,无法保证数据完整性。半同步复制:介于异步和完全同步复制之间。主库仅等待至少一个从库接收并记录事件。它不会等待所有从库确认收到,并且从库只需要确认接收,而不是事件已在从库完全执行和提交。当主库等待回复超时后自动转为异步。同步复制:当主库提交事务时,所有从库也将在主库返回执行事务的会话之前提交事务。这样做的缺点是完成事务可能会有很大延迟。与异步复制相比,半同步复制提供了改进的数据完整性,因为当提交成功返回时,已知数据至少存在于两个位置。但半同步复制确实会对性能产生一些影响,因为需要等待从库,提交速度会变慢,延迟至少是将提交发送到从库并等待从库确认收到的TCP/IP往返时间。这意味着半同步复制最好在低延时的网络中使用。
半同步复制时,为了保证主库上的每一个Binlog事务都能够被可靠的复制到从库上,主库在每次事务成功提交时,并不及时反馈给前端应用用户,而是等待其中的一个从库也接收到Binlog事务并成功写入中继日志后,主库才返回commit操作成功给客户端。半同步复制保证了事务成功提交后,至少有两份日志记录,一份在主库的Binlog日志上,另一份在至少一个从库的中继日志Relay log上,从而更近一步保证了数据的完整性。
旧版本的半同步复制受限于Binlog Dump线程,原因是该线程承担了两份不同且又十分频繁的任务:
传送二进制日志事件给从库接收从库的ACK反馈信息这两个任务是串行的,Binlog Dump线程必须等待从库返回之后才会传送下一个事件。Binlog Dump线程已然成为整个半同步复制性能的瓶颈。在高并发业务场景下,这样的机制会影响数据库整体的TPS。单一Binlog Dump线程发送接收的工作流程如图1所示。
图1 单一Binlog Dump线程发送接收的工作流程图在5.7.4版本的半同步复制框架中,独立出一个Ack Receiver线程 ,专门用于接收从库返回的ACK请求,这将之前Binlog Dump线程的发送和接收工作分为了两个线程来处理。这样主库上有两个线程独立工作,可以同时发送二进制日志事件到从库,和接收从库的ACK信息。因此半同步复制得到了极大的性能提升。Binlog Dump线程与Ack Receiver线程工作流程如图2所示。
图2 Binlog Dump线程与Ack Receiver线程工作流程图Ack Receiver线程在主库启用半同步复制时创建,并在主库禁用半同步复制时销毁。它是自动创建和销毁的,因此不受用户控制。它的状态信息可以在performance_schema中查询得到:
select name, type, processlist_state from performance_schema.threads where name like '%ack_receiver%'; +------------------------------+------------+--------------------------------------+ | name | type | processlist_state | +------------------------------+------------+--------------------------------------+ | thread/semisync/Ack_receiver | BACKGROUND | Waiting for semi-sync ACK from slave | +------------------------------+------------+--------------------------------------+Ack receiver线程有以下三个状态:
Waiting for semi-sync slave connectionWaiting for semi-sync ACK from slaveReading semi-sync ACK from slave在MySQL 5.7.17之前,这个Ack Receiver线程采用了select机制来监听从库返回的结果,然而select机制监控的文件句柄只能是0-1024,当超过1024时,用户在MySQL的错误日志中或许会收到类似如下的报错,更有甚者会导致MySQL发生宕机。
semi-sync master failed on net_flush() before waiting for slave reply.MySQL 5.7.17版本开始,官方修复了这个bug,开始使用poll机制来替换原来的select机制,从而可以避免上面的问题。其实poll调用本质上和select没有区别,只是在I/O句柄数理论上没有上限了,原因是它是基于链表来存储的。
旧版本半同步复制在主库提交二进制日志的写会话和Binlog Dump线程读取二进制日志的操作都会对二进制日志添加binlog lock互斥锁,用于保护二进制日志的读写操作。使用此互斥锁,二进制日志读写操作是安全的,但会导致二进制日志文件的读写串行化。不仅Binlog Dump线程和用户会话不能同时读写二进制日志,就连多个Binlog Dump线程本身也无法同时读取。每当一个会话正在读取或写入二进制日志文件时,所有其它会话都必须等待。如此顺序读写可能是一个瓶颈,尤其是当读写操作很慢时。串行化读写二进制日志如图3所示。
图3 串行化读写二进制日志示意图MySQL 5.7.2对binlog lock进行了以下两方面优化:
从Binlog Dump线程中移除binlog lock。加入了安全边际保证二进制日志的读安全。二进制日志文件看起来像一个仅追加的日志文件,可以安全地读取没有锁定的二进制事件。因此从Binlog Dump线程中删除了binlog锁。不使用binlog锁,而是为活动binlog维护安全读取边界(最大位置)。Binlog Dump线程永远不会读取超过安全读取边界的位置。当边界到达边界时,它将等待边界更新。用户会话负责在追加了二进制事件后更新安全读取边界。改进后的二进制日志读写如图4所示。
图4 改进后的二进制日志读写示意图从图中可知:
读取二进制日志事件时,Binlog Dump线程不会相互阻塞。正在写二进制日志事件的用户会话不会阻止Binlog Dump线程。读取二进制日志事件的Binlog Dump不会阻塞用户会话。因此,Binlog Dump线程和用户会话都可以获得更好的吞吐量,尤其是当有很多从库时,这种改进非常显着。
主库在等待备库ack时候,如果超时会退化为异步,这就可能导致数据丢失。在接下来分析中,先假设rpl_semi_sync_master_timeout足够大,不会退化为异步方式。
这里通过以下三个参数分析:
rpl_semi_sync_master_wait_pointsync_binlogsync_relay_logrpl_semi_sync_master_wait_point为WAIT_AFTER_COMMIT时,commitTrx的调用在engine层commit之后,如图5所示。
图5 WAIT_AFTER_COMMIT同步流程图主库在等待Slave ACK时候,虽然没有返回当前客户端,但事务已经提交,其他客户端会读取到已提交事务。如果Slave端还没有读到该事务的events,同时主库发生了crash,然后切换到备库。那么之前读到的事务就不见了,出现了幻读。另外,如果客户端会重试提交该事务到新的主上,当宕机的主库重新启动后,以从库的身份重新加入到该主从结构中。此时会发现,该事务在从库中被提交了两次,一次是之前作为主的时候,一次是被新主同步过来的,结果依然是主从数据不一致。
MySQL针对上述问题,在5.7.2中改为调用binlog sync之后,engine层commit之前等待Slave ACK。这样只有在确认Slave收到事务events后,事务才会提交。在commit之前等待Slave ACK,同时可以堆积事务,利于group commit,有利于提升性能。如图6所示。
图6 WAIT_AFTER_SYNC同步流程图当sync_binlog为0的时候,binlog sync磁盘由操作系统负责。当不为0的时候,其数值为定期sync磁盘的binlog commit group数。当sync_binlog值大于1的时候,sync binlog操作可能并没有使binlog落盘。如果没有落盘,事务在提交前,Master掉电,然后恢复,那么这个时候该事务被回滚。但是Slave上可能已经收到了该事务的events并且执行,这个时候就会出现Slave事务比Master多的情况,主备同步会失败。所以如果要保持主备一致,需要设置sync_binlog为1。
WAIT_AFTER_SYNC和WAIT_AFTER_COMMIT两图中Send Events的位置,也可能导致主备数据不一致,出现同步失败的情形。实际在rpl_semi_sync_master_wait_point分析的图中是sync binlog大于1的情况。根据上面源码,流程如图7所示。Master依次执行flush binlog,update binlog position,sync binlog。如果Master在update binlog position后,sync binlog前掉电,Master再次启动后原事务就会被回滚。但可能出现Slave获取到Events,这也会导致Slave数据比Master多,主备同步失败。
图7 binlog大于1的落盘流程图由于上面的原因,sync_binlog设置为1的时候,MySQL会update binlog end pos after sync。流程如图8所示。这时候,对于每一个事务都需要sync binlog,同时sync binlog和网络发送events会是一个串行的过程,性能下降明显。
图8 sync_binlog等于1的落盘流程图当sync_relay_log不是1的时候,semisync返回给Master的position可能没有sync到磁盘。在gtid_mode下,在保证前面两个配置正确的情况下,sync_relay_log不是1的时候,仅发生Master或Slave的一次Crash并不会发生数据丢失或者主备同步失败情况。如果发生Slave没有sync relay log,Master端事务提交,客户端观察到事务提交,然后Slave端Crash。这样Slave端就会丢失掉已经回复Master ACK的事务events。
图9 relaylog落盘流程图但当Slave再次启动,如果没有来得及从Master端同步丢失的事务Events,Master就Crash。这个时候,用户访问Slave就会发现数据丢失。
图10 数据丢失示意图
半同步配置是通过插件方式完成的。
主库端的:semisync_master.so从库端的:semisync_slave.so仅当使用INSTALL PLUGIN安装了相应的插件时,系统和状态变量才可用。
软件版本
OSCentOS Linux release 7.3.1611 (Core)MySQLmysql-8.0.21 路径 路径用途/home/mysql/mysql8MySQL主目录/home/mysql/mysql8/dataMySQL数据目录/home/mysql/mysql8/logs/binlogMySQL二进制日志目录/home/mysql/mysql8/logs/relaylogMySQL中继日志目录见 “MySQL复制之异步复制” 的配置过程
半同步复制是使用插件实现的,因此必须将插件安装到MySQL服务器中才能使用它们。安装插件后,可以通过与之关联的系统变量来控制它。安装插件之前,这些系统变量是不可用的。要使用半同步复制,必须满足以下要求:
安装插件需要MySQL服务器支持动态加载。要验证这一点,检查have_dynamic_loading系统变量的值是否为YES。MySQL 8缺省为YES。已经启动了异步复制。半同步不支持多源复制。 安装设置半同步复制,需要REPLICATION_SLAVE_ADMIN或SUPER权限。MySQL发行版包括主、从端的半同步复制插件文件semisync_master.so和semisync_slave.so,缺省位于MySQL安装目录下的lib/plugin目录下,本例中为/usr/local/mysql/lib/plugin。也可以通过设置plugin_dir系统变量的值指定插件目录位置。执行下面的SQL语句加载插件。
-- 在主库 install plugin rpl_semi_sync_master soname 'semisync_master.so'; -- 在每个从库 install plugin rpl_semi_sync_slave soname 'semisync_slave.so';建议在主从库都安装以上插件。这样便于主从切换
如果尝试安装插件导致Linux上出现类似于此处所示的错误,则必须安装libimf:
mysql> install plugin rpl_semi_sync_master soname 'semisync_master.so'; ERROR 1126 (HY000): Can't open shared library '/usr/local/mysql/lib/plugin/semisync_master.so' (errno: 22 libimf.so: cannot open shared object file:No such file or directory)可以从https://dev.mysql.com/downloads/os-linux.html获取libimf。
检查INFORMATION_SCHEMA.PLUGINS表或使用SHOW 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.00 sec)查看一些初始化变量
-- 主库 mysql> show variables like '%semi%'; +-------------------------------------------+------------+ | Variable_name | Value | +-------------------------------------------+------------+ | rpl_semi_sync_master_enabled | OFF | | 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.00 sec) --从库 mysql> show variables like '%semi%'; +---------------------------------+-------+ | Variable_name | Value | +---------------------------------+-------+ | rpl_semi_sync_slave_enabled | OFF | | rpl_semi_sync_slave_trace_level | 32 | +---------------------------------+-------+ 2 rows in set (0.00 sec)安装半同步复制插件后,默认情况下会禁用它。必须在主库和从库都启用插件才能启用半同步复制。如果仅启用一侧,则复制将是异步的
-- 主库 set global rpl_semi_sync_master_enabled = 1; -- 从库 set global rpl_semi_sync_slave_enabled = 1;以上的启动方式是在命令行操作,也可写在配置文件中。
# 主 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在有的高可用架构下,master和slave需同时启动,以便在切换后能继续使用半同步复制。
plugin-load = "rpl_semi_sync_master=semisync_master.so;rpl_semi_sync_slave=semisync_slave.so" rpl-semi-sync-master-enabled = 1 rpl-semi-sync-slave-enabled = 1重启从库上的I/O线程,重启后,从库会在主库上注册为半同步复制的从库角色。
-- 从库执行 mysql> stop slave io_thread; mysql> start slave io_thread;查看半同步是否在运行
-- 主库 mysql> show status like 'Rpl_semi_sync_master_status'; +-----------------------------+-------+ | Variable_name | Value | +-----------------------------+-------+ | Rpl_semi_sync_master_status | ON | +-----------------------------+-------+ 1 row in set (0.01 sec) -- 从库 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)这两个变量常用来监控主从是否运行在半同步复制模式下。至此,MySQL半同步复制搭建完毕。
MySQL 8缺省是每条语句自动提交的。主库等待3个事务的确认,分别对应create database、create table、insert语句。平均每个事务等待758微妙,从库正常确认了3个事务。
从上面的测试可以得出以下结论:
set session autocommit=0语句不被复制。DDL语句会触发一个commit,自动提交DDL语句本身及其前面未提交的事务。非事务表不能回滚。Rpl_semi_sync_master_tx_waits和Rpl_semi_sync_master_yes_tx都增加了3,分别对应 insert into t1 values(2);create table t2 (a int) engine=myisam;insert into t2 values(3);三个事务。从库数小于rpl_semi_sync_master_wait_for_slave_count值(收到多少个从库的回复认为事务完成,主库返回客户端commit,默认值1),需要等待rpl_semi_sync_master_timeout的时间后才会退化为异步。
-- 2从库都关闭IO线程 mysql> stop slave io_thread; Query OK, 0 rows affected (0.01 sec) -- 主库插入事物数据 mysql> insert into t1 values (4); Query OK, 1 row affected (10.01 sec) -- 等待从库10秒 -- 主库转为异步 mysql> show status like '%Rpl_semi_sync_master_status'; +-----------------------------+-------+ | Variable_name | Value | +-----------------------------+-------+ | Rpl_semi_sync_master_status | OFF | +-----------------------------+-------+从库数小于rpl_semi_sync_master_wait_for_slave_count值(收到多少个从库的回复认为事务完成,主库返回客户端commit,默认值1),无需等待rpl_semi_sync_master_timeout,直接退化为异步。
-- 主库关闭rpl_semi_sync_master_wait_no_slave mysql> set global rpl_semi_sync_master_wait_no_slave=off; Query OK, 0 rows affected (0.00 sec) -- 2从库都关闭IO线程 mysql> stop slave io_thread; Query OK, 0 rows affected (0.01 sec) -- 主库插入事物数据 mysql> insert into t1 values (5); Query OK, 1 row affected (0.01 sec) -- 无等待,立刻提交 -- 主库转为异步 mysql> show status like '%Rpl_semi_sync_master_status'; +-----------------------------+-------+ | Variable_name | Value | +-----------------------------+-------+ | Rpl_semi_sync_master_status | OFF | +-----------------------------+-------+全文完。
