1. 程式人生 > 資料庫 >第29問:MySQL 的複製心跳說它不想跳了

第29問:MySQL 的複製心跳說它不想跳了

問題

最近年底,大家的資料庫經常跑批量大事務,會發現複製突然斷開,報錯“心跳與本地資訊不相容”: 會是什麼原因?

實驗

我們先來複現一下,再進行分析。

寬油,做一對主從資料庫:

我們先造一個 500M 的空檔案,下一步有用:

再製造一張大表,這裡用到了之前的造表法,不同的是使用了一個 longblob 欄位,讓少數的幾行記錄就能佔用很大的 binlog 空間,方便我們後面做實驗。

這裡的 longblob 欄位,用到了上一步我們做的空檔案,

這樣我們獲得了一個行數較少,但體積很大的表。

現在起兩個會話,一個事務造表 t2,一個事務造表 t3,並同時提交操作,以下舉例其中一個事務:

這樣就獲得了一個超大的 binlog,一共 32G,前 16G 是一個事務,後 16G 是另一個事務。


小貼士

一個事務超過 binlog 的限制大小(最大 1G),就會在事務後直接切換到新的 binlog。

在同一個 binlog 中,我們想讓一個超大事務後再記錄一個事務,所以讓兩個事務同時提交,放在同一個提交組中。


檢視一下 master 上的 GTID,最後兩個事務分別是 25 和 26:

下面登入到 slave上,開始表演:

我們先重置 GTID 和複製狀態,然後騙 slave 說它已經接到了 1-25 事務,要從 26 號事務開始傳輸,也就是從 32G binlog 的中間位置開始傳輸。

然後開始複製的 IO 執行緒,過十幾秒,就可以看到複製報錯:

檢視 Error log:

和我們想要復現的報錯一樣。

下面我們來看一下原理:

這個復現中有幾個要素:

  1. 從報錯得知,報錯與心跳有關,複製線必須配置複製心跳。
  2. 一個 binlog 中包含兩個事務,第一個事務超過 4G。(我們在復現中為了方便,將第二個事務也做成了大事務,這一點不是必須的)。
  3. 從大事務後的位置,開始進行 binlog 複製傳輸。

我們用 tcpdump 抓個包:

用 wireshark 解開抓包,找到有問題的包(這裡怎麼找,我們分析後會有方法):

我們來分析一下包結構,這裡我們將包的內容謄寫下來,方便大家閱讀:

首先閱讀, MySQL 的客戶端網路包頭結構:

將我們的包對應上去:

其後的一位 00,是 MySQL 的 command type(),在此沒有意義,我們將其忽略,

再繼續閱讀, binlog event 的頭結構如下:

將我們的包對應上去:

接下來是個字串,明顯是一個 binlog 的名字,最後四個位元組(下圖中用黃色標註)是 checksum,

至此我們完成了一個心跳包的解析,並沒有看出嚴重的問題,不妨往前再找一個心跳包看看規律:

我將重點在圖中標註,就是 next_position 的位置,在這個包中為 0xfa000557,而其下一個包中為 0x19400583,明顯後面的 next_position 比前面的 next_position 小,這個不符合常理。

而 MySQL 的報錯 heartbeat is not compatible with local info,也是在報這個問題:心跳包中的 position 不應比當前的 position 小。

那是什麼導致了這個問題,我們注意到 next_position 的欄位長只有 4 位元組:

也就是說,該欄位最大值為 2 的 31 次方,也就是 4G,當前 binlog 的位置大於 4G 時,該欄位就會溢位。也就是說,之前我們看到的位置 0x19400583,實際丟掉了最高的一位,應當是 0x119400583。

這也就導致了 binlog event 傳輸時,next_position 突然會變小,心跳機制會檢查到這個變化,產生報錯。

那我們怎麼解決這個問題?

目前可能的方法有以下兩種:

  1. 別用大事務,別用大事務,別用大事務。資料庫系統本來就不是為大事務設計的,總會踩到不少坑。
  2. 停用心跳機制,這個問題並不是心跳機制帶來的問題,每個 binlog event 都會帶有這個包頭。只是心跳機制讓問題暴露了出來。如果關掉,提出問題的心跳機制,那麼複製對於網路故障就會不敏感,導致更大的問題。這種方式不推薦使用。

覆盤

因為文章比較長,我們對邏輯進行一下覆盤:

  1. 我們通過抓包分析,知道 binlog 傳輸的網路包裡,next_position 只有 4 個位元組,最大數值為 4G。

  2. 我們在 master 上做了一個超過 4G 的大事務,讓 slave 從這個大事務後開始傳輸。此時 master 會發送一個心跳包。

  3. 心跳包中的 next_position 是 log event 在 binlog 位置,由於這個位置大於 4G,會被截斷,導致 next_position 比實際的小。

slave 收到心跳包,進行檢測時發現 next_position 比實際的小,進行報錯。

以上只是一種容易復現問題的場景。實際使用中,master 在一段時間不傳送資料包後,或者特殊觸發條件,都會發送心跳包。

對於一主多從的環境,每條複製鏈路的心跳是單獨傳送的,也就會導致多個 slave 的表現會有所不同,有的 slave 會觸發報錯,有的 slave 由於 master 沒傳送心跳包而不會觸發報錯。


最後送上幾個小貼士:

1)我們如何快速找到有問題的包?

報錯資訊裡已經標誌了出錯的 log position 是 423626115,轉換成 16 進製為:0x19400583,找到由此資料的包即可。

2)一位一位讀包太麻煩了,怎麼辦?

好辦,先找到 server_id 的十六進位制形式,以此為基準往後推定位數就可以。

比如我們的 server_id 是 19327,很容易找到基準位置。

3)報錯裡有一段亂碼是啥?

最後這四位,是 MySQL 程式有缺陷,將包中的 checksum 作為檔名輸出了,對程式邏輯沒有影響。

0x11 是 17,對應 ASCII 碼 “device control 1 character”,鍵盤表達形式是 “ctrl + Q”,列印形式就是 “^Q”。


本文相關的 MySQL 的 bug 列表:


關於 MySQL 的技術內容,你們還有什麼想知道的嗎?趕緊****留言告訴小編吧!