[翻譯]如何定位和解決MySQL主從同步延遲

原文: https://www.percona.com/blog/2014/05/02/how-to-identify-and-cure-mysql-replication-slave-lag/

Percona的MySQL支持團(tuán)隊經(jīng)常會看到用戶在抱怨數(shù)據(jù)庫同步延遲,并且大多數(shù)都是由于MySQL復(fù)制slave端的落后引起的。這對于MySQL用戶來說從來都不是什么新鮮事,這幾年我們也在 MySQL Performance Blog 中關(guān)于這個話題發(fā)過幾個帖子(Percona CEO的Peter Zaitsev寫的“MySQL同步延遲的原因”“處理MySQL的Slave端延遲”尤其值得一看)

然而在今天這篇文章中,我將會分享一些定位同步延遲的方法(包括導(dǎo)致slave落后的一些可能性)以及如何解決這些問題。

如何定位同步延遲
MySQL 同步是通過兩個線程完成的:IO_THREAD 和SQL_THREAD。IO_THREAD 與master端鏈接并且讀取它的二進(jìn)制日志事件,同時將讀到的數(shù)據(jù)復(fù)制寫入本地的中繼日志。另一方面,SQL_THREAD從中繼日志中的事件,并且在slave端盡快執(zhí)行。如果發(fā)現(xiàn)同步延遲了,那第一步就是要確定是哪個線程引起的問題。

一般來說,I/O線程并不會造成很大的同步延遲,因為它只負(fù)責(zé)從master端讀取二進(jìn)制日志。然而這還是得看兩個服務(wù)器之間的網(wǎng)絡(luò)鏈接狀況和延遲。slave端的I/O線程可能會因為帶寬被占用的太多而運(yùn)行緩慢。通常如果IO_THREAD可以較快地讀取二進(jìn)制日志并且寫入中繼文件,那么這表示IO_THREAD不是slave落后的罪魁禍?zhǔn)住?/p>

另一方面,如果是SQL_THREAD造成同步延遲的話,那很可能是因為獲取的那些請求在slave端執(zhí)行消耗了很長的時間。這種情況有時是因為主從兩個機(jī)子的硬件性能差別導(dǎo)致的,也可能是由于不同的索引配置導(dǎo)致的,也有可能是負(fù)荷不同導(dǎo)致的。另外,slave的OLTP有時可能會因為加鎖策略而導(dǎo)致同步延遲。舉個例子,如果存在一個長時間運(yùn)行的MyISAM讀取操作,則可能會阻塞SQL線程,或者InnoDB上的數(shù)據(jù)交換會造成IX鎖并鎖住SQL縣城的DDL。另外,如果把MySQL 5.6版本之前的單線程slave這種情況也算在里面的話,那也可能導(dǎo)致slave端的SQL_THREAD延遲。

讓我來通過master和slave端的狀態(tài)來展示一些例子,并分析是IO_THREAD還是SQL_THREAD導(dǎo)致的延遲。

mysql-master> SHOW MASTER STATUS;
+------------------+--------------+------------------+------------------------------------------------------------------+
| File | Position  | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set                                                |
+------------------+--------------+------------------+------------------------------------------------------------------+
| mysql-bin.018196 | 15818564     |                  | bb11b389-d2a7-11e3-b82b-5cf3fcfc8f58:1-2331947                   |
+------------------+--------------+------------------+------------------------------------------------------------------+
mysql-slave> SHOW SLAVE STATUSG
*************************** 1. row ***************************
Slave_IO_State: Queueing master event to the relay log
Master_Host: master.example.com
Master_User: repl
Master_Port: 3306
Connect_Retry: 60
Master_Log_File: mysql-bin.018192
Read_Master_Log_Pos: 10050480
Relay_Log_File: mysql-relay-bin.001796
Relay_Log_Pos: 157090
Relay_Master_Log_File: mysql-bin.018192
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
Replicate_Do_DB:
Replicate_Ignore_DB:
Replicate_Do_Table:
Replicate_Ignore_Table:
Replicate_Wild_Do_Table:
Replicate_Wild_Ignore_Table:
Last_Errno: 0
Last_Error:
Skip_Counter: 0
Exec_Master_Log_Pos: 5395871
Relay_Log_Space: 10056139
Until_Condition: None
Until_Log_File:
Until_Log_Pos: 0
Master_SSL_Allowed: No
Master_SSL_CA_File:
Master_SSL_CA_Path:
Master_SSL_Cert:
Master_SSL_Cipher:
Master_SSL_Key:
Seconds_Behind_Master: 230775
Master_SSL_Verify_Server_Cert: No
Last_IO_Errno: 0
Last_IO_Error:
Last_SQL_Errno: 0
Last_SQL_Error:
Replicate_Ignore_Server_Ids:
Master_Server_Id: 2
Master_UUID: bb11b389-d2a7-11e3-b82b-5cf3fcfc8f58:2-973166
Master_Info_File: /var/lib/mysql/i1/data/master.info
SQL_Delay: 0
SQL_Remaining_Delay: NULL
Slave_SQL_Running_State: Reading event from the relay log
Master_Retry_Count: 86400
Master_Bind:
Last_IO_Error_Timestamp:
Last_SQL_Error_Timestamp:
Master_SSL_Crl:
Master_SSL_Crlpath:
Retrieved_Gtid_Set: bb11b389-d2a7-11e3-b82b-5cf3fcfc8f58:2-973166
Executed_Gtid_Set: bb11b389-d2a7-11e3-b82b-5cf3fcfc8f58:2-973166,
ea75c885-c2c5-11e3-b8ee-5cf3fcfc9640:1-1370
Auto_Position: 1

