MySQL半同步复制

作者 | 周彦伟、王竹峰、强昌金等

【福利号外】留言送书啦!!本次联合博文视点为广大粉丝提供了送书活动,两位最多点赞的留言粉丝将获得《MySQL 运维内参》宝书。统计截止时间 7 月 3 日 00 时 00 分

编辑推荐

√  去哪儿DBA老大 | ACE  Director | MySQL用户组主席周彦伟领衔打造

√  源码专家强势加盟,集三大主流开源项目源码剖析与实战于一书

√  一呼百应,MySQL官方指定专家特供NoSQL | Replication独家内幕

√  DBA案头必备,覆盖服务器性能 | 集群高可用 | 自动化运维高级话题

MySQL 半同步复制
MySQL 的半同步特性发布之后,一直受到业界各大公司的青睐。因为在之前完全异步的方案中,当主库挂了之后,从库很大程度上会丢失数据,导致主从数据出现不一致,并出现数据没法修复等问题。
半同步特性

MySQL 复制默认是异步的,主库写入事务在生成 Events 并写入到 Binlog 文件之后,写入线程是不等待的,或者可以说主库并不知道从库是不是已经收到或处理了这些 Binlog。在这种情况下,如果主库挂了,有可能没有任何一个从库可以收到已经在主库提交的事务,而此时如果高可用架构将业务从主库切换到了从库,则可能会导致从库丢失主库上面发生的很多修改。

而半同步可以作为异步同步的一个替代品,半同步复制具体有以下五点特征。

  • 从库会在连接到主库时告诉主库,它是不是配置了半同步。

  • 如果半同步复制在主库端是开启了的,并且至少有一个半同步复制的从库节点,那么此时主库的事务线程在提交时会被阻塞并等待,结果有两种可能,要么至少一个从库节点通知它已经收到了所有这个事务的 Binlog 事件,要么一直等待直到超过配置的某一个时间点为止,而此时,半同步复制将自动关闭,转换为异步复制。

  • 从库节点只有在接收到某一个事务的所有 Binlog,将其写入并 Flush 到 Relay Log 文件之后,才会通知对应主库上面的等待线程。

  • 如果在等待过程中,等待时间已经超过了配置的超时时间,没有任何一个从节点通知当前事务,那么此时主库会自动转换为异步复制,当至少一个半同步从节点赶上来时,主库便会自动转换为半同步方式的复制。

  • 半同步复制必须是在主库和从库两端都开启时才行,如果在主库上没打开,或者在主库上开启了而在从库上没有开启,主库都会使用异步方式复制。

通过上面几点特征,可以知道半同步的实质是,在主库被阻塞的过程中(等待从库反馈消息),主库处理线程不会返回去处理当前事务。当阻塞被**之后,系统才会把控制权交给当前线程,然后继续处理当前事务余下的事情。处理完成之后,此时主库的事务已经提交,同时至少会有一个从库也已经收到了这个事务的 Binlog,这样就尽可能地保证了主库和从库的数据一致性。

为了弄明白半同步中的半是什么意思,这里与之前的全异步方式和全同步方式对比一下,如下。

  • 对于异步复制,主库将事务 Binlog 事件写入到 Binlog 文件中,此时主库只会通知一下 Dump 线程发送这些新的 Binlog,然后主库就会继续处理提交操作,而此时不会保证这些 Binlog 传到任何一个从库节点上。

  • 对于全同步复制,当主库提交事务之后,所有的从库节点必须收到、APPLY 并且提交这些事务,然后主库线程才能继续做后续操作。这里面一个很明显的缺点就是,主库完成一个事务的时间被拉长,性能降低。

  • 半同步复制,是介于全同步复制与全异步复制之间的一种,主库只需要等待至少一个从库节点收到并且 Flush Binlog 到 Relay Log 文件即可,主库不需要等待所有从库给主库反馈。同时,这里只是一个收到的反馈,而不是已经完全执行并且提交的反馈,这样就节省了很多时间。

相比异步复制,半同步复制提高了数据完整性,因为很明确地知道,在一个事务提交成功之后,这个事务就至少会存在于两个地方。

半同步复制对集群整体的性能会有一些影响,因为事务提交操作由于对从库节点反馈的等待而变慢了,当然这也是对提高数据完整性的一种权衡。变慢时间的长短至少是 TCP/IP 的一次发送与接收所用的时间,因为它需要首先将 Binlog 发送出去,然后再等待从库给主库反馈消息。这也就意味着,半同步复制在网络状况好且主从主机距离较近的情况下,比网络状况不好且主从主机距离较远的情况下能够工作地更好。

