温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

MySQL半同步复制的示例分析

发布时间:2021-11-01 09:26:17 来源:亿速云 阅读:121 作者:小新 栏目:MySQL数据库

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


代码分析

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

  2. {  

  3.   

  4.   bool is_real_trans= param->flags & TRANS_IS_REAL_TRANS;  

  5.   

  6.   if (is_real_trans && param->log_pos)  

  7.   {  

  8.     const char *binlog_name= param->log_file;  

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

  10.   }  

  11.   return 0;  

  12. }

<ol start="1" class="dp-cpp" white-space:normal;margin:0px 0px 1px 45px !important;">

  • 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半同步复制的示例分析”这篇文章的所有内容,感谢各位的阅读!希望分享的内容对大家有帮助,更多相关知识,欢迎关注亿速云行业资讯频道!

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI