请不要用SECONDS_BEHIND_MASTER来衡量MYSQL主备的延迟时间【转】
mysql 本身通过 show slave status 提供了 seconds_behind_master ,用于衡量主备之间的复制延迟,但是 今天碰到了一个场景,发现 seconds_behind_master 为 0 , 备库的 show slave status 显示io/sql 线程都是正常的 , mysql 的主库上的变更却长时间无法同步到备库上。如果没有人为干预,直到一个小时以后, mysql 才会自动重连主库,继续复制主库的变更。
影响范围: mysql , percona , mariadb 的所有版本。
虽然这种场景非常特殊,遇到的概率并不高,但是个人觉得有必要提醒一下使用 mysql 的 dba 们。通过对这个场景的分析,也有助于我们更加深入的理解 mysql replication 重试机制。
一、重现步骤
搭建主备的复制,临时断开主库的网络,并 kill 掉主库 mysql 的 binlog dump 线程。
此时观察备库的复制情况, show slave status 中:
slave_io_running: yes
slave_sql_running: yes
seconds_behind_master: 0
但是此时你把网络恢复以后,在主库做任何变更,备库都无法获得数据更新了。而且备库上的show slave status 显示: io 线程 sql 线程一切正常,复制延迟一直是 0 。
一切正常,普通的监控软件都不会发现备库有数据延迟。
二、原理分析
mysql 的 replication 是区别于其他数据库很关键的地方。也是可扩展性和高可用的基础。它本身已经非常智能化,只需要我们调用 change master 指定 binlog 文件名和偏移位置就可以搭建从主库到备库的复制关系。
mysql 复制 线程 会自动将目前复制位置记录下来,在主备复制中断的时候自动连上主库,并从上次中断的位置重新开始复制。这些操作都是全自动化的,不需要人为的干预。这给了 mysql dba 带来了很多便利,同时却也隐藏了很多细节。
要真正的理解前面问题的真相以及怎么解决这个问题,我们还是需要真正的理解 mysql 复制的原理。
2.1“推”还是“拉”
首先, mysql 的复制是“推”的,而不是“拉”的。“拉”是指 mysql 的备库不断的循环询问主库是否有数据更新,这种方式资源消耗多,并且效率低。“推”是指 mysql 的主库在自己有数据更新的时候推送这个变更给备库,这种方式只有在数据有变更的时候才会发生交互,资源消耗少。如果你是程序员出身,你一定会选择“推”的方式。
那么 mysql 具体是怎么“推”的列,实际上备库在向主库申请数据变更记录的时候,需要指定从主库binlog 的哪个文件 ( master_log_file ) 的具体多少个字节偏移位置 ( master_log_pos ) 。对应的,主库会启动一个 binlog dump 的线程,将变更的记录从这个位置开始一条一条的发给备库。备库一直监听主库过来的变更,接收到一条,才会在本地应用这个数据变更。
2.2 原因解析
从上面的分析,我们可以大致猜到为什么 show slave status 显示一切正常,但是实际上主库的变更都无法同步到备库上来:
出现问题的时候, binlog dump 程序被我们 kill 掉了。作为监听的一方,备库一直没有收到任何变更,它会认为主库上长时间没有任何变更,导致没有变更数据推送过来。备库是无法判断主库上对应的binlog dump 线程 到底是意外终止了,还是长时间没有任何数据变更的。所以,对这两种情况来说,备库都显示为正常。
当然, mysql 会尽量避免这种情况。比如:
l 在 binlog dump 被 kill 掉时通知备库 线程 被 kill 掉了。所以我们重现时需要保证这个通知发送不到备库,也就是说该问题重现的关键在于 binlog dump 被 kill 的消息由于网络堵塞或者其他原因无法发送到备库。
l 备库如果长时间没有收到从主库过来的变更,它会每隔一段时间重连主库。
2.3 问题避免
基于上面的分析,我们知道 mysql 在这种情况下确实无法避免,那么我们可以有哪些办法可以避开列:
1. 被动处理:修改延迟的监控方法,发现问题及时处理。
2. 主动预防:正确设置 --master-retry-count , --master-connect-retry , --slave-net-timeout 复制重试参数。
l 被动处理
mysql 的延迟监控大部分直接采集 show slave status 中的 seconds_behind_master 。这种情况下,seconds_behind_master 就无法用来真实的衡量主备之间的复制延迟了。我们建议通过在主库轮询插入时间信息,并通过复制到备库的时间差来获得主备延迟的方案。 percona 提供了一种类似的方案 pt-heartbeat(在master上部署,实施监控从的延迟) 。
发现这个问题以后,我们只需要 stop slave; start slave; 重启复制就能解决这个问题。
l 主动预防
mysql 可以指定三个参数,用于复制线程重连主库: --master-retry-count , --master-connect-retry , --slave-net-timeout 。
其中 master-connect-retry 和 master-retry-count 需要在 change master 搭建主备复制时指定,而slave-net-timeout 是一个全局变量,可以在 mysql 运行时在线设置。
具体的重试策略为:备库过了 slave-net-timeout 秒还没有收到主库来的数据,它就会开始第一次重试。然后每过 master-connect-retry 秒,备库会再次尝试重连主库。直到重试了 master-retry-count 次,它才会放弃重试。如果重试的过程中,连上了主库,那么它认为当前主库是好的,又会开始 slave-net-timeout 秒的等待。
slave-net-timeout 的默认值是 3600 秒(mysql版本<= 5.7.6), master-connect-retry 默认为 60 秒, master-retry-count默认为 86400 次。也就是说,如果主库一个小时都没有任何数据变更发送过来,备库才会尝试重连主库。这就是为什么在我们模拟的场景下,一个小时后,备库才会重连主库,继续同步数据变更的原因。
这样的话,如果你的主库上变更比较频繁,可以考虑将 slave-net-timeout 设置的小一点,避免主库binlog dump 线程 终止了,无法将最新的更新推送过来。
当然 slave-net-timeout 设置的过小也有问题,这样会导致如果主库的变更确实比较少的时候,备库频繁的重新连接主库,造成资源浪费。
查看设置情况:show variables
like
'slave_net_timeout'
;
官网的默认配置:
property | value |
---|---|
command-line format | --slave-net-timeout=# |
system variable |
|
scope | global |
dynamic | yes |
type | integer |
default value (>= 5.7.7) | 60 |
default value (<= 5.7.6) | 3600 |
minimum value | 1 |
下一篇: 现代WEB前端的性能优化