上面就是半同步复制的大概原理及特性,下面就深入源码层面,对半同步的实现一探究竟,下面几节内容主要是想说明,半同步插件在开启之后,究竟是如何起作用、在哪里起作用、在每个位置的具体作用是什么。希望在看完上面的对比介绍之后,这些问题可以一清二楚。

半同步主库端

现在已经知道,在半同步环境下,主库提交任何事务,都会等待从库的反馈(ACK),而在什么时间去等待,这是一个很关键的问题。同时,半同步的实现,是通过插件的方式实现的,这个可以从安装方面看出来。那可能有些同学会问道,安装一个插件,如何能影响到 MySQL 的行为呢?这个问题就关系到了 MySQL 实现插入式的功能时所考虑的问题,简单而言,就是通过在一个事务处理过程中,在不同阶段设置“桩”的方式来实现插入式功能。在 MySQL 5.7 版本出来之后,我们现在熟悉的“桩”包括 after_flush 和 after_sync。在执行到某一个桩时,如果有对应的额外行为,就会去执行这些操作;如果没有,就会直接跳过。下面要讲的,就是以“桩”为切入点,分别讲述 MySQL 线程执行的过程中,会有哪些桩,分别是什么作用等。

下面要讲的内容中,还包括了 MySQL 源代码的一些东西,这是为了方便一些对源码感兴趣的同学找到对应的函数实现等。

 after_flush

  • 源码调用入口函数:ordered_commit,这也是本书所讲的关于“MySQL 5.7 多线程复制”中讲到的组提交的代码实现。

  • 阶段说明:时机是在事务提交之前,Flush Binlog 之后,目的是取事务的结尾。因为已经做了 Flush,所以事务对应的 Binlog 结束位置就可以知道了。

  • 半同步回调函数:repl_semi_report_binlog_update。

  • 详细说明:这个桩主要是用来处理每一个事务的 Binlog 位置信息的。首先根据每一个提交事务对应的结束位置,保存当前实例中已经做了 Flush 操作的事务中最大的 Binlog 位置。然后,通过一个 HASH 表,记录下当前实例中所有提交事务的结束位置,所记录的每条信息都是由一个文件名和 POSITION 组成,缓存这些信息的目的是在发送 Binlog 时判断要不要等到从库的 ACK,因为只有发送一个事务的最后一个 Binlog 时,才需要在发送给从库的 Binlog 中打标志,告诉从库这个 Event 需要接收 ACK,而不是每一个发送给从库的 Event 都需要得到 ACK。

 after_sync

  • 源码调用入口函数:ordered_commit。

  • 阶段说明:时机是在 Flush Binlog 之后,在 sync 之后,存储引擎提交之前。

  • 半同步回调函数:repl_semi_report_binlog_sync。

  • 详细说明:这是 MySQL 5.7 版本新增的功能,通过参数 rpl_semi_sync_master_wait_point(默认值为 AFTER_SYNC)来控制。如果设置为 AFTER_SYNC,就会执行当前这个回调。而如果设置为 AFTER_COMMIT,则会执行桩 after_commit(下面会讲到)对应的操作。具体这个阶段会做什么操作,在桩 after_commit 的介绍中会讲,这里只讲可能存在的问题。在 5.7 版本中,如果参数为 AFTER_SYNC(默认),假设主库在存储引擎提交之前挂了,那么很明显这个事务是不成功的,但由于对应的 Binlog 已经做了 Sync 操作,从库已经收到了这些 Binlog,并且执行成功,相当于在从库上多了数据,明显是存在问题的,但多了数据,问题一般不算严重。这个问题可以这样理解,作为 MySQL,在没办法解决分布式数据一致性问题的情况下,它能保证的是不丢数据,多了数据总比丢数据要好。但如果还是按照之前 MySQL 5.6 的设置(相当于是 AFTER_COMMIT),那么因为是在主库提交之后才去等待,所以,如果在等待过程中主库挂了,但此时从库可能根本没有收到相应的 Binlog,如果主库永远也启动不了了,那么实际上已成功提交的事务,在从库上是找不到的,也就是数据丢失了,这是 MySQL 不愿意看到的,所以在 MySQL 5.7 版本中默认增加了 AFTER_SYNC 的选择并将其设置为默认值是合理的。

 after_commit

  • 源码调用入口函数:finish_commit。

  • 阶段说明:数据库存储引擎提交完成之后,此时有可能会等待从库的消息。

  • 半同步回调函数:repl_semi_report_commit。

  • 详细说明:这个桩是半同步中最重要的位置之一,提交线程执行到这里时会检查当前实例中收到的从库 ACK 信息,其中包含了 Binlog 位置信息,这个 ACK 有可能是其他提交事务对应的 ACK。这个提交线程将自己的 Binlog 位置与最近收到的 ACK 消息中的位置对比,如果 ACK 的更大,就不再需要等待,直接提交。因为一个更大的 Binlog 位置已经复制到从库中了,当前事务对应的相对较小的 Binlog 自然也已经复制过去了,所以不需要等待。如果还没有收到任何比当前事务对应的 Binlog 位置大或相等的 ACK,则需要等待,此时状态参数 rpl_semi_sync_master_wait_sessions 递增 1,然后当前线程进入等待状态。等待结果有两种,一种是超时(超时时间为参数 rpl_semi_sync_master_timeout 的值),此时就将半同步关掉;一种是正常等到了 ACK,当前线程就会被唤醒,并且状态参数 rpl_semi_sync_master_wait_sessions 会减 1。当多个事务并发提交时,如果有多个线程在等待 ACK,rpl_semi_sync_master_wait_sessions 的值可能会比较大。等到 ACK 之后,统计出已经等待的时间,来更新参数 rpl_semi_sync_master_avg_trx_wait_time、rpl_semi_sync_master_avg_net_wait_time 等。最后再更新参数 rpl_semi_sync_master_yes_tx、rpl_semi_sync_master_no_tx 等参数值,表示有多少事务是通过半同步产生的、有多少不是,可以通过这些参数来看半同步是不是真的在运作了。MySQL 5.6 版本默认是在这个桩上等待的,而在 MySQL 5.7 版本中,如果将参数 rpl_semi_sync_master_wait_point 设置为 AFTER_COMMIT,对应的就是在桩 after_commit 中做这些操作了,而如果设置为 AFTER_SYNC,则这些操作就会在桩 after_sync 中做了。

 after_rollback

  • 源码调用入口函数: ha_rollback_low。

  • 阶段说明: 这也是一个拥有插件式特点的 MySQL Server 加的一个桩,这个桩设置在数据库回滚之后。

  • 半同步回调函数: repl_semi_report_rollback。

  • 详细说明:在 Binlog 中,经常也会出现 Rollback 操作,用来将之前产生的操作回滚掉。对于一个事务,回滚操作和提交操作的意义是相同的,即结束这个事务。对于回滚操作,这里是要在一个事务的 Binlog 中加入 Rollback 的相关处理,然后将 Binlog 复制给从库,其实这个意义和事务提交是一样的,即在这个位置,还是要等待从库的 ACK。从这个意义上讲,桩 after_rollback 的操作和 after_commit 就完全一样了。回调函数 repl_semi_report_rollback 直接调用了 repl_semi_report_commit。

 transmit_start

  • 源码调用入口函数: mysql_binlog_send。

  • 阶段说明:此桩执行时机是在主库向从库发送 Binlog 之前,在从库请求 Binlog 复制之后。当主库接收到 Dump Binlog 信息准备向从库发送 Binlog 时执行此桩,每执行一次 start slave,也执行一次此桩。

  • 半同步回调函数: repl_semi_binlog_dump_start。

  • 详细说明: 首先判断当前有没有开启半同步,如果没有,直接返回。在 ack_receiver(下面会详细介绍)中加入当前从库线程信息,状态参数 Rpl_semi_sync_master_clients 的值加 1。这里有一个假设,请求 Binlog 这个位置的从库,已经接收到了所有在这个点之前的 Binlog。所以这里的处理是,如果现在有等待从库消息的主库线程,就告诉它不要等了,此时至少会有一个主库事务可以提交了。通知方式是广播。在该假设的前提下,当前请求的位置会被主库当作是 ACK 消息。

  • ack_receiver:ack_receiver 是在 MySQL 5.7 版本中新加入的 ACK 接收管理机制。这种机制是单独建立的,通过 select 来对每一个注册的从库做监听,在有 ACK 消息返回时,通过读取其 ACK 消息获取对应的 Binlog 位置。MySQL 5.7 通过新增参数 rpl_semi_sync_master_wait_for_slave_count 来设置主库,在一个事务提交之前,需要等待几个从库的 ACK,以便进一步提高半同步的安全性。

    如果设置为 1,则与 5.6 版本无异;而如果大于 1,则采取 5.7 版本的新方式。在 5.7 版本中统计主库收到多少个从库的反馈是通过创建一个长度为 rpl_semi_sync_master_wait_for_slave_count 的数组,每个位置对应一个从库 ACK 信息(包含 Binlog 位置信息)实现的,每当一个从库给出反馈,就根据其 server_id 来更新对应的位置信息,当这个数组被写满时,说明所有从库都给出了反馈,同时这个数组中 Binlog 的最小位置就是这次通知主库可以提交的最大位置点(因为这是从数组中找的最小 Binlog 位置,说明数组中对应的所有从库都至少复制到了这个位置),同时将数组中与这个 Binlog 位置相同的位置清空以便循环使用,当再次都存储满之后,再从中找到新的最小 Binlog 位置,这样不断循环往复向前推进。简而言之,这类似木桶原理,木板个数为 rpl_semi_sync_master_wait_for_slave_count,最短的板为安全的可提交位置。

    MySQL 5.7 版本相比 MySQL 5.6 版本的共同点是,它们都找到了一个 Binlog 位置;而不同点是这个位置在 MySQL 5.7 中有多个从库都至少复制了这个 Binlog 位置,而在 MySQL 5.6 中只有一个节点确定复制到这个位置。在这个位置确定之后,ack_receiver 就会通过广播的方式告诉正在等待 ACK 消息的主库线程继续它们的提交工作。

 transmit_stop

  • 源码调用入口函数:mysql_binlog_send。

  • 阶段说明:这个桩的位置是在主库向从库发送 Binlog 结束之后,结束原因不限。

  • 半同步回调函数:repl_semi_binlog_dump_end。

  • 详细说明:在主库向从库发送 Binlog 结束之后(从库断掉复制等),主库这边首先要将状态参数 Rpl_semi_sync_master_clients 减 1。如果当前主库的半同步还处于开启状态,同时参数 rpl_semi_sync_master_wait_no_slave 为 OFF、Rpl_semi_sync_master_clients 为 0,则此时系统会自动关闭半同步,而这也是参数 rpl_semi_sync_master_wait_no_slave 的意义。

 before_send_event

  • 源码调用入口函数: mysql_binlog_send。

  • 阶段说明:这个桩执行位置是在发送 Binlog 事件之前,作用是在预留的位置中填写半同步控制信息。

  • 半同步回调函数: repl_semi_before_send_event。

  • 详细说明:这个桩的主要作用,就是要告诉从库在收到 Binlog 之后,要不要向主库反馈 ACK 消息,实现方法是在发送的 Binlog 事件头信息中打标志,要打什么标志,通过多个条件来判断。①首先,发送线程会确定当前要发送的 Binlog 位置是否比从库最新反馈的 Binlog 位置小。如果是的话,说明更新的 Binlog 已经收到了 ACK,而落后的 Binlog 就不需要再请求 ACK 了,这种情况一般发生在刚开始复制的时候;反过来,如果要发送的 Binlog 位置更大,则需要进一步判断,转步骤②。②如果已经有一个对应更大的 Binlog 位置的事务在等待 ACK,则这个事务也不会再请求 ACK,否则需要进一步判断,转步骤③。③判断当前要发送的 Binlog 位置是不是一个事务的最后结束位置(在桩 after_flush 中,有说明通过一个 HASH 表来缓存事务 Binlog 结束位置的信息),如果不是,则此时发送线程不需要请求 ACK,否则说明需要 ACK 反馈消息。完成这三步之后,就可以确定在发送 Binlog 事件时告诉从库要不要反馈 ACK 消息了。最终,这个决定会通过修改当前要发送的 Binlog 事件头内容打上相应标志。如果要反馈 ACK 消息,则写入 1,否则写入 0。通过这一系列的过滤,主要目的是为了尽可能地减少主库和从库的 ACK 交互,以提高半同步的性能,让每一次 ACK 都是有用的。

 after_send_event

  • 源码调用入口函数: mysql_binlog_send。

  • 阶段说明:这个桩的执行时机是在 Binlog 事务发送完成之后,如果需要的话,从从库接收反馈消息,来确定从库是不是已经收到 Binlog,以便通知等待事务继续做,同时继续向从库发送 Binlog。

  • 半同步回调函数: repl_semi_after_send_event。

  • 详细说明:在 MySQL 5.6 版本中,这个桩主要是用来接收从库的 ACK 的。与桩 before_send_event 对应的是,如果在 before_send_event 中确定了一个事务需要发送 ACK 请求,那么此时就会等待 ACK 反馈。如果确定需要等待 ACK 反馈(通过判断之前发送的 Binlog 事件头的标志位),则直接从当前线程与从库的连接中读取 ACK 消息,如果从库还没有发送,则当前发送线程被阻塞,直到有消息发送过来阻塞才会被解除。当前线程接收到 ACK 之后,会从中取出 Binlog 位置信息,然后更新主库中的从库,反馈 ACK 位置信息。如果 ACK 的位置比处于等待状态的主库事务线程的最小位置大,则说明可以广播通知它们可以继续提交操作了。假设某些事务之前讲的桩 after_commit 或 after_sync 处于等待状态了,则都是等待当前这个广播以继续提交。在 MySQL 5.7 版本中,接收 ACK 消息不会在这里做,它被放到一个专门的 ACK 消息接收线程 ack_receiver 中,这在桩 transmit_start 中已经介绍过了。两个版本的实现原理是一样的,只是加入了对多个从库的处理机制。

 reserve_header

  • 源码调用入口函数: mysql_binlog_send。

  • 阶段说明:此桩的执行时机是在每次发送一个事务的时候,目的是预留半同步通信位置。

  • 半同步回调函数:repl_semi_reserve_header。

  • 详细说明:前面已经说过,在 Binlog 事件头部有 4 字节的预留位置,如果当前要发送的 Binlog 对象为半同步的(通过在从库端中介绍的 rpl_semi_sync_slave 参数来判断),则此时预留两个字节给半同步通信使用(从库会通过这两个字节来判断是不是要给主库反馈)。

 after_reset_master

  • 源码调用入口函数:reset_master。

  • 阶段说明:重置复制信息,此桩执行时机是在执行 reset master 语句之后。

  • 半同步回调函数: repl_semi_reset_master。

  • 详细说明:重设本地所有关于半同步的参数。

