MySQL半同步复制的示例分析

42次阅读
没有评论

共计 4982 个字符,预计需要花费 13 分钟才能阅读完成。

这篇文章主要介绍 MySQL 半同步复制的示例分析,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!

代码分析

int repl_semi_report_commit(Trans_param *param)//gdb 下 param? 

 

 bool is_real_trans= param- flags   TRANS_IS_REAL_TRANS; 

 

 if (is_real_trans   param- log_pos) 

 { 

 const char *binlog_name= param- log_file; 

 return repl_semisync.commitTrx(binlog_name, param- log_pos); 

 } 

 return 0; 

}

ol start= 1 >

int ReplSemiSyncMaster::commitTrx(const char* trx_wait_binlog_name, 

 my_off_t trx_wait_binlog_pos) 

 // 自旋锁,下面的代码是线性执行。 

 mysql_mutex_lock(LOCK_binlog_); 

 if (active_tranxs_ != NULL   trx_wait_binlog_name){ 

 entry=active_tranxs_- find_active_tranx_node(trx_wait_binlog_name, 

 trx_wait_binlog_pos); 

 if (entry) 

 thd_cond=  entry- cond; 

 } 

 // 进入信号了,为后面发起信号量的等待动作做准备,每个正在进行提交的事务都对应一个初始化的信号量 thd_cond 

 THD_ENTER_COND(NULL, thd_cond,  LOCK_binlog_, 

   stage_waiting_for_semi_sync_ack_from_slave, 

   old_stage); 

 if (getMasterEnabled()   trx_wait_binlog_name){ 

 set_timespec(start_ts, 0);// 

 if (!getMasterEnabled() || !is_on()) 

 goto l_end; 

 // 计算等待 ACK 的截止时间。按照当前时间加上半同步等待的超时时间,这个时间回在发起信号量等待的时候用的  

 //rpl_semi_sync_master_timeout 

 abstime.tv_sec = start_ts.tv_sec + wait_timeout_ / TIME_THOUSAND; 

 abstime.tv_nsec = start_ts.tv_nsec +(wait_timeout_ % TIME_THOUSAND) * TIME_MILLION; 

 if (abstime.tv_nsec  = TIME_BILLION){ 

 abstime.tv_sec++; 

 abstime.tv_nsec -= TIME_BILLION; 

 } 

 //state_是 TRUE 表示当前半同步状态为 on,否则直接进入 l_end。Rpl_semi_sync_master_status 

 //reply_file_name_值的变化,在其他函数中? 

 while (is_on()){ 

 if (reply_file_name_inited_){ 

 // 比较事务所涉及的 binlog 位置跟 reply 的位置,如果 cmp 0,说明此事务的 binlog 已经同步  

 // 到 slave,跳出该循环,进入最后阶段 l_end 

 int cmp = ActiveTranx::compare(reply_file_name_, reply_file_pos_, 

 trx_wait_binlog_name, trx_wait_binlog_pos); 

 if (cmp  = 0){ 

 break; 

 } 

 } 

 if (wait_file_name_inited_){ 

 // 比较事务所涉及的 binlog 位置和当前最小需要等待的 binlog 位置。如果 cmp 0,表示调整当前最小需要等待  

 //binlog 的位置。rpl_semi_sync_master_wait_pos_backtraverse++, 即等待位置需要调整的次数,一般不会  

 // 调整  

 int cmp = ActiveTranx::compare(trx_wait_binlog_name, trx_wait_binlog_pos, 

 wait_file_name_, wait_file_pos_); 

 if (cmp  = 0){ 

 strncpy(wait_file_name_, trx_wait_binlog_name, sizeof(wait_file_name_) – 1); 

 wait_file_name_[sizeof(wait_file_name_) – 1]=  \0  

 wait_file_pos_ = trx_wait_binlog_pos; 

 rpl_semi_sync_master_wait_pos_backtraverse++; 

 } 

 

 }else{ 

 // 保存第一次最小需要响应的事务位置  

 strncpy(wait_file_name_, trx_wait_binlog_name, sizeof(wait_file_name_) – 1); 

 wait_file_name_[sizeof(wait_file_name_) – 1]=  \0  

 wait_file_pos_ = trx_wait_binlog_pos; 

 wait_file_name_inited_ = true; 

 } 

 // 如果 salve 个数是 0 了,则将半同步关闭,退出循环  

 if (abort_loop   rpl_semi_sync_master_clients == 0   is_on()){ 

 switch_off(); 

 break; 

 } 

 // 正式进入等待 binlog 同步的步骤,将 rpl_semi_sync_master_wait_sessions+1,表明  

 // 有多少要提交的事务线程在等待(这个值是否能够代表实际等待事务的线程数量,值得怀疑,因为该函数  

 // 开始位置有 lock,没有 unlock 前,其他线程也进不到这一步,没办法执行 ++) 

 // 然后发起等待信号,进入信号等待后,只有 2 种情况可以退出等待。1 是被其他线程唤醒(binlog dump) 

 // 2 是等待超时时间。如果是被唤醒则返回值是 0,否则是其他值  

 rpl_semi_sync_master_wait_sessions++; 

 entry- n_waiters++; 

 // 发起信号等待,然后根据返回结果做相应计数:上面是循环体里面的所有内容,接下来我们看退出循环后的操作。特别提一下,唤醒该线程的 dump 线程,当 dump 线程收到相应 binlog 位置的 ack 之后,会将其唤醒。 

 wait_result= mysql_cond_timedwait(entry- cond,  LOCK_binlog_,  abstime); 

 entry- n_waiters–; 

 rpl_semi_sync_master_wait_sessions–; 

 if (wait_result != 0){ 

 // 等待超时,关闭半同步  

 rpl_semi_sync_master_wait_timeouts++; 

 switch_off(); 

 }else{ 

 wait_time = getWaitTime(start_ts); 

 if (wait_time   0){ 

 // 表明时钟错误,可能是做了时间调整  

 rpl_semi_sync_master_timefunc_fails++; 

 }else{ 

 // 将等待事件与该等待计入总数  

 rpl_semi_sync_master_trx_wait_num++; 

 rpl_semi_sync_master_trx_wait_time += wait_time; 

 } 

 } 

 }//end while 

l_end: 

 /* Update the status counter. */ 

 if (is_on()) 

 rpl_semi_sync_master_yes_transactions++; 

 else 

 rpl_semi_sync_master_no_transactions++; 

 

 } 

 /* Last waiter removes the TranxNode */ 

 if (trx_wait_binlog_name   active_tranxs_ 

   entry   entry- n_waiters == 0) 

 active_tranxs_- clear_active_tranx_nodes(trx_wait_binlog_name, 

 trx_wait_binlog_pos); 

 THD_EXIT_COND(NULL,   old_stage); 

 

}  

