MySQL 8 复制(二)——半同步复制

Johnny21
• 阅读 1300

目录

一、简介

二、性能提升

1. 支持发送二进制日志事件和接收ACK的异步化

2. 控制主库接收确认反馈从库的数量

3. 二进制日志互斥锁改进

三、数据一致性

1. 源码剖析

2. rpl_semi_sync_master_wait_point的配置

3. sync_binlog的配置

4. sync_relay_log的配置

四、管理接口

五、在MySQL 8上安装配置半同步复制

1. 安装插件

2. 启用半同步复制

3. 监控半同步复制

六、测试

1. 正常提交事务

2. 回滚事务

3. rpl_semi_sync_master_wait_no_slave为ON时从库数小于rpl_semi_sync_master_wait_for_slave_count

4. rpl_semi_sync_master_wait_no_slave为OFF时从库数小于rpl_semi_sync_master_wait_for_slave_count


一、简介

    直到目前的最新版本为止,MySQL缺省依然使用异步复制策略。简单说所谓异步复制,指的是主库写二进制日志、从库的I/O线程读主库的二进制日志写本地中继日志、从库的SQL线程重放中继日志,这三步操作都是异步进行的。如此选择的主要理由是出于性能考虑,与同步复制相比,异步复制显然更快,同时能承载更高的吞吐量。但异步复制的缺点同样明显,不能保证主从数据实时一致,也无法控制从库的延迟时间,因此它不适于要求主从数据实时同步的场景。例如,为了分解读写压力,同一程序写主库读从库,但要求读到的数据与读主库的相同,异步复制不满足这种强数据一致性需求。异步复制的另一个问题是可能会有数据丢失,例如主库宕机时,已经提交的事务可能还没有传到从库上,如果此时强行主从切换,可能导致新主库上的数据不完整。

    于是在MySQL在5.5中就顺其自然地引入了半同步复制,可用作异步复制的替代方案,它具有以下特性:
  • 从库在连接主库时表明它是否支持半同步复制。

  • 如果在主库启用了半同步复制,并且至少有一个支持半同步复制的从库,则主库上执行事务提交的线程将等待,直到至少一个半同步从库确认已收到事务的所有事件(此时从库会向主库发送ACK,Acknowledgement),或者直到发生超时。

  • 只有在将事件写入其中继日志并刷新到磁盘后,从库才会确认收到事务的事件,向主库发送ACK。

  • 如果在没有任何从库确认事务的情况下发生超时,则主库将退化为异步复制。当至少有一个半同步从库赶上时,主库恢复半同步复制。

  • 必须在主库和从库都启用半同步复制,否则使用异步复制。

    当主库阻塞(等待来自从库确认)时,它不会返回执行事务的会话。阻塞结束时,主库返回到会话,然后该会话可以继续执行其它语句。此时,事务已在主库提交,并且至少一个从库已确认其事件的接收。在继续之前,主库必须收到的确认从库的数量可使用rpl\_semi\_sync\_master\_wait\_for\_slave\_count系统变量进行配置,默认值为1。
    
    不只是事务提交时,事务回滚时主库也会发生同样的阻塞。MySQL同时支持多个数据库引擎,当一个事务中既包含事务表又包含非事务表时,回滚即使对事务表没有影响,二进制日志中也会记录非事务表的事件,因为对非事务表的修改无法回滚并且必须发送到从库。
    
    在没以START TRANSACTION或SET autocommit = 0开启事务时,每个语句都自动隐式提交。使用半同步复制时,主库上的这类语句就像显式事务提交一样。
    
    为了加深对半同步复制中“半”的理解,简单将其与异步和全同步复制进行比较:
  • 异步复制:主库提交事务时,将事件写入它的二进制日志,而从库在准备就绪时请求它们。主库无需等待从库的ACK回复,直接提交事务并返回客户端。异步复制不确保所有事件都能到达从库,无法保证数据完整性。

  • 全同步复制:当主库提交事务时,所有从库也将在主库返回执行事务的会话之前提交事务。这样做的缺点是完成事务可能会有很大延迟。

  • 半同步复制:介于异步和完全同步复制之间。主库仅等待至少一个从库接收并记录事件。它不会等待所有从库确认收到,并且从库只需要确认接收,而不是事件已在从库完全执行和提交。

    与异步复制相比,半同步复制提供了改进的数据完整性,因为当提交成功返回时,已知数据至少存在于两个位置。但半同步复制确实会对性能产生一些影响,因为需要等待从库,提交速度会变慢,延迟至少是将提交发送到从库并等待从库确认收到的TCP/IP往返时间。这意味着半同步复制最好在低延时的网络中使用。

二、性能提升