半同步从库端
 thread_slave

  • 源码调用入口函数: handle_slave_io。

  • 阶段说明:从库向主库 Dump Binlog 之前,具体是在开始连接主库前。

  • 半同步回调函数: repl_semi_slave_io_start。

  • 详细说明:如果 rpl_semi_sync_slave_enabled 参数为 ON,此时会将 status 参数 Rpl_semi_sync_slave_status 也设置为 ON。需要注意的是,如果复制已经开始,而状态参数 rpl_semi_sync_slave_enabled 为 OFF,则需要将其设置为 ON 之后,还需要做 stop slave;start slave 才能开启半同步。也就是说,是不是启用半同步,是在这里决定的。

 before_request_transmit

  • 源码调用入口函数: request_dump。

  • 阶段说明: 此桩的执行时机是在上一个桩之后,具体是向主库发起 Dump Binlog 请求的时候。

  • 半同步回调函数:repl_semi_slave_request_dump。

  • 详细说明:如果从库没有开启半同步,或者主库参数 rpl_semi_sync_master_enabled 不存在或没有开启,则与异步复制无异。通过 slave 对 master 的连接,设置会话参数 rpl_semi_sync_slave=1,表示当前从库是半同步类型的复制。如果一个主有多个从,判断是不是半同步的从,就是通过这个变量来区别的,这个变量 DBA 是看不到的,只有系统才能知道。这个参数其实就是一个会话变量,是半同步连接自己设置的,名字是它自己起的。

 after_read_event

  • 源码调用入口函数: handle_slave_io。

  • 阶段说明: 此桩的执行时机是在连接上主库并读取到 event 之后。每读到一个事件,都会执行一次这个桩。

  • 半同步回调函数: repl_semi_slave_read_event。

  • 详细说明: 如果参数 rpl_semi_sync_slave_status 为 ON,读取 Binlog 头部信息,Binlog 头部本身有 4 个字节的预留位置,半同步使用 2 字节来同步主从行为。第 1 个字节是一个 Magic 数,用来校验半同步;第 2 个字节用来表示需不需要从库给主库发送信息的标志。

 after_queue_event

  • 源码调用入口函数: handle_slave_io。

  • 阶段说明: 此桩的执行时机是在从主库 Dump 到 Binlog 并写入本地 Relay Log 之后。每读到一个事件,都会执行一次这个桩。

  • 半同步回调函数: repl_semi_slave_queue_event。

  • 详细说明: 如果参数 rpl_semi_sync_slave_status 为 ON,同时上面的桩 after_read_event 得到的标志为 1,此时从库会给主库发送接收到的消息,即 ACK 反馈,否则就不向主库发送消息。给主库发送的消息是当前最新的 Binlog 位置,包括文件名及 POSITION。

 thread_stop

  • 源码调用入口函数: handle_slave_io。

  • 阶段说明: 此桩的执行时机是在停止复制之后。

  • 半同步回调函数: repl_semi_slave_io_end。

  • 详细说明: 在执行 stop slave 命令时会执行这个回调函数,其实只是将参数 rpl_semi_sync_slave_status 设置为 OFF 而已。

 after_reset_slave

  • 源码调用入口函数:reset_slave。

  • 阶段说明:此桩的执行时机是在执行 reset slave 语句之后。

  • 半同步回调函数:repl_semi_reset_slave。

  • 详细说明:当前版本中,没有任何操作。

