1. 程式人生 > 其它 >MySQL 複製延遲計算方式的問題研究

MySQL 複製延遲計算方式的問題研究

目錄

一、MySQL8.0之前原生的Seconds_Behind_Master

在MySQL8.0之前我們可以通過 show slave status 提供的 Seconds_Behind_Master來觀測主從複製之間的延遲情況,以下是官方文件對該引數的釋義

  • 該欄位記錄的是當從庫IO和SQL執行緒正常執行時,從庫當前伺服器主機的時間戳 與 主庫傳送過來的binlog日誌(relay log)中記錄的時間戳的差異
  • 如果沒有任何事務執行,該值預設為0
  • 如果計算的延遲值為負數,該值也會重置為0
    • 如主庫是2020年12月01日(1606752000),從庫是2020年11月01日(1604160000),則由於從庫比主庫時間早,轉換為時間戳相減得到負數,則會被自動轉換為0
      • 1604160000-1606752000=-2592000
  • 當從庫沒有任何需要處理的更新時,如果I/O和SQL執行緒狀態都為Yes,則此欄位顯示為0,如果有任意一個執行緒狀態不為Yes,則此欄位顯示為NULL
  • 這個欄位是度量從庫SQL執行緒和I/O執行緒之間的時間差,單位為秒
    • 如果主備之間的網路非常快,那麼從庫的I/O執行緒讀取的主庫binlog會與主庫中最新的binlog非常接近,所以這樣計算得來得值就可以作為主備之間的資料延遲時間
    • 如果主備之間的網路非常慢,可能導致從庫SQL執行緒正在重放的主庫binlog 非常接近從庫I/O執行緒讀取的主庫binlog,而I/O執行緒因為網路慢的原因可能讀取的主庫binlog遠遠落後於主庫最新的binlog,此時,這麼計算得來的值是不可靠的,儘管這個時候有可能該欄位顯示為0,但實際上可能從庫已經落後於主庫非常多了。所以,對於網路比較慢的情況,該值並不可靠
  • 如果主庫與從庫的伺服器的時間不一致,那麼,只要從庫複製執行緒啟動之後,沒有做過任何時間變更,那麼這個欄位的值也可以正常計算,但是如果修改了伺服器的的時間
    ,則可能導致時鐘偏移,從而導致這個計算值不可靠
    • 計算公式:從庫伺服器時間戳-從庫SQL執行緒正在執行的event的時間戳-主庫與從庫之間的時間差(該時間差只會在從庫I/O執行緒啟動時計算一次,每次重啟I/O執行緒時該值會重新計算)
      • clock_of_slave - last_timestamp_executed_by_SQL_thread - clock_diff_with_master
     The difference in seconds between the clock of the master and the clock of
     the slave (second - first). It must be signed as it may be <0 or >0.
     clock_diff_with_master is computed when the I/O thread starts; for this the
     I/O thread does a SELECT UNIX_TIMESTAMP() on the master.
     "how late the slave is compared to the master" is computed like this:
     clock_of_slave - last_timestamp_executed_by_SQL_thread - clock_diff_with_master
  • 當SQL執行緒重放大事務時,SQL執行緒的時間戳更新相當於被暫停了,此時,根據計算公式可以得出,無論主庫是否有新的資料寫入,所以就會出現主庫停止寫入之後,從庫複製延遲逐漸增大到某個最高值之後突然變為0的情況
  • 主機時間的修改會直接影響資料庫中時間函式獲取的時間以及binlog的時間戳

clock_diff_with_master的獲取

clock_diff_with_master作用是排除主從時間不同步導致的延遲誤差,在IO執行緒第一次啟動時會獲取主庫的系統時間戳,與從庫系統時間戳做對比,在計算主從延遲時會將系統時間的誤差減去,從而排除系統時間不同步的干擾得到真正的延遲,但該操作只會在IO執行緒第一次啟動時觸發,在IO執行緒啟動後對系統時間做修改則會導致延遲的計算出現誤差。

