1、介绍
在分析代码前,先介绍binlog中几个位置关系。

2、代码分析
-
MYSQL_BIN_LOG::ordered_commit->finish_commit:
-
ha_commit_low
-
RUN_HOOK(transaction, after_commit, (thd, all))->
-
(after_commit)repl_semi_report_commit:commitTrx
该函数在数据库存储引擎提交完成后,此时有可能等待从库的消息。
-
int repl_semi_report_commit(Trans_param *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;
-
}
-
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_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;
-
-
-
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;
-
}
-
-
-
while (is_on()){
-
if (reply_file_name_inited_){
-
-
-
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_){
-
-
-
-
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;
-
}
-
-
if (abort_loop && rpl_semi_sync_master_clients == 0 && is_on()){
-
switch_off();
-
break;
-
}
-
-
-
-
-
-
rpl_semi_sync_master_wait_sessions++;
-
entry->n_waiters++;
-
-
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;
-
}
-
}
-
}
-
l_end:
-
-
if (is_on())
-
rpl_semi_sync_master_yes_transactions++;
-
else
-
rpl_semi_sync_master_no_transactions++;
-
-
}
-
-
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()函数上,杜塞时间越长,累积的线程越多,容易引发雪崩,所以超时时间设置需谨慎,并非随意设置。
4、参考
http://mp.weixin.qq.com/s?__biz=MzIwNzEzNDkxNQ%3D%3D&idx=1&mid=401148347&scene=21&sn=f3372a773ddf948c5ebe35e77a8e1b3a
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/31493717/viewspace-2149124/
,如需转载,请注明出处,否则将追究法律责任。