两年前就写过一篇文章解释Seconds_Behind_Master代表的含义以及它为什么不准确,今天同事高老师又提了一个有趣的问题:Seconds_Behind_Master到底是怎么计算的呢?高老师还特地去翻了一下源码来解释,我发现我之前的理解还是有出入的,于是自己也动手去翻了一下源码,下面就来更全面的解释一下它是怎么计算的,为什么不能完全可信。
我平时读MySQL源码比较少,一般来说通过源码也是查一些基本的问题,对于我来说如果对关键代码位置不熟,比较快捷的方法就是cd到源码的根目录,然后grep "Seconds_Behind_Master" . -R -n。结果如下:
./sql/rpl_rli.cc:1209: Seconds_Behind_Master - not critical).
./sql/slave.cc:1345: "do not trust column Seconds_Behind_Master of SHOW "
./sql/slave.cc:1874: field_list.push_back(new Item_return_int("Seconds_Behind_Master", 10,
./sql/slave.cc:1963: Seconds_Behind_Master: if SQL thread is running and I/O thread is
./sql/slave.cc:3254: alive and connected, this is going to make Seconds_Behind_Master be 0
./sql/slave.cc:3258: Seconds_Behind_Master grows. No big deal.
./sql/slave.cc:4666: We say in Seconds_Behind_Master that we have "caught up". Note that
./sql/slave.cc:4679: Seconds_Behind_Master would be zero only when master has no
从搜索结果来看,主要设计到sql/slave.cc以及sql/rpl_rli.cc,而恰好这两个文件已经可以解决我们的疑惑了。
首先第一个问题,Seconds_Behind_Master到底是怎么计算的?
我们先来看手册上的解释:
This field is an indication of how “late” the slave is:
When the slave is actively processing updates, this field shows the difference between the current timestamp on the slave and the original timestamp logged on the master for the event currently being processed on the slave.
When no event is currently being processed on the slave, this value is 0.
大意就是如果slave正在处理更新,那么sbm的计算方式就是:slave当前的时间戳-正在执行更新的binlog event上附带的timestamp.如果slave没有处理更新,那么sbm=0
那么这么解释对吗?答案是基本上算对,但是里面没解释一些细节。然后我们通过在slave.cc里面找到这个真正的计算公式:
1962 /*
1963 Seconds_Behind_Master: if SQL thread is running and I/O thread is
1964 connected, we can compute it otherwise show NULL (i.e. unknown).
1965 */
1966 if ((mi->slave_running == MYSQL_SLAVE_RUN_CONNECT) &&
1967 mi->rli.slave_running)
1968 {
1969 long time_diff= ((long)(time(0) - mi->rli.last_master_timestamp)
1970 - mi->clock_diff_with_master);
此处省略很多注释
1991 protocol->store((longlong)(mi->rli.last_master_timestamp ?
1992 max(0, time_diff) : 0));
1993 }
1994 else
1995 {
1996 protocol->store_null();
1997 }
关键的代码如下:
1. long time_diff= ((long)(time(0) - mi->rli.last_master_timestamp)
- mi->clock_diff_with_master);
2. protocol->store((longlong)(mi->rli.last_master_timestamp ?
max(0, time_diff) : 0));
到这里终于看到传说中的sbm计算方法了,这里解释各个变量的含义:
time(0) //从库当前系统时间戳,Linux系统函数
mi->rli.last_master_timestamp //当前从库正在执行语句binlog event时间戳
mi->clock_diff_with_master //主从系统时间戳的差值,slave-master
到这里我们就知道手册上其实描述不准确,还少了一部分clock_diff_with_master,因为主库上记录binlog event的时间戳与从库上计算本地时间戳time(0)都是调用系统的时间函数,而此时假如说主从时间设置不一致,那么这个值不就完全没意义了吗?因此为了尽量避免这种情况出现,每次在从库与主库建立连接的时候都会获取主从的时间戳,然后算出一个差值作为一个常量保存,以后每次算sbm时都会减去这个值。但是有一个问题存在,MySQL只会在建立主从复制连接的时候算这个值,以后都不会再更新,因此假如说主从连接建立好了后去更改主从时间设置,比如NTPD,比如set timestamp=xxx之类的操作,那么此时看到的sbm的值就会更加不可靠了。
在正常情况下第一句就已经是我们show slave status看到的sbm值了,那么第二句是为了解决什么问题呢?
1. clock_diff_with_master是主从SELECT UNIX_TIMESTAMP()的差值,很有可能主库执行的时候是1,从库执行的时候是2(因为并不能保证主从是同一时刻执行),那么此时clock_diff_with_master=1, 那么假设此时time(0)-last_master_timestamp的值等于0,那么0-1=-1,而-1会给用户歧义,于是官方在这种情况下会强制把负值变成0
2. 前面说了sbm的值分两种情况,从库有SQL线程在处理语句时计算方法刚详细解释过,从库SQL线程没有处理语句是把这个值设为0,那么第二句代码就实现了这个功能,第二句的三元表达式告诉我们,当mi->rli.last_master_timestamp值为0的时候,sbm=0。那么mi->rli.last_master_timestamp值的更新逻辑是怎样的呢?
主要有两个地方:
其一,rpl_rli.cc/Relay_log_info::stmt_done
每次从relay log解析出一条binlog event执行时,last_master_timestamp= event_creation_time;
其二,slave.cc/static Log_event* next_event(Relay_log_info* rli)
进入到下面逻辑的前提条件是relay log已经执行完了,SQL线程在等待relay log有更新
4686 time_t save_timestamp= rli->last_master_timestamp; //先把最后一次处理的binlog event timestamp保存起来,等将来主库又推了binlog过来后第一次计算sbm时就可以用了,这也是为什么有时候start slave后第一次show slave status看到sbm值非常大的一个原因
4687 rli->last_master_timestamp= 0; //把值设为0,那么前面的三元表达式计算的sbm就是0
... 省略部分代码
4779 rli->relay_log.wait_for_update_relay_log(rli->sql_thd); //等待relay log有更新
4780 // re-acquire data lock since we released it earlier
4781 mysql_mutex_lock(&rli->data_lock);
4782 rli->last_master_timestamp= save_timestamp; //重新设置回原值
4783 continue;
然后再来第二个大问题:Seconds_Behind_Master是否可靠?其值为0是否表示主从数据完全一致?
答案必然是否定的。
1. sbm是表示的是relay log中event的延时,而MySQL默认复制是异步的,因此可能从库relay log执行完,但是主从由于网络以及这种原因主库上的binlog没有推送过来,而且我们实际线上也遇到过由于网络原因sbm=0,但主库的binlog根本就没推送过来,一般此时通过stop slave/start slave能发现这种假象。所以平常我们去监控主从延时也不会直接用sbm做判断标准,而是主从建立一张heartbeat表,往主库插入一条数据来判断延时情况。
2. 如果主从时间不同步,那么可能导致sbm延时值不准确
3. 5.6如果开启了多线程复制,那么这个值就更加不准了。我这里也很好奇大家的线上5.6如果开启了MTS是怎么来监控主从延时的?
--EOF--