半同步实现

众所周知,半同步复制是一个插件,用到的时候只需要 install 即可。那么有人可能会想,一个动态加载的插件,如何能让事务处理线程等待、停下来,这是怎么做到的?

其实安装时指定的插件包,只是这个插件的实现体而已,在 MySQL 源码中还是需要支撑这个实现体的框架的。大概包括如下几个框架(框架,简而言之,就是在代码的某些位置,加入一些桩,或者叫 observer,观察者,在执行到这些桩的位置时,如果有插件在这些 observer 中注册实现体了,就执行这些实现体,否则没有任何操作)。

 Trans_observer

这个 observer 是关于事务处理的,包括如下五个过程(分别对应主库端“桩”中的“半同步回调函数”一项)。

  • before_dml:从字面意思就可以知道,这是在执行一个 DML 之前可以做的一些操作,如果有,就实现。

  • before_commit:这个过程,是在将 Binlog 写入文件之前执行的。

  • before_rollback:是在存储引擎回滚时执行的。

  • after_commit:是在存储引擎提交之后执行的。

  • after_rollback:在回滚之后执行。

 Server_state_observer

这个 observer 是关于服务器状态的。当然,它与复制没有关系,这里也一并说明,包括如下六个过程。

  • before_handle_connection:这个过程,在服务器已经准备好可以接受客户端的连接时调用。

  • before_recovery:这个过程,在开始做数据库恢复时调用。

  • after_engine_recovery:在数据库的每个存储引擎恢复完成之后调用。

  • after_recovery:在执行完整个数据库的数据恢复之后调用。

  • before_server_shutdown:在数据库正常关闭时调用。

  • after_server_shutdown:在数据库正常关闭时调用。

 Binlog_storage_observer

