mysql 半同步 semi-sync 源码整理

用户线程
事务提交

MYSQL_BIN_LOG::ordered_commit
  一阶段 flush binlog 后,执行semi插件  调用repl_semi_report_binlog_update 
  file_name_ptr: binlog 文件名
  flush_end_pos: flush binlog时的大小
  ->  if (RUN_HOOK(binlog_storage, after_flush,(thd, file_name_ptr, flush_end_pos)))
     ->repl_semi_report_binlog_update
       ->lock() LOCK_binlog_
         记录当前的binlog 的 文件名 和 大小 
       ->repl_semisync.writeTranxInBinlog(log_file,log_pos);
         设置为半同步后如果是第一次写,则不比较大小,commit_file_name_inited_ 更新为true
         仅在次数更新为ttrue,其他修改为false的地方,都是关闭或者开启
         比较存储信息是否比当前的小,如果小,则更新存储的值commit_file_pos_
         ->int cmp = ActiveTranx::compare(log_file_name, log_file_pos,commit_file_name_, commit_file_pos_);
           存储当前binlog 的文件 和大小
         ->active_tranxs_->insert_tranx_node(log_file_name, log_file_pos)
           分配一个节点
           ->ins_node = allocator_.allocate_node();     
           当前的binlog 的节点放到链表的尾部
           -> trx_rear_->next_ = ins_node;   
              trx_rear_= ins_node;
              trx_front_ 为链表头
           根据binlog的文件和文件大小生成hash值,放到hash数组中
           ->hash_val = get_hash_value(ins_node->log_name_, ins_node->log_pos_);
            ins_node->hash_next_ = trx_htb_[hash_val];
            trx_htb_[hash_val]   = ins_node;
  sync binlog文件
  ->std::pair<bool, bool> result= sync_binlog_file(false);
  判断当前binlog的内容是否已经发送到从库且返回应答,如果没有从库返回应答则阻塞
  ->sync_error= call_after_sync_hook(commit_queue);
    ->repl_semi_report_binlog_sync
      ->repl_semisync.commitTrx(log_file, log_pos)
      根据当前的文件与大小,找到链表中存储的节点
       -> entry= active_tranxs_->find_active_tranx_node(trx_wait_binlog_name,trx_wait_binlog_pos);
       ->while(1) 循环
         判断已返回文件的大小与当前文件的大小,主要是判断是否需要wait 等待
         int cmp = ActiveTranx::compare(reply_file_name_, reply_file_pos_,
                                       trx_wait_binlog_name, trx_wait_binlog_pos);
                                       
         如果已经返回应答(cmp>0),则退出循环,把binlog的文件名与大小从等待节点链表中移除
         如果没有返回,则继续下面的逻辑 如果当前等待的文件偏移还大于当前文件则偏移,再更新为最小的,即使用当前的
         int cmp = ActiveTranx::compare(trx_wait_binlog_name, trx_wait_binlog_pos,
                                       wait_file_name_, wait_file_pos_);
         wait_file_name_inited_更新为  为true
                                     
         等待ack线程信号触发(即收到了从库的应答)
         wait_result= mysql_cond_timedwait(&entry->cond, &LOCK_binlog_, &abstime);
         如果有异常或等待超时则自动关闭半同步
         switch_off();
         当流程执行完毕后,清理当前节点
         active_tranxs_->clear_active_tranx_nodes(trx_wait_binlog_name,trx_wait_binlog_pos);
    半同步完成,继续第三阶段的提交   

