Mysql5.7半同步复制源码解析-last

 

可能绝大部分的朋友都semi-sync的参数含义以及状态指标含义,但也有个别指标项,例如Rpl_semi_sync_master_wait_pos_backtraverse这个指标,可能不甚其解。再深入一点,是否真正理解所有的半同步细节?...



在关于半同步的第一篇,我们讲到事务线程等待日志同步完成的函数ReplSemiSyncMaster::commitTrx。今天我们将把这个函数仔细分析一下,了解这个函数之后,你对有关半同步的所有参数和状态指标都会有更清楚的认知。



如上图所示:有关semi-sync状态的指标项都在上面的图片中显示,可能绝大部分的朋友都清楚其中绝大部分的指标项的含义,但也个别指标项,例如Rpl_semi_sync_master_wait_pos_backtraverse这个指标,你是否清楚其含义?另外,你是否真正了解这些指标的含义,再深入一点,你是否清楚ReplSemiSyncMaster::commitTrx这个函数到底做了哪些事情?另外,一个跟实际运维相关的参数,rpl_semi_sync_master_timeout是否可以随意设置?

上面的所有这些问题,通过对这个函数ReplSemiSyncMaster::commitTrx进行解析后,都可以找到答案。理解该函数,就真正理解了上面的这些指标的意义。

我们来分析ReplSemiSyncMaster::commitTrx这个函数的核心过程,该函数位于pluginsemisyncsemisync_master.cc文件中。

ReplSemiSyncMaster::commitTrx,这个函数,梳理一下,就关键几步。下面会解释:(代码太长,即使梳理了,贴在页面上感觉也很乱,请自行去semisync_master.cc文件找该函数)

详细步骤解析

该函数代码表面上看似很长,但梳理一下,就这些步骤,相当简单:

第一步:lock();请注意出现lock的地方,这个是自旋锁,如果没有获得锁,就一直在该函数等待,直到获得。同时,也意味着,从这一行开始,到unlock()代码为止,这部分的代码都是线程串行的,也就表明了该段代码的效率影响了整个mysql写事务的吞吐量。

第二步:THD_ENTER_COND(NULL,thd_cond, &LOCK_binlog_,

& stage_waiting_for_semi_sync_ack_from_slave,

& old_stage);

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

第三步:/* Calcuate the waiting period. */计算最大等待时间,按照当前时间加上半同步的等待的timeout时间,这个时间会在发起信号量等待的时候用到,用于设置信号等待的超时时间。 Timeout 的值来自于mysql的半同步设置参数rpl_semi_sync_master_timeout。

第四步:进入while (is_on())循环的入口。进入循环的条件是is_on()函数返回值为true. 这个函数的返回值来自于半同步的状态值Rpl_semi_sync_master_status。表明当前的半同步状态为on/true时,才进入循环。否则直接进入l_end:标记的代码。

第五步:进入while 循环体:

5.1 比较事务所涉及的binlog位置(即commitTrx函数的入参)跟replay的位置比较,如果cmp 大于0,说明此时事务的binlog已经同步到从库,之后直接跳出循环,进入最后的程序段l_end:,比较函数如下:

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

trx_wait_binlog_name,trx_wait_binlog_pos);

5.2 比较事务所涉及的日志的位置跟当前登记的等待位置进行比较,如果事务的binlog位置比当前登记的等待位置wait_file_pos要小,则表明需要调整当前登记的等待位置,如是就有了rpl_semi_sync_master_wait_pos_backtraverse++的动作,将该值加1。到此,就应该清楚show global

status like ‘%semi%’ 命令输出中的这个值的意思了,就是等待位置往后调整的次数。一般情况下是不会做调整的。

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

wait_file_name_, wait_file_pos_);

此外,我们省略了说明当登记的等待binlog文件信息的结构体没有被初始化的时候,将当前事务的Binlog文件与位置作为登记的等待位置的信息。代码如下:

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

wait_file_name_[sizeof(wait_file_name_)- 1]= '';

wait_file_pos_ = trx_wait_binlog_pos;

wait_file_name_inited_ = true;