这个 observer 是关于 Binlog 的存储的,包括下面两个过程。

  • after_flush:在 Binlog 刷盘之后调用。在半同步插件中,处理的是将事务的完整结束位置写入活动事务缓存中,以便判断是否需要从库发反馈消息回来。

  • after_sync:这个过程是在 MySQL 5.7 版本中新增的。在半同步插件中,它是当 Binlog 在主库写入到 Binlog 文件之后,就开始等待从库的反馈消息了。

 Binlog_transmit_observer

这个 observer 是关于 Binlog 的发送的,包括下面六个过程。

  • transmit_start:这个过程,在主库开始向从库发送 Binlog 时调用。对于一次从库的 Binlog 请求只调用一次,主要处理的是主库对从库信息的缓存及感知,并且假设请求点之前的 Binlog 都已经在从库那边了。

  • transmit_stop:这个过程,在主库停止向从库发送 Binlog 时调用。和上面的过程相反,主要是销毁对从库的缓存等。

  • reserve_header:这个过程,发生在每次向从库发送一个 Event 的时候。因为要告诉从库是否需要发送反馈给主库的空间,所以这里就要在事务头部预留出空间来(实际上是初始化头部中 4 个空闲字节中的前面 2 个字节)。

  • before_send_event:这个过程,发生在上面预留空间之后。在发送之前,已经知道当前事件是否是一个事务的最后一个位置。如果是最后一个位置,就在预留位置设置需要反馈消息的标志,这样从库接收到之后,才会给主库发送反馈。

  • after_send_event:这个过程是在发送之后。可能要等待接收至少一个从库的反馈消息,因为上面发送了什么 Event,此时是知道的,如果发现预留位置上面的标志需要等待从库的反馈消息,那么此时便需要等待,直到收到反馈消息,才能继续发送 Binlog。而在 5.7 版本中,是把等待 ACK 的工作放入一个专门的接收线程中,实际上是一个原理。

  • after_reset_master:这个过程,是在执行 RESET MASTER 命令时调用的过程,主要是初始化一些变量状态等。

 Binlog_relay_IO_observer