3、流程总结

1)在 commit 函数中,首先需要加一个自旋锁 LOCK_binlog_,主要动作都在这个锁内执行。

2)进入信号,为后面发起信号量的等待动作做准备

3)计算 binlog 等待 ACK 的截止时间。从此时开始 + 半同步等待的超时时间 rpl_semi_sync_master_timeout(默认是 10s)

4)需要在半同步状态下进入下面操作,否则进入 l_end。半同步状态的判断是 state_,和 Rpl_semi_sync_master_status 是什么关系?

5)第一次进来:保存最小需要响应的事务位置 wait_file_name_、wait_file_pos_,并将 wait_file_name_inited_置成 TRUE

6)最大响应位置 reply_file_name_、reply_file_pos_在 binlog dump 线程修改,和当前 binlog(已经 flush 的?)的位置比较。若当前 binlog 位置比 reply 的小,表示次事务的 binlog 已经到 slave 了,跳出循环,进入最后阶段 l_end

7)非第一次进来:比较事务涉及的 binlog 位置和当前最小需要等待的 binlog 位置。如果比 wai_file_name 的小,需要将最小需要等待的位置调整到当前位置。rpl_semi_sync_master_wait_pos_backtraverse++,即最小等待位置需要调整的次数。一般不会调整。

8)第一次进来:需要保存最小需要等待响应的位置为当前位置

9)接着,需要判断 slave 个数和半同步是否正常。不正常则退出循环,将半同步关闭

10)正式进入等待 binlog 同步的步骤:

       rpl_semi_sync_master_wait_sessions+1:表示有多少提交的事务线程正在等待

        发起信号等待:mysql_cond_timedwait:只有 2 中情况可以退出等待:1 是被其他线程 binlog dump 唤醒,2 是等待超时。

        特别提一下,唤醒该线程的 dump 线程,当 dump 线程收到相应 binlog 位置的 ack 之后,会将其唤醒。

        等待超时:将半同步关闭

        接收到 slave ACK,被 binlog dump 线程唤醒:修改对应变量

11)将 after_flush 步骤插入 active_trans 的 node 删掉

12)直到最后一步才释放锁,因此该函数是整个实例串行的。同时中间有个信号等待的动作。如果数据库并发量很大,而此时主从异常,一旦超时时间设置过大,则可能出现其他用户线程阻塞在 lock() 函数上,杜塞时间越长,累积的线程越多,容易引发雪崩,所以超时时间设置需谨慎,并非随意设置。

以上是“MySQL 半同步复制的示例分析”这篇文章的所有内容,感谢各位的阅读!希望分享的内容对大家有帮助,更多相关知识,欢迎关注丸趣 TV 行业资讯频道!

正文完
 
丸趣
版权声明:本站原创文章,由 丸趣 2023-07-27发表,共计4982字。
转载说明:除特殊说明外本站除技术相关以外文章皆由网络搜集发布,转载请注明出处。
评论(没有评论)