共计 3746 个字符,预计需要花费 10 分钟才能阅读完成。
前言
Seconds_behind_master 是咱们察看主从提早的一个重要指标。但任何指标所能示意的精度都是无限的。例如用精度只能到秒的指标去掂量毫秒级的体现就会产生十分大的误差。如果再以此误差去剖析问题,就会让思维走上弯路。例如用 Seconds_behind_master 去评估 1s 内的主从提早就是一个典型的例子。
问题现场
在一些问题的排查中,咱们留神到一个很奇怪的景象。那就是雷同配置的从库体现进去的主从提早差距有将近 500ms。而这两个从库之间的差异就是所在的机房不一样 (和主库都不在同一个机房)。如下图所示:
网络问题
难道是网络问题?那咱们 ping 一下吧,最多也就相差 1ms。那么还有 499ms 去哪里了呢,看来还得持续开掘。
Seconds_behind_master 的取点数据
直觉上来说网络问题不可能导致 500ms 这么大的误差,而机器配置和 MySQL 版本又是一样的。这就让笔者不得不狐疑这个兼容数据的准确性。所以就先看看这个 500ms 是怎么计算出来的。
从监控取点数据来看从库 C 的确有主从提早,不然为什么有那么多取点为 0 呢。
Seconds_behind_master 什么时候计算出来为 1
这时候笔者忽然想到一个点,如果主从提早一个是 501ms 一个是 499ms,那么 Seconds_behind_master 计算的时候会不会采纳四舍五入法。501ms (>=500ms) 的就是 1,499 (<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 计算出来是 – 1,Seconds_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.50+0.51)/(1.5-0.5)=0.5=500ms!
也就是说,在没有任何理论主从提早的状况下,仅仅跨秒这一个因素就能造成好几百毫秒的误差。
理论主从提早为 0 的状况下 clock_diff_with_master 计算为 0,Seconds_behind_master 计算为 – 1 并被校对为 0
另外一个有意思的点是,既然误差能加 1,天然也能减 1。也就是 Seconds_behind_master 计算为 – 1。这就会给察看人员造成一个错觉,从库比主库快!当然了 MySQL 源码思考到了这一点,强制校对为 0。
在这里,笔者将主从连贯的那一刻略微往前偏移 0.1s,就能够结构出方才说的景象,如下图所示:
MySQL 中的源码正文和强行校对逻辑如下所示:
long time_diff= ((long)(time(0) - mi->rli->last_master_timestamp)
- mi->clock_diff_with_master);
/*
Apparently on some systems time_diff can be <0. Here are possible
reasons related to MySQL:
- the master is itself a slave of another master whose time is ahead.
- somebody used an explicit SET TIMESTAMP on the master.
Possible reason related to granularity-to-second of time functions
(nothing to do with MySQL), which can explain a value of -1:
assume the master's and slave's time are perfectly synchronized, and
that at slave's connection time, when the master's timestamp is read,
it is at the very end of second 1, and (a very short time later) when
the slave's timestamp is read it is at the very beginning of second
2. Then the recorded value for master is 1 and the recorded value for
slave is 2. At SHOW SLAVE STATUS time, assume that the difference
between timestamp of slave and rli->last_master_timestamp is 0
(i.e. they are in the same second), then we get 0-(2-1)=-1 as a result.
This confuses users, so we don't go below 0: hence the max().
last_master_timestamp == 0 (an "impossible" timestamp 1970) is a
special marker to say "consider we have caught up".
*/
protocol->store((longlong)(mi->rli->last_master_timestamp ?
max(0L, time_diff) : 0));
如何取得准确的毫秒级的主从提早
因为 Seconds_behind_master 精度的起因,齐全无奈掂量毫秒级的主从提早,所以呈现了 pt-heartbeat 这样的工具去准确的计算主从间毫秒级的提早。在后续采纳 pt-heartbeat 对两个库进行监控后,这两个看上去均匀提早相差 500ms 的从库理论主从提早差距在 10ms 之内。
总结
任何指标都有其示意的精度,而在其精度示意范畴之外就会产生相当大的误差,以至于可能误导咱们的判断。当对某一项的指标感到很反常识的时候,能够思考是不是自身指标并不能形容以后咱们想要察看的景象。例如本文中的论述就表明 Seconds_behind_master 对 1s 的主从提早的刻画没有太大的意义。