共计 5371 个字符,预计需要花费 14 分钟才能阅读完成。
这篇文章主要为大家展示了“MySQL 中备库 Seconds_Behind_Master 计算的示例分析”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让丸趣 TV 小编带领大家一起研究并学习一下“MySQL 中备库 Seconds_Behind_Master 计算的示例分析”这篇文章吧。
背景
在 mysql 主备环境下,主备同步过程如下,主库更新产生 binlog, 备库 io 线程拉取主库 binlog 生成 relay log。备库 sql 线程执行 relay log 从而保持和主库同步。
理论上主库有更新时,备库都存在延迟,且延迟时间为备库执行时间 + 网络传输时间即 t4-t2。
那么 mysql 是怎么来计算备库延迟的?
先来看 show slave status 中的一些信息,io 线程拉取主库 binlog 的位置:
Master_Log_File: mysql-bin.000001 Read_Master_Log_Pos: 107
sql 线程执行 relay log 的位置:
Relay_Log_File: slave-relay.000003 Relay_Log_Pos: 253
sql 线程执行的 relay log 相对于主库 binlog 的位置:
Relay_Master_Log_File: mysql-bin.000001 Exec_Master_Log_Pos: 107
源码实现
Seconds_Behind_Master 计算的源码实现如下:
if ((mi- get_master_log_pos() == mi- rli- get_group_master_log_pos())
(!strcmp(mi- get_master_log_name(), mi- rli- get_group_master_log_name())))
{ if (mi- slave_running == MYSQL_SLAVE_RUN_CONNECT)
protocol- store(0LL); else protocol- store_null();} else { long time_diff= ((long)(time(0) - mi- rli- last_master_timestamp)
- mi- clock_diff_with_master);
protocol- store((longlong)(mi- rli- last_master_timestamp ? max(0L, time_diff) : 0));
}
大致可以看出是通过时间和位点来计算的,下面详细分析下。
if 里面条件表示如果 io 线程拉取主库 binlog 的位置和 sql 线程执行的 relay log 相对于主库 binlog 的位置相等,那么认为延迟为 0。一般情况下,io 线程比 sql 线程快。但如果网络状况特别差,导致 sql 线程需等待 io 线程的情况,那么这两个位点可能相等,会导致误认为延迟为 0。
再看 else 里:
clock_diff_with_master
io 线程启动时会向主库发送 sql 语句“SELECT UNIX_TIMESTAMP()”,获取主库当前时间,然而用备库当前时间减去此时间或者主备时间差值即为 clock_diff_with_master。这里如果有用户中途修改了主库系统时间或修改了 timestamp 变量,那么计算出备库延迟时间就是不准确的。
last_master_timestamp
表示主库执行 binlog 事件的时间。此时间在并行复制和非并行复制时的计算方法是不同的
非并行复制:
备库 sql 线程读取了 relay log 中的 event,event 未执行之前就会更新 last_master_timestamp,这里时间的更新是以 event 为单位。
rli- last_master_timestamp= ev- when.tv_sec + (time_t) ev- exec_time;
ev- when.tv_sec 表示事件的开始时间。exec_time 指事件在主库的执行时间,只有 Query_log_event 和 Load_log_event 才会统计 exec_time。
另外一种情况是 sql 线程在等待 io 线程获取 binlog 时,会将 last_master_timestamp 设为 0,按上面的算法 Seconds_Behind_Master 为 0,此时任务备库是没有延迟的。
并行复制:
并行复制有一个分发队列 gaq,sql 线程将 binlog 事务读取到 gaq,然后再分发给 worker 线程执行。并行复制时,binlog 事件是并发穿插执行的,gaq 中有一个 checkpoint 点称为 lwm, lwm 之前的 binlog 都已经执行,而 lwm 之后的 binlog 有些执行有些没有执行。
假设 worker 线程数为 2,gap 有 1,2,3,4,5,6,7,8 个事务。worker 1 已执行的事务为 1 4 6, woker 2 执行的事务为 2 3,那么 lwm 为 4。
并行复制更新 gap checkpiont 时,会推进 lwm 点,同时更新 last_master_timestamp 为 lwm 所在事务结束的 event 的时间。因此,并行复制是在事务执行完成后才更新 last_master_timestamp,更新是以事务为单位。同时更新 gap checkpiont 还受 slave_checkpoint_period 参数的影响。
这导致并行复制下和非并行复制统计延迟存在差距,差距可能为 slave_checkpoint_period + 事务在备库执行的时间。这就是为什么在并行复制下有时候会有很小的延迟,而改为非并行复制时反而没有延迟的原因。
另外当 sql 线程等待 io 线程时且 gaq 队列为空时,会将 last_master_timestamp 设为 0。同样此时认为没有延迟,计算得出 seconds_Behind_Master 为 0。
位点信息维护
io 线程拉取 binlog 的位点
Master_Log_File 读取到主库 ROTATE_EVENT 时会更新(process_io_rotate) Read_Master_Log_Pos:io 线程每取到一个 event 都会从 event 中读取 pos 信息并更新
mi- set_master_log_pos(mi- get_master_log_pos() + inc_pos);
sql 线程执行 relay log 的位置
Relay_Log_File
sql 线程处理 ROTATE_EVENT 时更新(Rotate_log_event::do_update_pos)
Relay_Log_Pos:
非并行复制时,每个语句执行完成更新(stmt_done)
并行复制时,事务完成时更新(Rotate_log_event::do_update_pos/ Xid_log_event::do_apply_event/stmt_done)
sql 线程执行的 relay log 相对于主库 binlog 的位置
Relay_Master_Log_File
sql 线程处理 ROTATE_EVENT 时更新(Rotate_log_event::do_update_pos)
Exec_Master_Log_Pos 和 Relay_Log_Pos 同时更新
非并行复制时,每个语句执行完成更新(stmt_done)
并行复制时,事务完成时更新(Rotate_log_event::do_update_pos/ Xid_log_event::do_apply_event/stmt_done)
谈到位点更新就有必要说到两个事件:HEARTBEAT_LOG_EVENT 和 ROTATE_EVENT。
HEARTBEAT_LOG_EVENT
HEARTBEAT_LOG_EVENT 我们的了解一般作用是,在主库没有更新的时候,每隔 master_heartbeat_period 时间都发送此事件保持主库与备库的连接。而 HEARTBEAT_LOG_EVENT 另一个作用是,在 gtid 模式下,主库有些 gtid 备库已经执行同时,这些事件虽然不需要再备库执行,但读取和应用 binglog 的位点还是要推进。因此,这里将这类 event 转化为 HEARTBEAT_LOG_EVENT,由 HEARTBEAT_LOG_EVENT 帮助我们推进位点。
ROTATE_EVENT
主库 binlog 切换产生的 ROTATE_EVENT,备库 io 线程收到时会也有切换 relay log。此 rotate 也会记入 relay log,sql 线程执行 ROTATE_EVENT 只更新位点信息。备库 io 线程接受主库的 HEARTBEAT_LOG_EVENT,一般不用户处理。前面提到,gtid 模式下,当 HEARTBEAT_LOG_EVENT 的位点大于当前记录的位点时,会构建一个 ROTATE_EVENT, 从而让 sql 线程推进位点信息。
if (mi- is_auto_position() mi- get_master_log_pos() hb。log_pos
mi- get_master_log_name() != NULL)
mi- set_master_log_pos(hb。log_pos);
write_ignored_events_info_to_relay_log(mi- info_thd, mi); // 构建 ROTATE_EVENT
......
}
另外,在 replicate_same_server_id 为 0 时,备库接收到的 binlog 与主库 severid 相同时,备库会忽略此 binlog,但位点仍然需要推进。为了效率,此 binlog 不需要记入 relay log。而是替换为 ROTATE_EVENT 来推进位点。
延迟现象
初始主备是同步的,且没有任何更新。假设主备库执行某个 DDL 在都需要 30s,执行某个大更新事务 (例如 insert..select * from) 需要 30s。
不考虑网络延迟。
非并行复制时
执行 DDL:t2 时刻主库执行完,t2 时刻备库执行 show slave status,Seconds_Behind_Master 值为 0。同时 t2 至 t3 Seconds_Behind_Master 依次增大至 30,然后跌 0。
执行大事务:t2 时刻主库执行完,t2 时刻备库执行 show slave status,Seconds_Behind_Master 值为 30。同时 t2 至 t3 Seconds_Behind_Master 依次增大至 60,然后跌 0。
以上区别的原因是 exec_time 只有 Query_log_event 和 Load_log_event 才会统计,普通更新没有统计导致。
并行复制时
执行 DDL:t2 时刻主库执行完,t2 至 t3 备库执行 show slave status,Seconds_Behind_Master 值一直为 0
执行大事务:t2 时刻主库执行完,t2 至 t3 备库执行 show slave status,Seconds_Behind_Master 值一直为 0
这是因为执行语句之前主备是完全同步的,gaq 队列为空,会将 last_master_timestamp 设为 0。而执行 DDL 过程中,gap checkpoint 一直没有推进,last_master_timestamp 一直未 0,直到 DDL 或大事务完成。
所以 t2 至 t3 时刻 Seconds_Behind_Master 值一直为 0。而 t3 时刻有一瞬间 last_master_timestamp 是会重置的,但又因 slave_checkpoint_period 会推进 checkpoint,gaq 队列变为空,会将 last_master_timestamp 重设为 0。
因此 t3 时刻可能看到瞬间有延迟(对于 DDL 是延迟 30s, 对于大事务时延迟 60s)。
这似乎很不合理,gaq 队列为空,会将 last_master_timestamp 设为 0, 这条规则实际可以去掉。
相关 bug
BUG#72376, PREVIOUS_GTIDS_LOG_EVENT 事件记录在每个 binlog 的开头,表示先前所有文件的 gtid 集合。relay-log 本身 event 记录是主库的时间,但 relay log 开头的 PREVIOUS_GTIDS_LOG_EVENT 事件,是在 slave 端生成的,时间也是以 slave 为准的。因此不能用此时间计算 last_master_timestamp。修复方法是在 relay log 写 PREVIOUS_GTIDS_LOG_EVENT 事件是标记是 relay log 产生的,在统计 last_master_timestamp 时,发现是 relay 产生的事件则忽略统计。
if (is_relay_log)
prev_gtids_ev。set_relay_log_event();
...... if (!(ev- is_artificial_event()||...))
rli- last_master_timestamp= ev- when。tv_sec + (time_t) ev- exec_time;
以上是“MySQL 中备库 Seconds_Behind_Master 计算的示例分析”这篇文章的所有内容,感谢各位的阅读!相信大家都有了一定的了解,希望分享的内容对大家有所帮助,如果还想学习更多知识,欢迎关注丸趣 TV 行业资讯频道!