我們很容易可以看出slave端的IO_THREAD已經(jīng)落后,并且因此SQL_THREAD也落后了。我們可以看到master端的日志文件是mysql-bin.018196(通過master狀態(tài)的文件參數(shù)可以看到),而slave的IO_THREAD正在讀取的是mysql-bin.018192(slave狀態(tài)中的Master_Log_File),這意味著slave的IO_THREAD正在讀取的是后者而master端在寫的是前者,所以slave的IO_THREAD落后了4個binlogs文件。同時,slave的SQL_THREAD正在讀取文件mysql-bin.018192(slave狀態(tài)的Relay_Master_Log_File)。這意味著slave的SQL_THREAD處理時間的速度足夠快,但是他也有所落后了,這從slave狀態(tài)的Read_Master_Log_Pos 和 Exec_Master_Log_Pos可以看出。

你可以通過 Read_Master_Log_PosExec_Master_Log_Pos 得到的差值來計算SQL_THREAD的落后,但前提是Master_Log_FileRelay_Master_Log_File是相同的。這可以讓你大概知道slave的 SQL_THREAD 處理事件的速度。正如我前面所說的,在這個例子中slave的IO_THREAD也處于落后狀態(tài),所以SQL_THREAD 也落后了。關(guān)于slave的狀態(tài)相關(guān)參數(shù),可以參考這篇文章

另外Seconds_Behind_Master參數(shù)顯示slave端存在巨大的延遲。然而這可能會產(chǎn)生一些誤導(dǎo),因為這個參數(shù)是通過計算最新獲取的中繼日志和正在執(zhí)行的中繼日志的時間戳差值得到的。如果還有部分master端的日志沒有獲取,那么這樣計算出來的就不是和master端真正的時間差值。你可以通過使用 Percona Toolkit. pt-heartbeat 來獲取更加準(zhǔn)確的延遲時間。所以到目前為止我們已經(jīng)知道了如何判別是哪一個縣城導(dǎo)致了slave端的同步落后,接下來我將介紹一些解決方案和建議。

關(guān)于造成同步延遲的原因和一些解決方案的建議
一般如果slave的 IO_THREAD落后是因為網(wǎng)絡(luò)速度慢導(dǎo)致的。大部分情況下打開 slave_compressed_protocol 可以緩解這個癥狀。另外,如果不需要緊急備份還原的話就關(guān)掉slave的二進(jìn)制文件記錄功能,以此來環(huán)節(jié)IO上的壓力。

為了最小化slave的SQL_THREAD 延遲,則需要優(yōu)化各個請求。我的建議是打開 log_slow_slave_statements 配置來找到slave端執(zhí)行時間超過long_query_time 的請求語句。如果要獲取更多關(guān)于請求的性能,可以將log_slow_verbosity 配置為“full”。

這樣我們就可以觀察到slave的SQL_THREAD 的那些查詢請求消耗了多少時間。讀者可以很據(jù)我之前的那篇文章來了解如何通過上述提到的配置選項來記錄執(zhí)行較慢的請求日志。log_slow_slave_statements作為提醒功能的日志第一次出現(xiàn)在Percona Server 5.1中,而現(xiàn)在已經(jīng)是MySQL 5.6.11版本后的普通功能。在更早版本的MySQL Server中l(wèi)og_slow_slave_statements是作為命令行選項存在的。更多的細(xì)節(jié)可以參考 這篇文章 關(guān)于Percona Server特有的log_slow_verbosity功能。

另外還有一個造成slave的SQL_THREAD延遲的可能:如果你使用基于行的binlog格式,并且某些表缺少主鍵或者唯一鍵則所有的SQL_THREAD會掃描全表并造成同步延遲。所以需要確保你的表有主鍵或者唯一鍵。這個bug的詳情可以參考http://bugs.mysql.com/bug.php?id=53375。你可以通過下列語句來確定slave上的數(shù)據(jù)v庫表是否有主鍵缺失。

