MySQL 複製 - 效能與擴充套件性的基石 4:主備切換

北國丶風光發表於2019-04-14

一旦使用 MySQL 的複製功能,就很大可能會碰到主備切換的情況。也許是為了迭代升級伺服器,或者是主庫出現問題時,將一臺備庫轉換成主庫,或者只是希望重新分配容量。不過出於什麼原因,都需要將新主庫的資訊告訴其它備庫。

對於主備切換,如果是計劃內的操作,較為容易(至少比緊急情況下容易)。只需在備庫簡單的使用 CHANGE MASTER TO 命令,並指定合適的值即可。而且大多數的值是可選的,只要指定需要改變的配置項介面。

備庫將拋棄之前的配置和中繼日誌,並從新的主庫開始複製。同時,新的引數會被更新到 master.info 檔案中,這樣就算重啟,備庫配置資訊也不會丟失。

整個過程中最難的是獲取新主庫上合適的二進位制日誌位置。這樣備庫才可以從老主庫相同的邏輯位置開始複製。

把備庫提升為主庫要較為麻煩,我們把備庫提升主庫分為計劃內切換計劃外切換兩種場景。

1 計劃內切換

備庫提升為主庫,簡單來說,有以下步驟:

  1. 停止向老主庫寫入。
  2. 讓備庫追趕上主庫(可選,可以簡化後續的步驟)。
  3. 將一臺備庫配置為新主庫。
  4. 將備庫和寫操作指向新主庫,然後開啟主庫寫入。

但上面的過程中還因此著很多細節。一些場景可能依賴於複製的拓撲結構。更深入一點,下面是大多數配置需要的步驟:

  1. 停止當前主庫上的所有寫操作。如果可以,最好能將所有的客戶端程式關閉(除了複製連線)。
  2. 通過 FLUSH TABLE WITH READ LOCK 命令在主庫上停止所有活躍的寫入。也可以在主庫上設定 read_only 選項。意味著從這一刻起,禁止向老主庫做任何寫入操作。因為一旦切換的新主庫,老主庫的寫入就意味著資料丟失。要注意的是,即使設定了 read_only 也不會阻止當前已存在的事務繼續提交。因此,可以 kill 所有開啟的事務,真正的結束所有寫入。
  3. 選擇一個備庫作為新的主庫,並確保它已經完全跟上主庫(例如,讓它執行完所有從主庫獲得的中繼日誌)。
  4. 確保新主庫和老主庫資料一致。
  5. 在新主庫上執行 STOP SLAVE。
  6. 在新主庫上執行 CHANGE MASTER TO MASTER_HOST='',然後再執行 RESET SLAVE,使其斷開與老主庫的連線,並丟棄 master.info 裡記錄的資訊(如果連線資訊記錄在 my.cnf 裡,會無法正常工作,因此我們建議不要把複製連線資訊寫到配置檔案裡)。
  7. 執行 SHOW MASTER STATUS 記錄新主庫的二進位制日誌座標。
  8. 確保其它備庫已經追趕上老主庫。
  9. 關閉老主庫。
  10. 將客戶端連線到新主庫。
  11. 在每臺備庫上執行 CHANGE MASTER TO 語句,使用之前獲得的二進位制日誌座標,指向新的主庫。

2 計劃外切換

當主庫崩潰時,需要將一臺備庫提升為主庫。這個過程就比較麻煩。如果只有一臺備庫,可以直接使用這臺備庫。但如果有超過一臺的備庫,就需要做一些額外的工作。

另外,還有潛在的丟失複製事件的問題。可能有主庫上已發生的修改還沒有更新到它任何一臺備庫上的情況。甚至可能一條語句在主庫上執行了回滾,但在備庫上沒有回滾,這樣備庫可能就超過主庫的邏輯複製位置。如果能在某一點恢復主庫的資料,也許就可以取得丟失語句,並手動執行他們。

在以下描述中,需要確保在伺服器中使用 Master_Log_File 和 Read_Master_Log_Pos 的值。