## 從庫在執行了start slave io_thread後,主庫的general日誌中的資訊
2020-03-10T10:36:05.394751+08:00	  634 Connect	[email protected] on  using TCP/IP
2020-03-10T10:36:05.395346+08:00	  634 Query	SELECT UNIX_TIMESTAMP()
2020-03-10T10:36:05.395881+08:00	  634 Query	SELECT @@GLOBAL.SERVER_ID
2020-03-10T10:36:05.396288+08:00	  634 Query	SET @master_heartbeat_period= 30000001024
2020-03-10T10:36:05.398045+08:00	  634 Query	SET @master_binlog_checksum= @@global.binlog_checksum
2020-03-10T10:36:05.398401+08:00	  634 Query	SELECT @master_binlog_checksum
2020-03-10T10:36:05.398743+08:00	  634 Query	SELECT @@GLOBAL.GTID_MODE
2020-03-10T10:36:05.399078+08:00	  634 Query	SELECT @@GLOBAL.SERVER_UUID
2020-03-10T10:36:05.399364+08:00	  634 Query	SET @slave_uuid= '09235915-5c54-11ea-9666-02000aba3d16'
2020-03-10T10:36:05.400822+08:00	  634 Binlog Dump GTID	Log: '' Pos: 4 GTIDs: '56929ffe-5d09-11ea-bb4e-02000aba3da2:1-237069'
  • 日誌顯示從庫634執行緒連線到主庫後執行了SELECT UNIX_TIMESTAMP()操作來獲取系統時間,從庫獲取這個時間與自己的伺服器時間做對比來消除主從伺服器時間不一致的問題。
  • 該操作只在IO執行緒啟動時觸發,如果從庫IO執行緒啟動後對主庫的時間做了修改,如:將主庫時間調慢,則即使主從不存在延遲,Seconds_Behind_Master也仍會記錄有一個系統時間差的延遲(由於主庫修改了了系統時間影響了主庫中binlog記錄的時間戳導致)

Seconds_Behind_Master無法判斷主從延遲的場景

  1. 搭建主從複製,壓測部分資料,使用臨時iptables斷開主庫網路(與從庫不通即可)
  2. kill掉主庫dump執行緒
  3. 觀察從庫的show slave status是否有變化
  4. 恢復主庫網路,在主庫上做壓測模擬資料變更
  5. 檢視從庫的show slave status是否有變化
## 主庫壓測部分資料
sysbench /usr/local/share/sysbench/oltp_read_write.lua --db-ps-mode=disable --mysql-host=127.0.0.1 --mysql-port=3306 --mysql-user=sysbench --mysql-password=sysbench --mysql-db=sbtest --tables=1 --table-size=10000000 --report-interval=1 --time=600 --threads=1 run

## 主庫使用iptables斷開與從庫的網路連線
#!/bin/bash
iptables -A OUTPUT -d 10.186.61.22  -j DROP
iptables -A INPUT -s 10.186.61.22  -j DROP
sleep 120
iptables -F
## 在主庫上kill掉dump執行緒

## 在這期間主庫已經產生大量事務,但從庫由於網路中斷未接收到binlog日誌,Seconds_Behind_Master指標失效,如果10分鐘內網路恢復,則IO執行緒能繼續連上主庫同步binlog,如果不能則報錯主庫無法連線
	net_retry_count    = 10 ## 重試10次
	slave_net_timeout  = 60 ## 每次60秒超時

dump執行緒是推資料還是拉資料

MySQL 的複製是“推”的,而不是“拉”的。

  • “拉”是指 MySQL 的備庫不斷的迴圈詢問主庫是否有資料更新,這種方式資源消耗多,並且效率低。
  • “推”是指 MySQL 的主庫在自己有資料更新的時候推送這個變更給備庫,這種方式只有在資料有變更的時候才會發生互動,資源消耗少。
  • 實際上備庫在向主庫申請資料變更記錄的時候,需要指定從主庫Binlog 的哪個檔案 ( MASTER_LOG_FILE ) 的具體多少個位元組偏移位置 ( MASTER_LOG_POS ),對應的,主庫會啟動一個 Binlog dump 的執行緒,將變更的記錄從這個位置開始一條一條的發給備庫。備庫一直監聽主庫過來的變更,接收到一條,才會在本地應用這個資料變更。

二、Percona pt-heartbeat

pt-heartbeat是percona公司開發用來更為精確的檢測主從延遲的小工具,以下是其工作原理介紹

  1. 首先通過pt-heartbeat在主庫建立一張heartbeat表,並以--interval(seconds)引數指定的頻率寫入timestamp型別資料(heartbeat record)
  2. 然後在從庫回放該記錄時,通過與從庫的系統時間做比對計算出時間差,得出主從延遲的具體數值,這樣當主從延遲或複製中斷,延遲值仍會不斷增加

引數解釋