5.3

正式进入等待binlog同步的步骤。此时会将xxxx_wait_sessions的值加1,该值表明有多少个要提交事务的线程在等待(但这个值是否能够代表实际等待事务的线程数量,这个非常值得怀疑,因为该函数的开始位置就有一个lock函数,没有unlock之前,其他的线程根本进入不了这一步,也就没有办法执行rpl_semi_sync_master_wait_sessions++)。然后发起等待信号,进入信号等待之后,只有两种情况可以退出等待。1,被其他的线程唤醒(即被binlog dump 线程唤醒)。2 等待时间达到超时时间。如果是被唤醒退出,则返回值为0,如果不是,则是其他值。我们看一下这一段的代码。

rpl_semi_sync_master_wait_sessions++;

/* wait for the position to be ACK'edback */

assert(entry);

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)

{

/* This is a real wait timeout. */

rpl_semi_sync_master_wait_timeouts++;

/* switch semi-sync off */

switch_off();

}

else

{

int wait_time;

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;

}

}

函数wait_result= mysql_cond_timedwait(&entry->cond,&LOCK_binlog_, &abstime);就是发起信号等待。然后根据返回结果做相应的计数:

如果是等待超时,则rpl_semi_sync_master_wait_timeouts++ ,同时switch_off();该函数的作用是将半同步状态关闭。

如果等待没有超时,则将等待时间与该次等待计入总数:

rpl_semi_sync_master_trx_wait_num++;

rpl_semi_sync_master_trx_wait_time += wait_time;

此外,还有一个计数是rpl_semi_sync_master_timefunc_fails++; 如果出现这种情况,则表明时钟错误,可能是做了时间调整。

当等待结束之后,会将rpl_semi_sync_master_wait_sessions减1,该值是实时值。

上面是while循环里面的内容,退出循环的条件由两个,只要满足一个即可退出循环。一个是同步状态不是半同步状态,另外一个就是信号等待被唤醒,表明binlog已经被传送到了从库,进入下一次循环时,进入事务的binlog与relay的比较时,会命中cmd>=0,然后退出循环。再次列出binlog与relay比较位置的函数。

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

trx_wait_binlog_name, trx_wait_binlog_pos);

上面是循环体里面的所有内容,接下来我们看退出循环后的操作。特别提一下,唤醒该线程的dump线程,当dump线程收到相应binlog位置的ack之后,会将其唤醒。

第六步:退出循环体,根据是否还是半同步状态,还判断该事务应该往哪里计数。

if(is_on())

rpl_semi_sync_master_yes_transactions++;

else

rpl_semi_sync_master_no_transactions++;

第七步:释放锁与信号量,最后函数返回。

unlock();

THD_EXIT_COND(NULL, & old_stage);

return function_exit(kWho, 0);

直到最后一步,才释放锁,因此该函数是整个实例串行的。同时,中间还有一个信号等待的动作,设想,假如数据库的并发量很大,而此时主从同步出现异常,如果rpl_semi_sync_master_timeout参数设置比较长的时间,则有可能出现大量的其他用户线程杜塞在该函数的lock()操作上,堵赛的时间越长,则累积越来越多的线程,则容易引发雪崩。因此,在设置这个rpl_semi_sync_master_timeout值的时候,要谨慎考虑,并非可以随意。

至此,整个函数流程已经介绍完,当你清楚了上面的函数之后,通过show globalstauts like ‘%semi%’的命令获得的指标,你应该一清二楚。

总结:

通过两篇对semi-sync的源码解析,相信你对semi-sync的机制,以及在日常运维中看到semi-sync的各种指标的含义有了清楚地认识,特别提醒一下,ReplSemiSyncMaster::commitTrx的执行时线程串行,中间有个信号量等待的动作,如果超时时间设置过长,当数据库的事务并发高时,可能造成严重积压,导致数据库崩溃。关注个人公众号,文章均pig君编写,将持续数据库源码级的分享,请关注!

pig君文章均原创,请保护。多谢!


    关注 数据库随笔


微信扫一扫关注公众号

0 个评论

要回复文章请先登录注册