1. 支持发送二进制日志事件和接收ACK的异步化

    旧版本的半同步复制受限于Binlog Dump线程,原因是该线程承担了两份不同且又十分频繁的任务:传送二进制日志事件给从库 ;接收从库的ACK反馈信息。这两个任务是串行的,Binlog Dump线程必须等待从库返回之后才会传送下一个事件。Binlog Dump线程已然成为整个半同步复制性能的瓶颈。在高并发业务场景下,这样的机制会影响数据库整体的TPS。单一Binlog Dump线程发送接收的工作流程如图1所示。

MySQL 8 复制(二)——半同步复制

图1 单一Binlog Dump线程发送接收的工作流程

    为了解决上述问题,在5.7.4版本的半同步复制框架中,独立出一个Ack Receiver线程 ,专门用于接收从库返回的ACK请求,这将之前Binlog Dump线程的发送和接收工作分为了两个线程来处理。这样主库上有两个线程独立工作,可以同时发送二进制日志事件到从库,和接收从库的ACK信息。因此半同步复制得到了极大的性能提升。Binlog Dump线程与Ack Receiver线程工作流程如图2所示。

MySQL 8 复制(二)——半同步复制

图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 connection

  • Waiting for semi-sync ACK from slave

  • Reading 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句柄数理论上没有上限了,原因是它是基于链表来存储的。

2. 控制主库接收确认反馈从库的数量

    MySQL 5.7新增了rpl\_semi\_sync\_master\_wait\_for\_slave\_count系统变量,可以用来控制主库接收多少个从库写事务成功反馈,给高可用架构切换提供了灵活性。如图3所示,当该变量值为2时,主库需等待两个从库的ACK。

MySQL 8 复制(二)——半同步复制

图3 一主两从的半同步复制

    使用这个功能,可以在不同机房部署主服务器和两个从服务器,并配置半同步复制以将事务复制到至少两个从库,以便在多个服务器一次性崩溃的情况下减少数据丢失的可能,从库越多,数据越安全。

3. 二进制日志互斥锁改进

    旧版本半同步复制在主库提交二进制日志的写会话和Binlog Dump线程读取二进制日志的操作都会对二进制日志添加binlog lock互斥锁,用于保护二进制日志的读写操作。使用此互斥锁,二进制日志读写操作是安全的,但会导致二进制日志文件的读写串行化。不仅Binlog Dump线程和用户会话不能同时读写二进制日志,就连多个Binlog Dump线程本身也无法同时读取。每当一个会话正在读取或写入二进制日志文件时,所有其它会话都必须等待。如此顺序读写可能是一个瓶颈,尤其是当读写操作很慢时。串行化读写二进制日志如图4所示。

MySQL 8 复制(二)——半同步复制

图4 串行化读写二进制日志

    MySQL 5.7.2对binlog lock进行了以下两方面优化:
  • 从Binlog Dump线程中移除binlog lock。

  • 加入了安全边际保证二进制日志的读安全。

    二进制日志文件看起来像一个仅追加的日志文件,可以安全地读取没有锁定的二进制事件。因此从Binlog Dump线程中删除了binlog锁。不使用binlog锁,而是为活动binlog维护安全读取边界(最大位置)。Binlog Dump线程永远不会读取超过安全读取边界的位置。当边界到达边界时,它将等待边界更新。用户会话负责在追加了二进制事件后更新安全读取边界。改进后的二进制日志读写如图5所示。

MySQL 8 复制(二)——半同步复制

图5 改进后的二进制日志读写

    从图中可知:
  • 读取二进制日志事件时,Binlog Dump线程不会相互阻塞。

  • 正在写二进制日志事件的用户会话不会阻止Binlog Dump线程。

  • 读取二进制日志事件的Binlog Dump不会阻塞用户会话。

    因此,Binlog Dump线程和用户会话都可以获得更好的吞吐量,尤其是当有很多从库时,这种改进非常显着。