## 連線相關
--database					指定heartbeat表所在的資料庫
--ask-pass					是否通過非命令列的方式輸入密碼
--host						指定資料庫的IP
--password					指定資料庫的密碼
--port						指定資料庫的埠
--socket					指定資料庫的套接字檔案
--table						指定建立的heartbeat表名稱,預設就叫做heartbeat
--user						指定資料庫的使用者
--charset					指定連線的字符集,預設utf8
## 初始化心跳錶
--create-table				指定該引數則自動建立heartbeat表
--create-table-engine		指定heartbeat表的儲存引擎,預設InnoDB,也可設定為memory
--check						檢測一次從庫的延遲後退出,還可指定--recurse找到所有從庫延遲
--check-read-only			如果資料庫是read-only的,check時不會做任何insert操作
--read-only-interval		read-only狀態的檢測頻率
## pt-heartbeat執行相關
--run-time					指定pt-heartbeat工具執行的時間
--stop						可以指定建立哨兵檔案來停止pt-heartbeat執行
--sentinel					指定一個哨兵檔案,如果沒指定--run-time,則如果哨兵檔案存
--daemonize					將心跳檢測命令後臺執行
--file						將最後一條心跳資料輸出到檔案,可以結合--daemonize使用
--log						當開啟--daemonize模式時,將執行日誌儲存到該引數指定的檔案
--interval					heartbeat表更新的頻率,預設1s
--pid						指定pt-heartbeat工具執行時的pid檔案
--config					指定引數配置檔案,可以把多個引數指定為一個配置檔案
--dbi-driver				預設為mysql,也支援pg
--defaults-file				讀取的MySQL連線資訊檔案,需要提供絕對路徑
--frames					顯示1m,5m,15m的延遲情況
--[no]insert-heartbeat-row	是否預設對heartbeat表插入一條心跳資料,預設yes
--master-server-id			明確指定主庫的server-id
--monitor					持續監測主從的資料延遲	
--print-master-server-id	在監測輸出時一同列印主庫的server-id
--recurse					遞迴的方式檢測關聯的所有從庫
--recursion-method			遞迴的方式:processlist或hosts,[show processlist|show slave status]
--replace					對heartbeat表不使用普通的update,而是用replace 方式替換資料
--update					對heartbeat表使用普通的update更新
--slave-user				如果從庫有不同的賬戶時明確指定
--slave-password			如果從庫有不同的密碼時明確指定
--set-vars					設定pt-heartbeat連線MySQL時session級別的變數
--skew						延遲檢測的頻率,預設0.5s

使用示例

## 連線主庫在percona資料庫中建立heartbeat表並將延遲檢測的程式後臺執行
## 每秒鐘對heartbeat表做replace更新操作
pt-heartbeat --host=10.186.61.162 --user=zhenxing --port=3306 --password=zhenxing --database=percona --table=heartbeat --create-table --update --replace --daemonize  --log=/tmp/pt-heartbeat.log --interval=1

## 連線從庫實時檢測主從延遲情況
pt-heartbeat --host=10.186.61.22 --user=zhenxing --port=3306 --password=zhenxing --database=percona --table=heartbeat --print-master-server-id --monitor

heartbeat表相關資訊

-- 表結構
-- 其中relay_master_log_file和exec_master_log_pos欄位通過show slave status獲取,且只有資料庫既是主又是從時才有值,也就是級聯複製的場景
root@localhost[percona]> desc heartbeat;
+-----------------------+---------------------+------+-----+---------+-------+
| Field                 | Type                | Null | Key | Default | Extra |
+-----------------------+---------------------+------+-----+---------+-------+
| ts                    | varchar(26)         | NO   |     | NULL    |       |
| server_id             | int(10) unsigned    | NO   | PRI | NULL    |       |
| file                  | varchar(255)        | YES  |     | NULL    |       |
| position              | bigint(20) unsigned | YES  |     | NULL    |       |
| relay_master_log_file | varchar(255)        | YES  |     | NULL    |       |
| exec_master_log_pos   | bigint(20) unsigned | YES  |     | NULL    |       |
+-----------------------+---------------------+------+-----+---------+-------+


-- 資料示例
root@localhost[percona]> select * from heartbeat\G
*************************** 1. row ***************************
                   ts: 2020-03-10T16:54:50.001070
            server_id: 33
                 file: mysql-bin.000058
             position: 218470087
relay_master_log_file: NULL
  exec_master_log_pos: NULL
1 row in set (0.00 sec)

-- replace方式更新語句
REPLACE INTO `percona`.`heartbeat` (ts, server_id, file, position, relay_master_log_file, exec_master_log_pos) VALUES ('2020-03-10T16:55:42.001460', '33', 'mysql-bin.000058', '218500663', NULL, NULL)

-- update方式更新語句
UPDATE `percona`.`heartbeat` SET ts='2020-03-10T16:56:50.001400', file='mysql-bin.000058', position='218534058', relay_master_log_file=NULL, exec_master_log_pos=NULL WHERE server_id='33'

三、MySQL8.0之後P_S庫下replication相關狀態表

WL#7319 和 WL#7374 共同完善了複製延遲觀測

