MySQL-Seconds_behind_master的精度误差
网络问题难道是网络问题那我们ping一下吧最多也就相差1ms。那么还有499ms去哪里了呢看来还得继续挖掘。Seconds_behind_master的取点数据直觉上来说网络问题不可能导致500ms这么大的误差而机器配置和MySQL版本又是一样的。这就让笔者不得不怀疑这个兼容数据的准确性。所以就先看看这个500ms是怎么计算出来的。从监控取点数据来看从库C确实有主从延迟不然为什么有那么多取点为0呢。Seconds_behind_master什么时候计算出来为1这时候笔者突然想到一个点如果主从延迟一个是501ms一个是499ms那么Seconds_behind_master计算的时候会不会采用四舍五入法。501ms(500ms)的就是1499(500ms)的就是0为了了解这一问题笔者就去翻了翻源码。Seconds_behind_master在MySQL中的计算源码计算这个指标的代码有很多微妙的分支应对了各种corner case。在此笔者只列出和当前问题相关的源码。long time_diff ((long)(time(0) - mi-rli-last_master_timestamp) - mi-clock_diff_with_master);前面time(0) - mi-rli-last_master_timestamp明显就是指时间差。但是我们要考虑到一个很容易被忽略的常识也就是不同机器的时间戳是不一样的那么很明显的如果主从实际延迟是0但是计算的时候没有剔除掉机器时钟的差异。那么主从延迟就是6s。源码中的mi-clock_diff_with_master就是去修正这个差距而计算这个clock_diff_with_master就会引起不小的误差。什么时候计算clock_diff_with_master笔者在源码中翻阅时候注意到clock_diff_with_master不是每次都去计算的而是在主从连接上或者重连(reconnect)的那一刻去计算一次。handle_slave_io /* 建立主从连接 */ |-safe_connect(thd, mysql, mi)) /* connected: 主从连接成功后计算一下主从clock_diff_with_master */ |-get_master_version_and_clock这就自然会导致下面的现象假设一旦clock_diff_with_master计算有了误差。那么这个误差就会一直存在直到下次重连为止clock_diff_with_master跨秒误差接着笔者又注意到clock_diff_with_master精度只能到秒。那么自然就会出现下面这几种现象。为了简单起见我们假设绝对时钟是从0开始而且我们假设主从延迟是0。只看精度误差所能造成的影响。在实际主从延迟为0的情况下clock_diff_with_master计算出来是-1Seconds_behind_master计算为1尽管有NTP我们也不可能做到两台机器的时间戳在完全一致(除非两台机器有铯原子钟那基本就没有毫秒级的误差了。两台机器之间出现几百毫秒甚至数秒的延迟非常正常。例如假设我当前从库的clock是0.5s主库的clock是1s。那么由于计算精度(只能到秒)的原因实际实际只有0.5s的时间差会放大到1s。那么我们现在可以计算出来在这种情况下Seconds_behind_master的平均值在这里有一个预先假设就是我们取监控点的时间是随机的。在上图中我们可以看到在我们取从库时钟[0.5,1.5)这个1s的时间段范围内。在前0.5s也就是[0.5,1)这个区间中我们计算出来的Seconds_behind_master是0而在[1,1.5)区间计算的确是1。那我们的平均值就可以计算出来为(0.5*00.5*1)/(1.5-0.5)0.5500ms!也就是说在没有任何实际主从延迟的情况下仅仅跨秒这一个因素就能造成好几百毫秒的误差。实际主从延迟为0的情况下clock_diff_with_master计算为0Seconds_behind_master计算为-1并被校正为0另外一个有意思的点是既然误差能加1自然也能减1。也就是Seconds_behind_master计算为-1。这就会给观察人员造成一个错觉从库比主库快当然了MySQL源码考虑到了这一点强制校正为0。在这里笔者将主从连接的那一刻稍微往前偏移0.1s就可以构造出刚才说的现象如下图所示