三、数据一致性

    这一部分节选自阿里系的一篇文章,原文地址[http://mysql.taobao.org/monthly/2017/04/01/](http://mysql.taobao.org/monthly/2017/04/01/)。文中从MySQL源代码的层面分析了半同步复制的数据一致性。如果不看源码,很难搞清楚这个复杂的问题。

1. 源码剖析

    以下源码版本均为官方MySQL 5.7。MySQL semi-sync是以插件方式引入,在plugin/semisync目录下。这里以semi-sync主要的函数调用为入口,学习semi-sync源码。
plugin/semisync/semisync_master.cc
403 /*******************************************************************************
404  *
405  * <ReplSemiSyncMaster> class: the basic code layer for sync-replication master.
406  * <ReplSemiSyncSlave>  class: the basic code layer for sync-replication slave.
407  *
408  * The most important functions during semi-syn replication listed:
409  *
410  * Master:
          //实际由Ack_receiver线程调用,处理semi-sync复制状态,获取备库最新binlog位点,唤醒对应线程
411  *  . reportReplyBinlog():  called by the binlog dump thread when it receives
412  *                          the slave's status information.
          //根据semi-sync运行状态设置数据包头semi-sync标记
413  *  . updateSyncHeader():   based on transaction waiting information, decide
414  *                          whether to request the slave to reply.
          //存储当前binlog 文件名和偏移量,更新当前最大的事务 binlog 位置
415  *  . writeTranxInBinlog(): called by the transaction thread when it finishes
416  *                          writing all transaction events in binlog.
          //实现客户端同步等待逻辑
417  *  . commitTrx():          transaction thread wait for the slave reply.
418  *
419  * Slave:
          //确认网络包头是否有semi-sync标记
420  *  . slaveReadSyncHeader(): read the semi-sync header from the master, get the
421  *                           sync status and get the payload for events.
          //给Master发送ACK报文
422  *  . slaveReply():          reply to the master about the replication progress.
423  *
424  ******************************************************************************/

Ack_receiver线程,不断遍历slave,通过select监听slave网络包,处理semi-sync复制状态,唤醒等待线程。 plugin/semisync/semisync_master_ack_receiver.cc Ack_receiver::run() ->plugin/semisync/semisync_master.cc ReplSemiSyncMaster::reportReplyPacket ->plugin/semisync/semisync_master.cc ReplSemiSyncMaster::reportReplyBinlog

binlog Dump线程。如果slave是semi-slave,通过add_slave将slave添加到监听队列,在发送网络包时根据semi-sync运行状态设置包头的semi-sync标记。 sql/rpl_binlog_sender.cc Binlog_sender::run() ->sql/rpl_binlog_sender.cc Binlog_sender::send_binlog ->sql/rpl_binlog_sender.cc Binlog_sender::send_events ->sql/rpl_binlog_sender.cc Binlog_sender::before_send_hook ->plugin/semisync/semisync_master_plugin.cc repl_semi_before_send_event ->plugin/semisync/semisync_master.cc ReplSemiSyncMaster::updateSyncHeader

事务提交阶段,在flush binlog后,存储当前binlog 文件名和偏移量,更新当前最大的事务 binlog 位置。 sql/binlog.cc MYSQL_BIN_LOG::ordered_commit ->plugin/semisync/semisync_master_plugin.cc repl_semi_report_binlog_update//after_flush ->plugin/semisync/semisync_master.cc repl_semisync.writeTranxInBinlog

事务提交阶段,客户端等待处理逻辑,分为after_sync和after_commit两种情况 sql/binlog.cc MYSQL_BIN_LOG::ordered_commit ->sql/binlog.cc process_after_commit_stage_queue || call_after_sync_hook ->plugin/semisync/semisync_master_plugin.cc repl_semi_report_commit || repl_semi_report_binlog_sync ->plugin/semisync/semisync_master.cc ReplSemiSyncMaster::commitTrx

Slave IO线程,读取数据后后检查包头是否有semi-sync标记。 sql/rpl_slave.cc handle_slave_io ->plugin/semisync/semisync_slave_plugin.cc repl_semi_slave_read_event ->plugin/semisync/semisync_slave.cc ReplSemiSyncSlave::slaveReadSyncHeader

Slave IO线程,在queue event后,在需要回复Master ACK报文的时候,回复Master ACK报文。 sql/rpl_slave.cc handle_slave_io ->plugin/semisync/semisync_slave_plugin.cc repl_semi_slave_queue_event ->plugin/semisync/semisync_slave.cc ReplSemiSyncSlave::slaveReply


        首先半同步方式,主库在等待备库ack时候,如果超时会退化为异步,这就可能导致数据丢失。在接下来分析中,先假设rpl\_semi\_sync\_master\_timeout足够大,不会退化为异步方式。这里通过三个参数rpl\_semi\_sync\_master\_wait\_point、sync\_binlog、sync\_relay\_log的配置来对semi-sync做数据一致性的分析。

2\. rpl\_semi\_sync\_master\_wait\_point的配置
-------------------------------------------

(1)源码剖析

plugin/semisync/semisync_master_plugin.cc

68 int repl_semi_report_binlog_sync(Binlog_storage_param *param, 69 const char *log_file, 70 my_off_t log_pos) 71 { 72 if (rpl_semi_sync_master_wait_point == WAIT_AFTER_SYNC) 73 return repl_semisync.commitTrx(log_file, log_pos); 74 return 0; 75 }

97 int repl_semi_report_commit(Trans_param *param) ... 102 if (rpl_semi_sync_master_wait_point == WAIT_AFTER_COMMIT && 106 return repl_semisync.commitTrx(binlog_name, param->log_pos);


(2)配置为WAIT\_AFTER\_COMMIT

![](https://img-hello-world.oss-cn-beijing.aliyuncs.com/051c57d1365ce00ce9babd2181c9e975.png)

图6

        rpl\_semi\_sync\_master\_wait\_point为WAIT\_AFTER\_COMMIT时,commitTrx的调用在engine层commit之后(在ordered\_commit函数中process\_after\_commit\_stage\_queue调用),如图6所示。即在等待Slave ACK时候,虽然没有返回当前客户端,但事务已经提交,其他客户端会读取到已提交事务。如果Slave端还没有读到该事务的events,同时主库发生了crash,然后切换到备库。那么之前读到的事务就不见了,出现了幻读,如图7所示。图片引自[Loss-less Semi-Synchronous Replication on MySQL 5.7.2](https://my-replication-life.blogspot.com/2013/09/loss-less-semi-synchronous-replication.html)。

        (补充一点。除了幻读,这种场景还有一个问题是,如果客户端会重试提交该事务到新的主上,当宕机的主库重新启动后,以从库的身份重新加入到该主从结构中。此时会发现,该事务在从库中被提交了两次,一次是之前作为主的时候,一次是被新主同步过来的,结果依然是主从数据不一致。)

![](https://img-hello-world.oss-cn-beijing.aliyuncs.com/a104e9ffb2f0ab583f1942f5c897d7b6.png)

图7

(2)配置为WAIT\_AFTER\_SYNC  
        MySQL针对上述问题,在5.7.2引入了Loss-less Semi-Synchronous,在调用binlog sync之后,engine层commit之前等待Slave ACK。这样只有在确认Slave收到事务events后,事务才会提交。在commit之前等待Slave ACK,同时可以堆积事务,利于group commit,有利于提升性能。如图8所示,图片引自[Loss-less Semi-Synchronous Replication on MySQL 5.7.2](https://my-replication-life.blogspot.com/2013/09/loss-less-semi-synchronous-replication.html)。

![](https://img-hello-world.oss-cn-beijing.aliyuncs.com/0d343ea51ae246b84e450108ebd9cec6.png)

图8

        其实上图流程中存在着会导致主备数据不一致,使主备同步失败的情形。见下面sync\_binlog配置的分析。

3\. sync\_binlog的配置
-------------------

(1)源码剖析

sql/binlog.cc ordered_commit //当sync_period(sync_binlog)为1时,在sync之后update binlog end pos 9002 update_binlog_end_pos_after_sync= (get_sync_period() == 1); ... 9021 if (!update_binlog_end_pos_after_sync) //更新binlog end position,dump线程会发送更新后的events 9022 update_binlog_end_pos(); ... // 9057 std::pair<bool, bool> result= sync_binlog_file(false); ... 9061 if (update_binlog_end_pos_after_sync) 9062 { ... 9068 update_binlog_end_pos(tmp_thd->get_trans_pos()); 9069 }

sql/binlog.cc sync_binlog_file 8618 std::pair<bool, bool> 8619 MYSQL_BIN_LOG::sync_binlog_file(bool force) 8620 { 8621 bool synced= false; 8622 unsigned int sync_period= get_sync_period();//sync_binlog值 //sync_period为0不做sync操作,其他值为达到sync调用次数后sync 8623 if (force || (sync_period && ++sync_counter >= sync_period)) 8624 {


(2)配置分析  
        当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的情况。根据上面源码,流程如图9所示。Master依次执行flush binlog,update binlog position,sync binlog。如果Master在update binlog position后,sync binlog前掉电,Master再次启动后原事务就会被回滚。但可能出现Slave获取到Events,这也会导致Slave数据比Master多,主备同步失败。

![](https://img-hello-world.oss-cn-beijing.aliyuncs.com/41c3fb3bfbcb54c5ca470b0b1edae71f.png)

图9

        由于上面的原因,sync\_binlog设置为1的时候,MySQL会update binlog end pos after sync。流程如图10所示。这时候,对于每一个事务都需要sync binlog,同时sync binlog和网络发送events会是一个串行的过程,性能下降明显。

![](https://img-hello-world.oss-cn-beijing.aliyuncs.com/80d2566fc31eb4df4f9edacdff0f9e7d.png)

图10

4\. sync\_relay\_log的配置
-----------------------

(1)源码剖析

sql/rpl_slave.cc handle_slave_io

5764 if (queue_event(mi, event_buf, event_len)) ... 5771 if (RUN_HOOK(binlog_relay_io, after_queue_event, 5772 (thd, mi, event_buf, event_len, synced)))

after_queue_event ->plugin/semisync/semisync_slave_plugin.cc repl_semi_slave_queue_event ->plugin/semisync/semisync_slave.cc ReplSemiSyncSlave::slaveReply

queue_event ->sql/binlog.cc MYSQL_BIN_LOG::append_buffer(const char* buf, uint len, Master_info *mi) ->sql/binlog.cc after_append_to_relay_log(mi); ->sql/binlog.cc flush_and_sync(0) ->sql/binlog.cc sync_binlog_file(force)


(2)配置分析  
        在Slave的IO线程中get\_sync\_period获得的是sync\_relay\_log的值,与sync\_binlog对sync控制一样。当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。

![](https://img-hello-world.oss-cn-beijing.aliyuncs.com/453782b4e68b7a7d004c6bea21a34c6e.png)

        但当Slave再次启动,如果没有来得及从Master端同步丢失的事务Events,Master就Crash。这个时候,用户访问Slave就会发现数据丢失。

![](https://img-hello-world.oss-cn-beijing.aliyuncs.com/04a36baada3de65dcc6a65ed38fe43e8.png)

        (我们可以发现当前原生的MySQL主从复制实现要同时满足数据一致性、高可用和高性能,依然是力有不逮。)

四、管理接口
======

        这里所说的半同步复制管理接口包含相关插件和变量。  
(1)两个插件,主库端的semisync\_master.so和从库端的semisync\_slave.so,实现半同步复制功能。  
(2)系统变量控制插件行为,例如:

*   rpl\_semi\_sync\_master\_enabled:控制是否在主库上启用半同步复制。要启用或禁用插件,将此变量分别设置为1或0,默认值为0(关闭)。
*   rpl\_semi\_sync\_master\_timeout:一个以毫秒为单位的值,用于控制主库在超时并退化到异步复制之前等待来自从库确认提交的时间。默认值为10000(10秒)。
*   rpl\_semi\_sync\_slave\_enabled:与rpl\_semi\_sync\_master\_enabled类似,控制启用从库的插件。

(3)状态变量用来反映半同步复制的状态信息,例如:

*   Rpl\_semi\_sync\_master\_clients:半同步从库的数量。
*   Rpl\_semi\_sync\_master\_status:半同步复制当前是否在主库上运行。如果已启用插件且未发生提交确认,则该值为ON。如果未启用插件,或者由于提交确认超时,主服务器已回退到异步复制,则为OFF。
*   Rpl\_semi\_sync\_master\_no\_tx:从库未成功确认的事务数。
*   Rpl\_semi\_sync\_master\_yes\_tx:从库成功确认的事务数。
*   Rpl\_semi\_sync\_slave\_status:半同步复制当前是否在从库上运行。如果插件已启用且从库的I/O线程正在运行,则此值为ON,否则为OFF。

        仅当使用INSTALL PLUGIN安装了相应的插件时,系统和状态变量才可用。

五、在MySQL 8上安装配置半同步复制
====================

 **实验环境:**  
        主机IP  
        172.16.1.125(主)  
        172.16.1.126(从)  
        172.16.1.127(从)

        软件环境  
        OS:CentOS Linux release 7.2.1511 (Core)  
        MySQL:MySQL Community Server 8.0.16  
        glibc:glibc-2.17-105.el7.x86\_64

       硬件环境  
       三台虚拟机,每台基本配置为:

*   双核双CPU,Intel(R) Xeon(R) CPU E5-2420 0 @ 1.90GHz
*   8G物理内存,8G Swap
*   100G物理硬盘

        三台主机已经配置了一主两从的异步复制,参见“[MySQL 8 复制(一)——异步复制](https://wxy0327.blog.csdn.net/article/details/90081518)”。

1\. 安装插件
--------

        半同步复制是使用插件实现的,因此必须将插件安装到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](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)


        主相关系统变量:

*   rpl\_semi\_sync\_master\_enabled:主库是否启用了半同步复制,默认为OFF。
*   rpl\_semi\_sync\_master\_timeout:等待从库的ACK回复的超时时间,默认为10秒。
*   rpl\_semi\_sync\_master\_trace\_level:半同步复制时主库的调试级别。
*   rpl\_semi\_sync\_master\_wait\_for\_slave\_count:主库在超时时间内需要收到多少个ACK回复才认为此次提交成功,否则就降级为异步复制。该变量在MySQL 5.7.3才提供,在此之前的版本都默认为收到1个ACK则确认成功,且不可更改。MySQL 5.7.3之后该变量的默认值也是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,只有当事务提交后等待rpl\_semi\_sync\_master\_timeout超时后,Rpl\_semi\_sync\_master\_status才会变为OFF,即降级为异步复制;为OFF时,当状态变量Rpl\_semi\_sync\_master\_clients中的值小于rpl\_semi\_sync\_master\_wait\_for\_slave\_count时,Rpl\_semi\_sync\_master\_status立即显示为OFF,即立即降级为异步复制。
*   rpl\_semi\_sync\_master\_wait\_point:控制主库上commit、接收ack、返回消息给客户端的时间点。值为 AFTER\_SYNC 或 AFTER\_COMMIT。该选项是MySQL5.7.2后引入的,默认值为 AFTER\_SYNC。此版本之前,等价于使用了 AFTER\_COMMIT 模式。

        从相关系统变量:

*   rpl\_semi\_sync\_slave\_enabled:从库是否开启半同步复制。
*   rpl\_semi\_sync\_slave\_trace\_level:从库的调试级别。

2\. 启用半同步复制
-----------

        安装半同步复制插件后,默认情况下会禁用它。必须在主库和从库都启用插件才能启用半同步复制。如果仅启用一侧,则复制将是异步的。(1)启用半同步复制

-- 主 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


(2)重启从库上的I/O线程

mysql> stop slave io_thread; mysql> start slave io_thread;


        如果没有重启,则默认还是异步复制。重启后,从库会在主库上注册为半同步复制的从库角色。  
(3)查看半同步是否在运行

-- 主 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半同步复制搭建完毕。

3\. 监控半同步复制
-----------

        半同步复制功能的插件公开了几个状态变量,可以检查这些变量以确定其操作状态。这些变量仅当安装了半同步复制插件以后才可用。

-- 主 mysql> show status like 'rpl_semi_sync%'; +--------------------------------------------+-------+ | 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 | 0 | | Rpl_semi_sync_master_no_times | 0 | | Rpl_semi_sync_master_no_tx | 0 | | Rpl_semi_sync_master_status | ON | | 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.00 sec)

-- 从 mysql> show status like 'rpl_semi_sync%'; +----------------------------+-------+ | Variable_name | Value | +----------------------------+-------+ | Rpl_semi_sync_slave_status | ON | +----------------------------+-------+ 1 row in set (0.01 sec)


        从库上只有一个半同步复制相关的状态变量Rpl\_semi\_sync\_slave\_status,为ON时表示从库使用半同步复制,OFF表示从库使用异步复制。

        主库有14个半同步复制相关的状态变量:

*   Rpl\_semi\_sync\_master\_clients:当前连接了多少个半同步从库。
*   Rpl\_semi\_sync\_master\_net\_avg\_wait\_time:主库等待从库回复的平均时间,以微秒为单位。此变量始终为0,不推荐使用,并且将在以后的版本中删除。
*   Rpl\_semi\_sync\_master\_net\_wait\_time:主库等待从库回复的总时间,以微秒为单位。此变量始终为0,不推荐使用,并且将在以后的版本中删除。
*   Rpl\_semi\_sync\_master\_net\_waits:主库等待从库回复的总次数。
*   Rpl\_semi\_sync\_master\_no\_times:主库关闭半同步复制的次数。
*   Rpl\_semi\_sync\_master\_no\_tx:从库未成功确认的事务数。
*   Rpl\_semi\_sync\_master\_status:为ON时表示主库使用半同步复制,OFF表示主库使用异步复制。
*   Rpl\_semi\_sync\_master\_timefunc\_failures:调用gettimeofday等时间函数时主库失败的次数。
*   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:从库成功确认的事务数。

六、测试
====

1\. 正常提交事务
----------

-- 主 mysql> create database test; Query OK, 1 row affected (0.00 sec)

mysql> use test; Database changed mysql> create table test.t1 (a int) engine=innodb; Query OK, 0 rows affected (0.02 sec)

mysql> insert into t1 values(1); Query OK, 1 row affected (0.02 sec)

mysql> show status like 'rpl_semi_sync%'; +--------------------------------------------+-------+ | 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 | 6 | | Rpl_semi_sync_master_no_times | 0 | | Rpl_semi_sync_master_no_tx | 0 | | Rpl_semi_sync_master_status | ON | | Rpl_semi_sync_master_timefunc_failures | 0 | | Rpl_semi_sync_master_tx_avg_wait_time | 758 | | Rpl_semi_sync_master_tx_wait_time | 2274 | | Rpl_semi_sync_master_tx_waits | 3 | | Rpl_semi_sync_master_wait_pos_backtraverse | 0 | | Rpl_semi_sync_master_wait_sessions | 0 | | Rpl_semi_sync_master_yes_tx | 3 | +--------------------------------------------+-------+ 14 rows in set (0.00 sec)

mysql>

-- 从 mysql> select * from test.t1; +------+ | a | +------+ | 1 | +------+ 1 row in set (0.00 sec)


        MySQL 8缺省是每条语句自动提交的。主库等待3个事务的确认,分别对应create database、create table、insert语句。平均每个事务等待758微妙,从库正常确认了3个事务。

2\. 回滚事务
--------

-- 主 mysql> show variables like 'autocommit'; +---------------+-------+ | Variable_name | Value | +---------------+-------+ | autocommit | OFF | +---------------+-------+ 1 row in set (0.01 sec)

mysql> set session autocommit=0; -- 关闭自动提交,开启事务 Query OK, 0 rows affected (0.00 sec)

mysql> insert into t1 values(2); -- 向事务表插入记录 Query OK, 1 row affected (0.00 sec)

mysql> create table t2 (a int) engine=myisam; -- 执行一个DDL语句,创建非事务表t2 Query OK, 0 rows affected (0.01 sec)

mysql> insert into t1 values(3); -- 向事务表插入记录 Query OK, 1 row affected (0.00 sec)

mysql> insert into t2 values(3); -- 向非事务表插入记录 Query OK, 1 row affected (0.01 sec)

mysql> rollback; -- 回滚事务 Query OK, 0 rows affected, 1 warning (0.00 sec)

mysql> show warnings; +---------+------+---------------------------------------------------------------+ | Level | Code | Message | +---------+------+---------------------------------------------------------------+ | Warning | 1196 | Some non-transactional changed tables couldn't be rolled back | +---------+------+---------------------------------------------------------------+ 1 row in set (0.00 sec)

mysql> select * from t1; +------+ | a | +------+ | 1 | | 2 | +------+ 2 rows in set (0.00 sec)

mysql> select * from t2; +------+ | a | +------+ | 3 | +------+ 1 row in set (0.00 sec)

mysql> show status like 'rpl_semi_sync%'; +--------------------------------------------+-------+ | 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 | 12 | | Rpl_semi_sync_master_no_times | 0 | | Rpl_semi_sync_master_no_tx | 0 | | Rpl_semi_sync_master_status | ON | | Rpl_semi_sync_master_timefunc_failures | 0 | | Rpl_semi_sync_master_tx_avg_wait_time | 691 | | Rpl_semi_sync_master_tx_wait_time | 4149 | | Rpl_semi_sync_master_tx_waits | 6 | | Rpl_semi_sync_master_wait_pos_backtraverse | 0 | | Rpl_semi_sync_master_wait_sessions | 0 | | Rpl_semi_sync_master_yes_tx | 6 | +--------------------------------------------+-------+ 14 rows in set (0.00 sec)

mysql>

-- 从 mysql> show variables like 'autocommit'; +---------------+-------+ | Variable_name | Value | +---------------+-------+ | autocommit | ON | +---------------+-------+ 1 row in set (0.01 sec)

mysql> melect * from test.t1; +------+ | a | +------+ | 1 | | 2 | +------+ 2 rows in set (0.00 sec)

mysql> select * from test.t2; +------+ | a | +------+ | 3 | +------+ 1 row in set (0.01 sec)

mysql>


        从上面的测试可以得出以下结论:

*   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);三个事务。

3\. rpl\_semi\_sync\_master\_wait\_no\_slave为ON时从库数小于rpl\_semi\_sync\_master\_wait\_for\_slave\_count
-----------------------------------------------------------------------------------------------------

-- 关闭两个从库的复制 mysql> stop slave; Query OK, 0 rows affected (0.01 sec)

mysql> show status like 'rpl_semi_sync%'; -- 查看当前是否启用半同步复制 +----------------------------+-------+ | Variable_name | Value | +----------------------------+-------+ | Rpl_semi_sync_slave_status | OFF | +----------------------------+-------+ 1 row in set (0.00 sec)

mysql>

-- 主 mysql> show status like 'Rpl_semi_sync_master_clients'; -- 查看当前半同步从库数 +------------------------------+-------+ | Variable_name | Value | +------------------------------+-------+ | Rpl_semi_sync_master_clients | 0 | +------------------------------+-------+ 1 row in set (0.00 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)

mysql> insert into t1 values(3); Query OK, 1 row affected (0.00 sec)

mysql> commit; Query OK, 0 rows affected (10.00 sec)

mysql> select * from t1; +------+ | a | +------+ | 1 | | 2 | | 3 | +------+ 3 rows in set (0.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)

mysql>


        可以看到,主库提交后等待了10秒(rpl\_semi\_sync\_master\_timeout的缺省值)才完成,而且此时主库已经降级为异步复制。

-- 启动一个从库的复制 mysql> start slave; Query OK, 0 rows affected (0.00 sec)

mysql> select * from test.t1; +------+ | a | +------+ | 1 | | 2 | | 3 | +------+ 3 rows in set (0.00 sec)

mysql> show status like 'rpl_semi_sync%'; +----------------------------+-------+ | Variable_name | Value | +----------------------------+-------+ | Rpl_semi_sync_slave_status | ON | +----------------------------+-------+ 1 row in set (0.01 sec)

mysql>

-- 主 mysql> show variables like 'rpl_semi_sync_master_wait_for_slave_count'; +-------------------------------------------+-------+ | Variable_name | Value | +-------------------------------------------+-------+ | rpl_semi_sync_master_wait_for_slave_count | 1 | +-------------------------------------------+-------+ 1 row in set (0.00 sec)

mysql> show status like 'Rpl_semi_sync_master_clients'; +------------------------------+-------+ | Variable_name | Value | +------------------------------+-------+ | Rpl_semi_sync_master_clients | 1 | +------------------------------+-------+ 1 row in set (0.00 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)

mysql> insert into t1 values(4); Query OK, 1 row affected (0.00 sec)

mysql> commit; Query OK, 0 rows affected (0.01 sec)

mysql>

-- 从 mysql> select * from test.t1; +------+ | a | +------+ | 1 | | 2 | | 3 | | 4 | +------+ 4 rows in set (0.00 sec)

mysql>


        当Rpl\_semi\_sync\_master\_clients大于等于rpl\_semi\_sync\_master\_wait\_for\_slave\_count时,主库立即恢复为半同步复制,并发送那些未被复制的binlog。

4\. rpl\_semi\_sync\_master\_wait\_no\_slave为OFF时从库数小于rpl\_semi\_sync\_master\_wait\_for\_slave\_count
------------------------------------------------------------------------------------------------------

-- 关闭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)

mysql> show variables like 'rpl_semi_sync_master_wait_no_slave'; +------------------------------------+-------+ | Variable_name | Value | +------------------------------------+-------+ | rpl_semi_sync_master_wait_no_slave | OFF | +------------------------------------+-------+ 1 row in set (0.00 sec)

mysql>

-- 关闭所有从库的复制 mysql> stop slave; Query OK, 0 rows affected (0.01 sec)

mysql>

-- 主 mysql> show status like 'Rpl_semi_sync_master_clients'; -- 查看当前半同步从库数 +------------------------------+-------+ | Variable_name | Value | +------------------------------+-------+ | Rpl_semi_sync_master_clients | 0 | +------------------------------+-------+ 1 row in set (0.00 sec)

mysql> show status like 'Rpl_semi_sync_master_clients'; +------------------------------+-------+ | Variable_name | Value | +------------------------------+-------+ | Rpl_semi_sync_master_clients | 0 | +------------------------------+-------+ 1 row in set (0.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.00 sec)

mysql> insert into t1 values(5); Query OK, 1 row affected (0.00 sec)

mysql> commit; Query OK, 0 rows affected (0.00 sec)

mysql> select * from t1; +------+ | a | +------+ | 1 | | 2 | | 3 | | 4 | | 5 | +------+ 5 rows in set (0.00 sec)

mysql>

```

    可以看到,当rpl\_semi\_sync\_master\_wait\_no\_slave设置为OFF时,一旦Rpl\_semi\_sync\_master\_clients小于rpl\_semi\_sync\_master\_wait\_for\_slave\_count,Rpl\_semi\_sync\_master\_status立即变为OFF,即立即降为异步复制。  

本文转自 https://blog.csdn.net/wzy0623/article/details/90267132,如有侵权,请联系删除。

点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Jacquelyn38 Jacquelyn38
2年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Stella981 Stella981
2年前
Python3:sqlalchemy对mysql数据库操作,非sql语句
Python3:sqlalchemy对mysql数据库操作,非sql语句python3authorlizmdatetime2018020110:00:00coding:utf8'''
Easter79 Easter79
2年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Wesley13 Wesley13
2年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
2年前
MySQL主从复制异步原理以及搭建
MySQL主从复制的原理:  1、首先,MySQL主库在事务提交时会把数据变更作为时间events记录在二进制日志文件binlog中;MySQL主库上的sync\_binlog参数控制Binlog日志以什么样的方式刷新到磁盘上。  2、主库推送二进制日志文件Binlog中的事件到从库的中继日志RelayLog中,之后从库根据中继日志Rela
Stella981 Stella981
2年前
Android蓝牙连接汽车OBD设备
//设备连接public class BluetoothConnect implements Runnable {    private static final UUID CONNECT_UUID  UUID.fromString("0000110100001000800000805F9B34FB");
Wesley13 Wesley13
2年前
MYSQL数据库之主从复制及读写分离
MYSQL数据库之主从复制及读写分离一、MySQL的主从复制1、概述  MySQLReplication俗称MySQLAB复制或主从复制,是MySQL官方推荐的数据同步技术。数据同步基本过程:从库会实时去读取主库的二进制日志文件,按照日志中记录对从座进行同样的操作,以达到数据同步效果。2、MySQLReplication优点
Python进阶者 Python进阶者
2个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这