这个 observer 是关于从库端 Binlog 处理的,包括下面七个过程。

  • thread_start:这个过程,就是简单处理一下从库这边的半同步参数的。

  • thread_stop:这个过程,和上面的相反,也比较简单。

  • applier_stop:这个过程,在半同步中没有实现,其调用时机是在 SQL 线程执行失败,导致复制中断的时候。

  • before_request_transmit:这个过程,是从库向主库请求 Binlog 时调用的,与上面 Binlog_transmit_observer 中的 transmit_start 对应。

  • after_read_event:这个过程是在每次 IO 线程读到 Binlog 中的一个事件之后,从 Binlog 事件头部读取反馈标志来判断是否要发送反馈消息,这个结果会在下面的过程中用到。

  • after_queue_event:如果上面的结果表示从库需要发送反馈消息,那么在这个过程中,会将当前 Binlog 的位置信息发送给主库。

  • after_reset_slave:这个过程,是在执行 RESET SLAVE 命令时调用的。

下面是这些过程调用方式的一个例子。

(void) RUN_HOOK(binlog_relay_io, after_reset_slave, (thd, mi));

第一个参数指定的是 observer 名;第二个参数指定的是其中的过程名;第三个参数指定的是具体某一个过程的参数。

上面已经提到,介绍的这些过程或 Observer 只是一个框架,如果要实现一个插件,需要给这个框架注册自己的过程实现体,针对每一个插件,都需要有一个公共的声明结构。

