半同步出现的背景--(异步复制)
MySQL默认复制方式中,主库在处理事务时,只会按照自身的配置来控制何时写入binlog以及返回响应给客户端。不管是否存在从库、从库的数据是否已经跟上,典型的独行客形象。
在这种方式下,无法保证主从的数据保持一致,可能会存在从库严重落后于主库的现象。除此之外,如果主库完成事务的写入操作并返回响应给客户端后,从库还未接收到这个事务;此时主库崩溃,HA组件对其进行切主操作,此时这个事务就丢失了,引起了数据的不一致性。
半同步复制和无损复制

半同步复制:保证在至少一个从库将事务数据写入到relay-log后才返回响应给客户端。
在MySQL5.5版本中,推出了半同步复制,在事务提交之前需要从库进行反馈。主库在应答客户端提交的事务之前需要保证至少一个从库接收并写到relay log中。我们可以看到在从库返回ACK前,主库已经在binlog和engine中均已完成写入刷盘过程;如果在主库写完引擎后,从库还未收到那个事务,主库崩溃,恢复后,从库比主库要少一个事务。所以在MySQL5.7引入了无损复制。

无损复制:将engine commit过程退后到从库返回ACK后执行;这样即使主库崩溃,也会因为engine未提交而进行事务回滚。并不会造成主库比从库多一个事务的情况。
我们考虑这样一个场景:在从库已经将事务写入到relay log即将返回ACK给主库时,主库崩溃恢复后,由于事务没有提交到引擎,所以会进行事务回滚。这个时候从库已经写入成功,所以这个时候变成多了事务的场景。
半同步相关参数和状态
mysql> show variables like '%semi%';+-------------------------------------------+------------+| Variable_name | Value |+-------------------------------------------+------------+| rpl_semi_sync_master_enabled | ON || rpl_semi_sync_master_timeout | 3000 || 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 | OFF || rpl_semi_sync_slave_trace_level | 32 |+-------------------------------------------+------------+8 rows in set (0.07 sec)
rpl_semi_sync_master_enabled:主库是否开启半同步
rpl_semi_sync_master_timeout:半同步超时时间
rpl_semi_sync_master_trace_level:主库用于开启半同步复制模式时的调试级别,默认是32
rpl_semi_sync_master_wait_for_slave_count:等待semi-slave的回复个数
rpl_semi_sync_master_wait_no_slave:表示如果在半同步过程中遇到超时等情况切换后的处理方式。默认为ON,表示从机追上来后又会切换成半同步;如果为OFF则表示切换到异步后不再自动切换成半同步
rpl_semi_sync_master_wait_point:半同步方式,可选值AFTER_SYNC和AFTER_COMMIT。用来控制等待从库回复的位置。
rpl_semi_sync_slave_enabled:从库是否开启半同步
rpl_semi_sync_slave_trace_level:从库用于开启半同步复制模式时的调试级别,默认是32
mysql> show status like '%semi%';+--------------------------------------------+-------+| Variable_name | Value |+--------------------------------------------+-------+| Rpl_semi_sync_master_clients | 1 || Rpl_semi_sync_master_net_avg_wait_time | 0 || Rpl_semi_sync_master_net_wait_time | 0 || Rpl_semi_sync_master_net_waits | 17 || Rpl_semi_sync_master_no_times | 10 || Rpl_semi_sync_master_no_tx | 21 || Rpl_semi_sync_master_status | ON || Rpl_semi_sync_master_timefunc_failures | 0 || Rpl_semi_sync_master_tx_avg_wait_time | 792 || Rpl_semi_sync_master_tx_wait_time | 3960 || 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 || Rpl_semi_sync_slave_status | OFF |+--------------------------------------------+-------+15 rows in set (0.10 sec)
Rpl_semi_sync_master_clients:表示连上主库的semi-slave个数
Rpl_semi_sync_master_net_avg_wait_time:表示事务提交后,等待从库响应的平均时间
Rpl_semi_sync_master_net_wait_time:总的网络等待时间
Rpl_semi_sync_master_net_waits:等待网络响应的总次数
Rpl_semi_sync_master_no_times:表示经历多少次的半同步切换成异步的总次数
Rpl_semi_sync_master_no_tx:表示从库未及时响应的事务数
Rpl_semi_sync_master_status:表示主库上的半同步状态
Rpl_semi_sync_master_timefunc_failures:时间函数未正常工作的次数
Rpl_semi_sync_master_tx_avg_wait_time:事务等待备库返回等待的平均时间
Rpl_semi_sync_master_tx_wait_time:事务等待备库响应的总时间
Rpl_semi_sync_master_tx_waits:事务等待备库响应的总次数
Rpl_semi_sync_master_wait_pos_backtraverse:改变等待当前最小二进制日志的次数
Rpl_semi_sync_master_wait_sessions:当前存在几个线程在等待备库响应
Rpl_semi_sync_master_yes_tx:半同步模式下,成功的事务次数
Rpl_semi_sync_slave_status:表示从库的半同步状态
semi-sync的实现
从上图中,我们可以看到半同步复制有这样几个过程构成:
客户端提交事务到主库
主库写binlog,并将该事务加入到等待ACK的队列中
Dump线程将binlog日志发送给从库
从库的IO线程将信息写入到relay log后返回ACK给主库
主库返回OK信息给客户端
在semi-sync的实现过程中,主要是有这几个线程发挥作用:
- Ack_receiver线程,不断遍历slave,通过select监听slave网络包,处理semi-sync复制状态,唤醒等待线程。
- binlog Dump线程。如果slave是semi-slave,通过add_slave将slave添加到监听队列,在发送网络包时根据semi-sync运行状态设置包头的semi-sync标记。
- Slave IO线程,读取数据后后检查包头是否有semi-sync标记。在queue event后,在需要回复Master ACK报文的时候,回复Master ACK报文。
主库端
为了方便描述过程,这里默认rpl_semi_sync_master_timeout=3000,rpl_semi_sync_master_wait_for_slave_count=1,rpl_semi_sync_master_wait_no_slave=ON。
根据rpl_semi_sync_master_enabled参数决定主库端是否开启半同步复制,并根据半同步状态决定是否执行以下半同步步骤。
在从库创建主从连接时,会携带自身是否输入semi-slave的信息一同发送给主库;主库根据携带的信息判断该从是不是semi-slave,如果是,则Rpl_semi_sync_master_clients加一,表示从库有几个是semi-slave。并将该semi-slave添加到ack queue队列中。Ack_receiver线程会不断的扫描这个队列,并处理semi-salve的复制状态。
主库通过一个HASH表记录当前实例所有提交事务的结束位置,在后面的过程中会判断发送的Binlog是不是一个事务的结束。
根据rpl_semi_sync_master_wait_point参数决定是采用AFTER_SYNC还是AFTER_COMMIT方式进行半同步。这两种方式的区别就在于等待semi-slave回复信息的位置不一样。
主库会维护一个semi-slave回复ACK的Binlog位置信息的数组,该数组长度为rpl_semi_sync_master_wait_for_slave_count;在发送Binlog之前,对当前Binlog的位置和该数组中的值进行比较。如果当前发送的Binlog位置比该数组中的最小值还小,则不需要从库返回ACK。这是因为该Binlog信息已经保证有从库已经备份了。
主库会在满足以下几个条件的Binlog信息中打上需要从库返回ACK的标记:1.当前发送的binlog是一个事务的结束位置;2.当前发送的binlog并从库返回过ACK的binlog位置信息大;3.不存在一个更大的binlog位置事务在等待
在将包含需要semi-slave返回ACK的Binlog发送给从库后,线程会开始进入等待时期,此时Rpl_semi_sync_master_wait_sessions会加一。
在等待rpl_semi_sync_master_timeout秒没有semi-slave进行回复(超时)或者正常收到semi-slave的ACK回复后,Rpl_semi_sync_master_wait_sessions减一。并相应的更新Rpl_semi_sync_master_tx_wait_time等半同步状态信息。
返回客户端成功信息。
从库端
在第一次连接时,会根据rpl_semi_sync_slave_enabled告诉主库自己是不是一个semi-slave。
从库接收到主库发送过来的Binlog,解析该Binlog是否需要返回ACK信息
将该Binlog写入到本地的relay-log,如果是需要返回ACK的Binlog,则返回ACK给主库
测试记录
网上有很多文章说需要满足以下两个条件才算开启半同步
主库开启了rpl_semi_sync_master_enabled
至少存在一个semi-slave
但是我觉得这样的说法并不准确,以下是我测试的过程:
只启动一个实例,开启rpl_semi_sync_master_enabled,没有从库。此时日志是会打印出开启半同步的日志。
执行一条插入语句,发现需要执行10秒才能执行完,此时日志打印半同步超时,转变成异步复制。
再执行一条插入语句,发现能够马上执行成功,说明此时是异步复制
mysql> insert into t(name,age) values('xx',22);Query OK, 1 row affected (10.13 sec)mysql> insert into t(name,age) values('xx',22);Query OK, 1 row affected (0.00 sec)2019-04-11T17:17:58.504120+08:00 167 [Note] Semi-sync replication initialized for transactions.2019-04-11T17:17:58.504187+08:00 167 [Note] Semi-sync replication enabled on the master.2019-04-11T17:17:58.504397+08:00 0 [Note] Starting ack receiver thread2019-04-11T17:20:19.049529+08:00 167 [Warning] Timeout waiting for reply of binlog (file: mysql-bin.000005, pos: 38311), semi-sync up to file , position 0.2019-04-11T17:20:19.049589+08:00 167 [Note] Semi-sync replication switched OFF.
总结:这个过程说明,即使是不存在semi-slave,半同步也是会开启的;只是在执行第一条语句后发现超时后,才会从半同步转变成异步。
扩展一:如果此时连上一个semi-slave会怎样?
步骤:配置一个半同步从,并从最新的位置开始创建复制。当此半同步从连接到主库后,主库日志会打印以下日志:
2019-04-11T19:32:22.309449+08:00 171 [Note] Start binlog_dump to master_thread_id(171) slave_server(219235864), pos(mysql-bin.000005, 41828)2019-04-11T19:32:22.309645+08:00 171 [Note] Semi-sync replication switched ON at (mysql-bin.000005, 41828)2019-04-11T19:32:22.309738+08:00 171 [Note] Start semi-sync binlog_dump to slave (server_id: 219235864), pos(mysql-bin.000005, 41828)
通过查看主库的Rpl_semi_sync_master_status状态,发现其自动由OFF切换成了ON
总结:如果主库处于半同步切换成异步的状态,当满足半同步的条件时,又会切换回半同步复制。这个条件就是:有rpl_semi_sync_master_wait_for_slave_count个semi-slave追赶上了主库的进度。
扩展二:如果semi-slave网络超时,会存在哪些现象:
步骤:在从库通过iptables限制网络连接模拟断网情况。会出现以下现象:
从库机器ping不通主库
没有binlog产生前,半同步状态正常
执行一条DML,主库会阻塞直到半同步超时,此时主库半同步转异步
执行多条DML后,关闭iptables限制,发现主库又切换成半同步
在整个过程中,主库上的复制线程连接一直存在
如果设置的rpl_semi_sync_master_wait_for_slave_count大于semi-slave个数会如何处理?
将rpl_semi_sync_master_wait_for_slave_count设置为2,但是只连接了一个semi-slave。执行DML,发现主库会阻塞超时,切换成异步复制。
有什么疑问或者好的建议可以加入我们的微信群交流。






