共計 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 行業資訊頻道!