MySQL半同步复制

其中可以看到,有一个注释为“Plugin Init”对应的参数 semi_sync_master_plugin_init,这是一个函数指针,半同步插件就是通过这个函数来初始化的,其实现代码如下。

MySQL半同步复制

可以看到,通过函数 register_trans_observer、register_binlog_storage_observer、register_binlog_transmit_observer,向框架注册了半同步插件自己实现的 trans_observer、storage_observer 及 transmit_observer,它们对应的实现函数如下。

MySQL半同步复制

现在看来,一个插件就全部对上了,如果哪天想自己实现一个 Observer 中的小功能插件,也知道如何做了。

插件安装

一个插件,可以通过如下语句安装。

INSTALL PLUGIN rpl_semi_sync_master SONAME 'semisync_master.so';

在安装之后,如果数据库重启了,以后就不需要再次安装,因为这个信息已经被存储到数据库表 mysql.plugins 中了。数据库启动时,系统会自动从这里找到所有的插件,自动安装。

半同步自动开关

半同步自动关闭,有以下 3 种情况。

  • 当参数 rpl_semi_sync_master_wait_no_slave 为 OFF,主库在做 transmit_stop 时,此时正常复制的从库个数小于设置的 rpl_semi_sync_master_wait_for_slave_count 个数,此时主库会将其半同步关闭。

  • 当主库等待从库反馈消息时间超过设置的超时时间后,主库将自动关闭半同步。

  • 当数据库服务器正常退出时,半同步会先自动关闭,不过此时有可能正在等待从库的反馈消息,退出之后,有可能造成一部分事务的丢失。

半同步自动开启,只有下面一种情况。

当收到从库反馈消息之后,如果满足了通知主库的条件,同时当前最大的可提交 Binlog 的位置已经大于当前正在提交的事务中最大的 Binlog 位置,那就说明从库已经赶上了主库的进度,此时主库就自动恢复半同步复制。

今日荐文

点击下方图片即可阅读

 大量 MySQL 表导致服务变慢的问题


本书作者还是 CNUTCon 全球运维技术大会的讲师哦~

MySQL半同步复制

2017 年,有哪些值得关注的运维技术热点?智能化运维、Serverless、DevOps ......CNUTCon 全球运维技术大会将于 9 月上海举办,12 位大牛联合出品,揭秘最前沿运维技术,推荐学习!