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複製相關表的解釋

其他參考鏈接

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章