zoukankan      html  css  js  c++  java
  • mysql半同步(semi-sync)源码实现

          mysql复制简单介绍了mysql semi-sync的出现的原因,并说明了semi-sync如何保证不丢数据。这篇文章主要侧重于semi-sync的实现,结合源码将semi-sync的实现过程展现给大家。最新的semi-sync源码可以参考官方5.7版本的实现,https://github.com/mysql/mysql-server

    打开semi-sync的正确姿势
         默认情况下的mysql复制都是异步复制,mysql通过参数来控制semi-sync开关。具体而言,主库上通过rpl_semi_sync_master_enabled参数控制,备库上通过rpl_semi_sync_slave_enabled参数控制,打开这两个参数后,mysql semi-sync的特性就打开了。注意对于备库而言,为了保证半同步立即生效,需要重启slave的IO线程。另外,还有一个比较重要的参数是rpl_semi_sync_master_timeout,这个参数用于控制master等待semi-slave ack报文的时间,单位是毫秒,默认是10000。master等待超时,则切换为普通的异步复制。

    master:
    set global rpl_semi_sync_master_enabled=1;
    set global rpl_semi_sync_master_timeout=xxx;
    
    slave:
    stop slave io_thread;
    set global rpl_semi_sync_slave_enabled=1;
    start slave io_thread;

    另外需要注意的是,打开了上述两个参数只说明master-slave已经具备打开semi-sync的基本条件了,但复制是否依照半同步运行,还需要根据Rpl_semi_sync_master_status的状态值确定。因为比如slave较master有很大延迟(超过rpl_semi_sync_master_timeout),那么复制切换为普通复制。对于需要调试代码的童鞋而言,rpl_semi_sync_master_trace_level和rpl_semi_sync_slave_trace_level非常重要,通过设置level取值,可以打印日志记录半同步的详细过程,方便定位问题。

    semi-sync的实现
    semi-sync说到底也是一种复制,只不过是在普通的复制基础上,添加了一些步骤来实现。因此semi-sync并没有改变复制的基本框架,我们的讨论也从master,slave两方面展开。
    1.master(主库)

    主库上面主要包含三个部分,(1).负责与slave-io线程对接的binlog dump线程,将binlog发送到slave,(2).主库上写事务的工作线程,(3).收取semi-slave报文的ack_receiver线程。

    (1).binlog dump流程
    主要执行逻辑在mysql_binlog_send函数中。

    1.判断slave是否是semi_slave,调用add_slave将semi-slave加入到ack_receiver线程的监听队列中。判断的逻辑是slave对应的会话上是否设置了参数rpl_semi_sync_slave。

    2.根据slave的请求偏移和binlog文件,从指定位点读取binlog
    3.根据文件和位点,捞binlog文件中的数据
    4.调用updateSyncHeader设置数据包头semi-sync标记
    根据实时semi-sync运行状态来确定是否设置(这个状态由ack_receiver线程根据是否及时收到slave-ack报文设置)
    5.调用my_net_write发送binlog
    6.调用net_flush确认网络包发送出去
    如果当前所有产生的binlog已经处理完,需调用wait_for_update_bin_log等待binlog更新。

    (2).半同步事务提交流程
    mysql5.6以后提交采用组提交方式,主要分为三个阶段,每个阶段有一个队列,增加semi-sync后,又增加了一个semi-sync阶段。
    1.flush阶段:
    队列能保证写binlog的顺序与innodb-commit的顺序一致。通过队列,可以保证顺序写每个事务的binlog-cache,然后只进行一次write操作(flush_cache_to_file)。flush阶段后,如果sync_binlog不是1,则通知master有新binlog产生;如果sync_binlog为1,则等待sync阶段后,再通知dump线程有新binlog产生。这里我理解是因为如果不为1,则可能没有后续的sync阶段,而操作系统缓存也有binlog数据,所以可以在flush阶段后通知;而对于sync_binlog为1的情况,可以保证主库的binlog先落地,永远比备库多。但如果sync_binlog不为1,比如1000,则主机异常情况下,则可能出现备库的binlog比主库还多的情况。根据sync_binlog的设置,确认是否要跳过sync阶段。
    2.sync阶段:
    sync_binlog_file
    3.semi_sync阶段:
    call_after_sync,等待备库应答。调用cond_timewait等待条件变量&COND_binlog_send_,注意这里只是leader线程在等,将leader线程唤醒后,才会结束semi_sync阶段,进而唤醒其它的follower线程。
    4.commit阶段:
    innodb-commit
    waitAfterCommit,等待备库应答。调用cond_timewait等待条件变量&COND_binlog_send_ 。最终我们根据semi-sync复制模式的设置(AFTER_COMMIT,AFTER_SYNC),来确定是第(3)步还是第(4)步进行等待。

    (3).接收slave-ack报文流程
    这个流程的工作主要在ack_receiver线程中,这个线程的主要作用是监听semi-slave的ack包,确认master-slave链路是否工作在半同步状态,并根据实际运行状态将普通复制与半同步复制进行切换。打开主库rpl_semi_sync_master_enabled参数后,该线程启动,关闭参数后,该线程消亡。
    流程如下:
    1.遍历semi-slave数组
    2.通过select函数监听每个slave是否有网络包过来
    3.调用my_net_read读取包数据
    4.调用reportReplyPacket处理semi-sync复制状态
    若备库已经获取了最新的binlog位点,则唤醒等待的工作线程
    5.调用reportReplyBinlog唤醒等待的线程,mysql_cond_broadcast(&COND_binlog_send_);

    2.slave(备库)

    主要实现在(handle_slave_io)
    1.启动io-thread后,调用safe_connect建立与master的连接
    2.调用request_dump函数处理请求binlog逻辑
    (1).执行命令SET @rpl_semi_sync_slave= 1,设置一个局部变量,通过这个参数标记slave为semi-slave
    (2).发送命令COM_BINLOG_DUMP请求日志
    循环从master端收取日志,处理日志
    {
      1.调用read_event,从master端收取日志(如果没有网络包,会阻塞等待)
      2.调用slaveReadSyncHeader,确定网络包头是否有semi-sync标记
      3.调用queue_event将日志写入relay-log,在这个过程中会过滤自身server-id的日志
      4.如果有semi-sync标记,调用slaveReply函数,发送ack报文
    }

  • 相关阅读:
    跟我一起学extjs5(02--建立project项目)
    SVN经常使用操作
    CWnd::Attach()具体解释
    51nod1160 压缩算法的矩阵——一道有趣的题
    Java实现 LeetCode 594 最长和谐子序列(滑动窗口)
    Java实现 LeetCode 1162 地图分析(可以暴力或者动态规划的BFS)
    Java实现 LeetCode 1162 地图分析(可以暴力或者动态规划的BFS)
    Java实现 LeetCode 1162 地图分析(可以暴力或者动态规划的BFS)
    Java实现 LeetCode 593 有效的正方形(判断正方形)
    Java实现 LeetCode 593 有效的正方形(判断正方形)
  • 原文地址:https://www.cnblogs.com/cchust/p/5093965.html
Copyright © 2011-2022 走看看