主库ack应答线程

   主库执行set global rpl_semi_sync_master_enabled=1; 
   设置为1时,启动ack线程
   ->fix_rpl_semi_sync_master_enabled
      初始化一些类
     ->repl_semisync.enableMaster()
       创建ActiveTranx,主要是等待链表的一些操作
       ->active_tranxs_ = new ActiveTranx(&LOCK_binlog_, trace_level_);
         初始化一些变量
         commit_file_name_inited_ = false;
         reply_file_name_inited_  = false;
         wait_file_name_inited_   = false;
         修改状态
         set_master_enabled(true);
     开启ack线程
     ->ack_receiver.start()    
       修改状态
       m_status= ST_UP;
       创建线程
       mysql_thread_create(key_ss_thread_Ack_receiver_thread, &m_pid,
                            &attr, ack_receive_handler, this))
       ->ack_receive_handler
         ->Ack_receiver::run()
           如果是第一个从库,则等待从库加入
           wait_for_slave_connection();
           如果已有从库,则把从库添加到m_fds数组中
           if (!listener.init_slave_sockets(m_slaves))
           把从库的fd 添加到poll中
           ret= listener.listen_on_sockets();
           遍历从库的socket ,读取所有从库发来的信息
           while (i < listener.number_of_slave_sockets() && m_status == ST_UP)
              net_clear(&net, 0);
              读取socket中的数据
              len= my_net_read(&net);
              处理收到数据的逻辑
              repl_semisync.reportReplyPacket(slave_obj.server_id,net.read_pos, len);
              解析收到数据的报文
              log_file_pos = uint8korr(packet + REPLY_BINLOG_POS_OFFSET); 文件大小
              log_file_len = packet_len - REPLY_BINLOG_NAME_OFFSET;文件名长度
              strncpy(log_file_name, (const char*)packet + REPLY_BINLOG_NAME_OFFSET, log_file_len);文件名
              对当前的文件名与文件大小做触发激活等待的线程的相关逻辑
              handleAck(server_id, log_file_name, log_file_pos);
                  lock();
                  如果有一个从库,log_file_pos 从库应答已经确认收到的文件的大小
                  reportReplyBinlog(log_file_name, log_file_pos);
                      如果半同步是关闭了,试图根据收到的文件大小重新开始半同步
                     try_switch_on(log_file_name, log_file_pos);
                         如果收到的文件大小 大于提交的文件的大小 则开始半同步的状态
                         int cmp = ActiveTranx::compare(log_file_name, log_file_pos,
                                   commit_file_name_, commit_file_pos_);
                     更新reply_file_pos_,把从库发来的报文解析的log_file_pos 更新到reply_file_pos_
                     
                     cmp = ActiveTranx::compare(log_file_name, log_file_pos,
                               reply_file_name_, reply_file_pos_);
                     从库发送已经确认的reply_file_pos_ 是否已经大于 当前线程等待的binlog 的 wait_file_pos_ 大小
                     如果是第一次,则设置reply_file_name_inited_为true
                     cmp = ActiveTranx::compare(reply_file_name_, reply_file_pos_,
                               wait_file_name_, wait_file_pos_);
                     如果已经响应的binlog大小reply_file_pos_  大于 等待的binlog大小wait_file_pos_
                     则需要更新为wait_file_name_inited_为false
                     如果大于则激活等待的线程
                     active_tranxs_->signal_waiting_sessions_up_to(reply_file_name_, reply_file_pos_);
                         int cmp= ActiveTranx::compare(entry->log_name_, entry->log_pos_, log_file_name, log_file_pos) ;
                         遍历所有的等待的线程,凡是等待的binlog 偏移 小于从库应答的binlog的小,则依次激活等待的线程
                         while (entry && cmp <= 0)  
                             mysql_cond_broadcast(&entry->cond);
                             entry= entry->next_;
                             if (entry)
                                 cmp= ActiveTranx::compare(entry->log_name_, entry->log_pos_, log_file_name, log_file_pos) ;
                  如果有多个从库,log_file_pos 从库应答已经确认收到的文件的大小找一个最小的
                  ackinfo= ack_container_.insert(server_id, log_file_name, log_file_pos);   
                  reportReplyBinlog(ackinfo->binlog_name, ackinfo->binlog_pos);

主库dump线程调用semi的接口为


主要接口函数
   repl_semi_binlog_dump_start,	// start
   repl_semi_binlog_dump_end,	// stop
   repl_semi_reserve_header,	// reserve_header
   repl_semi_before_send_event,	// before_send_event
   repl_semi_after_send_event,	// after_send_event
   repl_semi_reset_master,	// reset  