WL#7319 Infrastructure for GTID based delayed replication and replication lag monitoring 在binlog 的 gtid_log_event (啟用 GTID)和 anonymous_gtid_log_event(未啟用 GTID)新增事務提交時間戳。將事務原始提交時間寫在 binlog 中,提交時間在複製鏈路上傳遞,使得 slave 可以計算事務延遲。

  • original_commit_timestamp 事務在 master 提交 binlog 的時間戳(微秒),該時間戳每個節點都是一樣的。
  • immediate_commit_timestamp 事務在 slave(包括中繼節點)提交 binlog 的時間戳(微秒),該時間戳在 relay log 中與 original_commit_timestamp 一樣,在 slave 的 binlog 是完成回放的時間戳。

WL#7374 Performance schema tables to monitor replication lags and queue 為 performance_schema 複製相關表新增觀測點。

  • replication_connection_status 記錄事件接收執行緒(IO Thread)工作狀態
  • replication_applier_status_by_coordinator 記錄啟用並行回放的協調執行緒工作狀態
  • replication_applier_status_by_worker 記錄事件回放執行緒(SQL Thread)工作狀態
-- 檢視MGR叢集角色及狀態
select * from performance_schema.replication_group_members;


-- 檢視主庫當前的binlog(GTID)情況
select 
    COUNT_TRANSACTIONS_IN_QUEUE,
    COUNT_CONFLICTS_DETECTED,
    LAST_CONFLICT_FREE_TRANSACTION,
    COUNT_TRANSACTIONS_REMOTE_IN_APPLIER_QUEUE
 from performance_schema.replication_group_member_stats
where 1=1
and LAST_CONFLICT_FREE_TRANSACTION !='';

-- 檢視從庫接收到的binlog(relaylog)情況
select 
    LAST_QUEUED_TRANSACTION as "從庫接收到的binlog對應GTID",
    date_format(LAST_QUEUED_TRANSACTION_ORIGINAL_COMMIT_TIMESTAMP,'%Y-%m-%d %H:%i:%s') as "從庫接收到的binlog對應時間",
    date_format(LAST_QUEUED_TRANSACTION_START_QUEUE_TIMESTAMP,'%Y-%m-%d %H:%i:%s') as "從庫寫入Relaylog的開始時間",
    date_format(LAST_QUEUED_TRANSACTION_END_QUEUE_TIMESTAMP,'%Y-%m-%d %H:%i:%s') as "從庫寫入Relaylog的結束時間"
from performance_schema.replication_connection_status
where 1=1
and SERVICE_STATE='ON'
and CHANNEL_NAME='group_replication_applier';

-- 檢視從庫回放的relaylog情況(取已回放的世界最新的一條即可)
select 
LAST_APPLIED_TRANSACTION as "已回放的GTID",
date_format(LAST_APPLIED_TRANSACTION_ORIGINAL_COMMIT_TIMESTAMP,'%Y-%m-%d %H:%i:%s') as "已回放的GTID時間",
APPLYING_TRANSACTION as "正在回放的GTID",
date_format(APPLYING_TRANSACTION_ORIGINAL_COMMIT_TIMESTAMP,'%Y-%m-%d %H:%i:%s') as "正在回放的GTID時間"
from performance_schema.replication_applier_status_by_worker
where CHANNEL_NAME='group_replication_applier'
and SERVICE_STATE='ON'
and LAST_APPLIED_TRANSACTION is not null
and LAST_APPLIED_TRANSACTION !=''
order by LAST_APPLIED_TRANSACTION_ORIGINAL_COMMIT_TIMESTAMP desc limit 1;

四、總結對比

Seconds_Behind_Master

  • 並不能反應真實的業務事務同步或回放延遲。
  • 在網路異常時延遲的資料不準確

pt-heartbeat

  • 觀測粒度只能達到秒級,精度不夠
  • 心跳程序單點風險,心跳程序不可用則延遲檢測失效
  • 汙染 binlog,大量心跳事件佔據 binlog,更多空間佔用,干擾排查和日誌恢復

MySQL8.0 P_S庫replication相關表

  • 粒度更細,支援微妙級別
  • 支援不同階段的延遲檢測
    • 可以檢測IO程序是否存在延遲
    • 可以檢測排程程序是否存在延遲
    • 可以檢測SQL程序是否存在延遲
    • 可以總體檢測主庫事務提交到從庫SQL執行緒回放之間的延遲

五、參考連結

Seconds_Behind_Master官方文件解釋

pt-heartbeat官方使用手冊

MySQL8.0 對performance_schema複製相關表的解釋

其他參考連結

轉載請說明出處 |QQ:[email protected]