mysql> SELECT t.table_schema,t.table_name,engine
FROM information_schema.tables t INNER JOIN information_schema .columns c
on t.table_schema=c.table_schema and t.table_name=c.table_name
GROUP BY t.table_schema,t.table_name
HAVING sum(if(column_key in ('PRI','UNI'), 1,0)) =0;

MySQL 5.6在這個場景上作出了優(yōu)化,即在內(nèi)存散列中使用slave_rows_search_algorithms

Seconds_Behind_Master在我們讀取一個巨大的RBR事件時并不會更新,所以“落后”只和我們未讀取的事件有關(guān)。舉個例子,在一個基于行的同步中,巨大的數(shù)據(jù)修改會造成slave端的延遲,比如你有1億行數(shù)據(jù)的表中你執(zhí)行了“DELETE FROM table WHERE id < 5000000” ,5000萬行的數(shù)據(jù)會發(fā)送到slave端,并且每行處理的都非常慢。所以如果你需要刪除一個巨大表的多行老數(shù)據(jù),使用分表也許是個不錯的代替方案:用DROP舊部分的數(shù)據(jù)來代替DELETE在同步中會表現(xiàn)得更好。

為了更好地介紹這個方法,我們假設(shè)你有partition1 擁有ID是 11000000的行,partition2擁有ID是 1000001 到 **2000000 **的行。然后取代“DELETE FROM table WHERE ID<=1000000;”的方法就可以是“ALTER TABLE DROP partition1;”了解更多分表相關(guān)的信息可以參照對應(yīng)的 手冊,以及另外一篇由我的同時Roman寫的關(guān)于同步延遲的文章

pt-stalkPercona Toolkit 中最好用的工具之一,它可以問題發(fā)生時候相關(guān)的診斷數(shù)據(jù)。你可以通過下面介紹的方法來搭建pt-stalk,之后不管什么時候slave延遲發(fā)生了,它都能將診斷信息存入日志,從而我們就可以更輕松得發(fā)現(xiàn)罪魁禍?zhǔn)住?/p>

通過下列腳本就可以搭建好pt-stalk來找出同步延遲的原因:

------- pt-plug.sh contents
#!/bin/bash
trg_plugin() {
mysqladmin $EXT_ARGV ping &> /dev/null
mysqld_alive=$?
if [[ $mysqld_alive == 0 ]]
then
seconds_behind_master=$(mysql $EXT_ARGV -e "show slave status" --vertical | grep Seconds_Behind_Master | awk '{print $2}')
echo $seconds_behind_master
else
echo 1
fi
}
# Uncomment below to test that trg_plugin function works as expected
#trg_plugin
-------
-- That's the pt-plug.sh file you would need to create and then use it as below with pt-stalk:
$ /usr/bin/pt-stalk --function=/root/pt-plug.sh --variable=seconds_behind_master --threshold=300 --cycles=60 --notify-by-email=muhammad@example.com --log=/root/pt-stalk.log --pid=/root/pt-stalk.pid --daemonize

You can adjust the threshold, currently its 300 seconds, combining that with –cycles, it means that if seconds_behind_master value is >= 300 for 60 seconds or more then pt-stalk will start capturing data. Adding –notify-by-email option will notify via email when pt-stalk captures data. You can adjust the pt-stalk thresholds accordingly so that’s how it triggers to collect diagnostic data during problem.
你可以調(diào)整延遲閾值,當(dāng)前是 300 秒,這意味著如果seconds_behind_master值大于300的時間超過60 秒, pt-stalk 將會開始記錄數(shù)據(jù)。添加–notify-by-email 選項就可以在pt-stalk 記錄數(shù)據(jù)的同時發(fā)送電子郵件到你的郵箱。你可以調(diào)整根據(jù)實際情況來調(diào)整pt-stalk的閾值在問題發(fā)生時決定何時出發(fā)相關(guān)的數(shù)據(jù)收集。

結(jié)論
slave端的延遲雖然不是什么大問題,但是卻在MySQL同步的使用中非常常見。我試著在這篇文章中覆蓋同步延遲的方方面面。如果你知道任何其他導(dǎo)致同步延遲的可能,請務(wù)必在留言區(qū)分享給大家,謝謝!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,002評論 6 542
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,400評論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,136評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,714評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 72,452評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,818評論 1 328
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,812評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,997評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,552評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,292評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,510評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,035評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,721評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,121評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,429評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,235評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 48,480評論 2 379

推薦閱讀更多精彩內(nèi)容