dump 线程
com_binlog_dump
    mysql_binlog_send
      Binlog_sender::run()
        Binlog_sender::init
            调用semi 插件,m_start_file 从库发送的binlog文件名 (char*)thd->get_protocol_classic()->get_raw_packet()       
            m_start_pos 从库发送的binlog文件大小即从库收到主库的binlog大小  thd->get_protocol_classic()->get_packet_length()
            if (RUN_HOOK(binlog_transmit, transmit_start,(thd, m_flag, m_start_file, m_start_pos,
                   &m_observe_transmission)))
                repl_semi_binlog_dump_start
                    判断当前线程是否半同步,有从库设置见 repl_semi_slave_request_dump SET @rpl_semi_sync_slave= 1 session级别的参数
                    if (semi_sync_slave != 0)
                        把从库建立的链接信息添加到ack线程中
                        ack_receiver.add_slave(current_thd)
                            连接信息
                            slave.vio= *thd->get_protocol_classic()->get_vio();
                            与ack线程相对应listener.init_slave_sockets(m_slaves)
                            m_slaves.push_back(slave); 
                            mysql_cond_broadcast(&m_cond); 激活ack线程 与 wait_for_slave_connection对应
                       /* 从库个数计数器 One more semi-sync slave */
                       repl_semisync.add_slave();
                       第一次建立链接后,与从库确认信息
                       repl_semisync.handleAck(param->server_id, log_file, log_pos);  
                       至此 与从库semi 关系建立完成
        每次新打开一个binlog文件之前,需要往从库发送rotate信号,从库relay文件需要新建一个文件
        fake_rotate_event(log_file, start_pos)
            reset_transmit_packet
               生成一个的header 用户semi 发送消息时的校验信息
               m_packet[0]='\0'
               RUN_HOOK(binlog_transmit, reserve_header, (m_thd, flags, &m_packet))
                   Binlog_transmit_delegate::reserve_header
                       repl_semi_reserve_header
                         repl_semisync.reserveSyncHeader(header, size)
                            memcpy(header, kSyncHeader, sizeof(kSyncHeader));  kSyncHeader={0xef0} ReplSemiSyncBase::kPacketMagicNum = 0xef
                            hlen= sizeof(kSyncHeader); hlen = 2
                       packet->append((char *)header, hlen) hlen = 2 header={0xef0} packet为 {0,0xef0} len=3
               即repl_semi_reserve_header函数作用为异步复制发送的消息添加了3字节的报文头        
               Binlog_sender::read_event     
                    Binlog_sender::reset_transmit_packet 
                       RUN_HOOK(binlog_transmit, reserve_header, (m_thd, flags, &m_packet))
                       
      异步复制发送数据之前,判断当前消息是否需要从库发送消息应答 packet为 {0,0xef1} 此消息需要应答 packet为 {0,0xef0} 此消息不需要应答
      repl_semi_before_send_event                 
          repl_semisync.updateSyncHeader                 
          判断从库需要发送应答的依次条件
          当前的发送的文件大小 大于 已保存reply_file_pos_的大小 则 需要应答 reply_file_pos_在此函数中更新reportReplyBinlog
          cmp = ActiveTranx::compare(log_file_name, log_file_pos,
                                 reply_file_name_, reply_file_pos_);
          且 当前的发送的文件大小大于已保存wait_file_pos__的大小 或wait_file_name_inited_为false 则需要应答, 
           wait_file_pos_ 在此函数中更新 commitTrx                        
          cmp = ActiveTranx::compare(log_file_name, log_file_pos,
                                 wait_file_name_, wait_file_pos_);  
          且 当前当前的发送的文件大小大于已保存 等待链表中  在MYSQL_BIN_LOG::ordered_commit中flush binlog之后 repl_semi_report_binlog_update                             
          sync = active_tranxs_->is_tranx_end_pos(log_file_name,
                                               log_file_pos);  
          或者 当前的发送的文件大小 大于 已保存 commit_file_pos_ 的大小 或 wait_file_name_inited_ 为false 则需要应答 
            commit_file_pos_ 在此函中ReplSemiSyncMaster::writeTranxInBinlog 更新
            int cmp = ActiveTranx::compare(log_file_name, log_file_pos,
                                     commit_file_name_, commit_file_pos_);                                                                     
          (packet)[2] = kPacketFlagSync; 
     异步复制发送数据后
     Binlog_sender::send_events                                                                
        repl_semi_after_send_event                  
           ReplSemiSyncMaster::readSlaveReply
              net_flush 把报文发送到从库

从库semi 接口函数
repl_semi_slave_io_start, // start
repl_semi_slave_io_end, // stop
repl_semi_slave_request_dump, // request_transmit
repl_semi_slave_read_event, // after_read_event
repl_semi_slave_queue_event, // after_queue_event

start slave; 启动从库
   repl_semi_slave_io_start 
   ReplSemiSyncSlave::slaveStart
      设置半同步
      rpl_semi_sync_slave_status= 1;
  
  repl_semi_slave_request_dump
     读取主库是否开始半同步
     query= "SELECT @@global.rpl_semi_sync_master_enabled";
     设置连接主库的session 此连接执行半同步状态
     query= "SET @rpl_semi_sync_slave= 1";

从库读取数据的线程  
handle_slave_io  
    RUN_HOOK(binlog_relay_io, after_read_event,
                    (thd, mi,(const char*)mysql->net.read_pos + 1,
                   event_len, &event_buf, &event_len))
    Binlog_relay_IO_delegate::after_read_event
       repl_semi_slave_read_event
          repl_semisync.slaveReadSyncHeader
              *need_reply  = (header[1] & kPacketFlagSync); 判断是否需要回复 1 则回复
              *payload_len = total_len - 2; 把报文前两个字节占用的长度剔除
  
    Binlog_relay_IO_delegate::after_queue_event 
        repl_semi_slave_queue_event
           if (rpl_semi_sync_slave_status && semi_sync_need_reply) 从库开启半同步且需要回复
              ReplSemiSyncSlave::slaveReply 
                 生成需要应答的信息
                 reply_buffer[REPLY_MAGIC_NUM_OFFSET] = kPacketMagicNum; 校验码
                 int8store(reply_buffer + REPLY_BINLOG_POS_OFFSET, binlog_filepos); 文件pos
                 memcpy(reply_buffer + REPLY_BINLOG_NAME_OFFSET,binlog_filename,name_len + 1 /* including trailing '\0' */); 文件名
                 my_net_write
                 net_flush
                 主库的ack线程负责收应答报文
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值