2.1 主備結構之備庫提升

  1. 確定哪臺備庫的資料最新。檢查每臺備庫上 SHOW_SLAVE_STATUS 命令的輸出,選擇其中 Master_Log_File 和 Read_Master_Log_Pos 的值最新的那個。
  2. 讓所有備庫執行完所有從老主庫崩潰前獲得的中繼日誌。
  3. 在新主庫上執行 STOP SLAVE。
  4. 在新主庫上執行 CHANGE MASTER TO MASTER_HOST='',然後再執行 RESET SLAVE,使其斷開與老主庫的連線,並丟棄 master.info 裡記錄的資訊。
  5. 執行 SHOW MASTER STATUS 記錄新主庫的二進位制日誌座標。
  6. 比較每臺備庫和新主庫上的 Master_Log_File 和 Read_Master_Log_Pos 的值。
  7. 將客戶端連線到新主庫。
  8. 在每臺備庫上執行 CHANGE MASTER TO 語句,使用之前獲得的二進位制日誌座標,指向新的主庫。

如果已經在所有備庫上開啟了 log_bin 和 log_slave_updates,就可以將所有備庫恢復到一個一致的時間點,如果沒有開啟這兩個選項,則很難做到這一點。

上面過程中比較重要的一點是確定日誌位置。接下來,我們就來看看如何卻。

3 確定日誌位置

如果有備庫和新主庫的位置不相同,則需要找到該備庫最後一條執行的事件在新主庫的二進位制日誌中對應的位置,然後再執行 CHANGE MASTER TO。可以通過 mysqlbinlog 工具來找到備庫執行的最後一條查詢,然後再主庫上找到同樣的查詢,進行簡單的計算即可得到。

為了便於描述,假設每個日誌事件都有一個自增數字 ID。新主庫在老主庫崩潰時獲得了編號為 100 的事件,另外兩條備庫:R2 和 R3。R2 已結獲取了 99 號事件,R3 獲取了 98 號事件。

如果把 R2 和 R3 都指向新主庫的同一個二進位制日誌位置,它們將從 101 號事件開始複製,從而導致資料不同步。但只要新主庫的二進位制日誌已結通過 log_slave_updates 開啟,就可以在新主庫的二進位制日誌中找到 99 號 和 100 號事件,從而將備庫恢復到一致的狀態。

由於伺服器重啟,不同的配置,日誌輪轉或者 FLUSH LOGS 命令,同一個事件在不同的伺服器上可能有不同的偏移量。我們可以通過 mysqlbinlog 從二進位制日誌或中繼日誌中解析出每臺備庫上執行的最後一個事件,並還有該命令解析新主庫上的二進位制檔案,找到相同的查詢,mysqlbinlog 會列印出該事件的偏移量,在 CHANGE MASTER TO 命令中使用這個值。

更快的方法是把新主庫和停止的備庫上的位元組偏移量相減,它顯示了位元組位置的差異。然後把這個值和新主庫當前二進位制日誌的位置相減,就可以得到期望的查詢位置。

一起來看個栗子。

假設 s1 是 s2 和 s3 的主庫。其中 s1 已經崩潰。根據 SHOW SLAVE STATUS 獲得 Master_Log_File 和 Read_Master_Log_Pos 的值,s2 已結執行完了 s1 上所有的二進位制日誌,但 s3 還沒有。如圖 1:

圖 1:s1 崩潰,s2 已追趕上,s3 落後

我們可以肯定 s2 已經執行完了主庫上的所有二進位制日誌,因為 Master_log_File 和 Read_Master_Log_Pos 的值和 s1 上最後的日誌位置相吻合。因此,我們可以將 s2 提升為新主庫,並將 s3 設定為 s2 的備庫。

應該在 s3 上為需要執行的 CHANGE MASTER TO 語句賦予什麼引數呢?這裡需要做一點計算。

s3 在偏移量 1493 處停止,比 s2 執行的最後一條語句的偏移量 1582 要小 89 位元組。

s2 正在向偏移量為 8167 的二進位制日誌寫入,因此,理論上我們應該將 s3 指向 s2 日誌的偏移量為 8167-89=8078 的位置。

最後在 s2 日誌中的 8078 位置,確定該位置上是否是正確的日誌事件。

如果驗證沒問題,可以通過下面命令將 s3 切換為 s2 的備庫:

CHANGE MASTER TO MASTER_HOST="s2 host", MASTER_LOG_FILE="mysql-bin.000009", MASTER_LOG_POS=8078;

如果伺服器在它崩潰時已經執行完成並記錄了一個事件 a。因為 s2 僅僅讀取並執行到了 1582,因此可能會失去事件 a。但是如果老主庫的磁碟沒有損壞,仍然可以通過 mysqlbinlog 或者從日誌伺服器的二進位制日誌中找到丟失的事件。

總結

  1. 備庫提升區分計劃內和計劃外場景。
  2. 備庫提升,找到新主庫準確的二進位制日誌位置是關鍵。

相關文章