深刻淺出MySQL crash safe

一 前言

MySQL 主從架構已經被普遍應用,保障主從複製關係的穩定性是你們一直關注的焦點。MySQL 5.6 針對主從複製穩定性提供了新特性: slave 支持 crash-safe。該功能能夠解決以前版本中系統異常斷電可能致使 relay_log.info 位點信息不許確的問題。
本文將從原理,參數,新的問題等幾個方面對該特性進行介紹。html

二 crash-unsafe

在瞭解 slave crash-safe 以前,咱們先分析 MySQL 5.6 以前的版本出現 slave crash-unsafe 的緣由。咱們知道在一套主從結構體系中,slave 包含兩個線程:即 IO thread 和 SQL thread。兩個線程的執行進度(偏移量)都保存在文件中。mysql

IO thread 負責從 master 拉取 binlog 文件並保存到本地的 relay-log 文件中。

SQL thread 負責執行重複 sql,執行 relay-log 記錄的日誌。算法

crash-unsafe 狀況下 SQL_thread 的 的工做模式:sql

START TRANSACTION;
 Statement 1
  ...
 Statement N
 COMMIT;

Update replication info files (master.info, relay_log.info)

IO thread 的執行狀態信息保存在 master.info 文件, SQL thread 的執行狀態信息保存在 relay-log.info 文件。slave 運行正常的狀況下,記錄位點沒有問題。可是每當系統發生 crash,存儲的偏移量多是不許確的(須要注意的是這些文件被修改後不是同步寫入磁盤的)。由於應用 binlog 和更新位點信息到文件並非原子操做,而是兩個獨立的步驟。好比 SQL thread 已經應用 relay-log.01 的4個事務架構

trx1(pos:10)
 trx2(pos:20)
 trx3(pos:30)
 trx4(pos:40)

可是 SQL thread 更新位點 (relay-log.01,30) 到 relay-log.info 文件中,slave 實例重啓的時候 sql thread 會重複執行事務 trx4,因而乎,你們就看到比較常見的複製報錯 error 1062,error 1032。app

MySQL 5.5 經過兩個參數來緩解該問題,使用 sync_master_info=1 和sync_replay_log_info=1 來保證 Slave 的兩個線程每次寫一個事務就分別向兩個文件同步一次 IO thread 和 SQL thread 當前執行的位點信息。固然同步操做不是免費的,頻繁更新磁盤文件須要消耗性能。async

可是,即便設置了 sync_master_info=1 和 sync_relay_info=1,問題仍是會出現,由於複製信息是在 transactions 提交後寫入的,若是 crash 發生在事務提交和 OS 寫文件之間,那麼 relay-log.info 就多是錯誤的。當 slave 重新啓動的時候,最後那個事務可能會被執行兩次.具體的影響取決於事務的具體操做.複製可能會繼續運行好比 update/delete,或者報錯 好比 insert 操做,此時主從數據的一致性可能會被破壞。ide

三 crash-safe 特性

3.1 保障 apply log 和更新位點信息操做的原子性

經過上面的分析,咱們知道 slave crash-unsafe 的緣由在於應用 binlog 和更新文件的非原子性。MySQL 5.6 版本經過將更新位點信息存放到表中,而且和正常的事務一塊兒執行,進而保障 apply binlog 的事務和更新 relay info 信息到 slave_relay_log_info 的原子性.post

就是把 SQL thread 執行事務和更新 mysql.slave_replay_log_info 的語句合併爲同一個事務,由 MySQL 系統來保障事務的原子性。咱們能夠經過僞代碼來模擬 crash-safe 的原理:crash-safe 狀況下 SQL_thread 的工做模式性能

START TRANSACTION;
  Statement 1
  ...
  Statement N
  Update replication info
COMMIT

一圖勝千言:

clipboard.png

綠色的表明實際業務的事務,藍色的是開啓 MySQL 執行的更新slave_replay_log_info 相關位點信息的 sql ,而後將這兩個 sql 合併在一個事務中執行,利用 MySQL 事務機制和 InnoDB 表保障原子性。不會出現應用 binlog 和更新位點信息兩個動做割裂致使不一致的問題。

3.2 crash 後的恢復動做

經過設置 relay_log_recovery = ON,slave 遇到異常 crash,而後重啓的時候,系統會刪除現有的 relay log,而後 IO thread 會從 mysql.slave_replay_log_info 記錄的位點信息從新拉取主庫的 binlog。MySQL 如此設計的出發點是:

  1. SQL thread apply binlog 的位點永遠小於等於 IO thread 從主庫拉取的位點。
  2. SQL thread 記錄的位點是已經執行而且提交的事務以後位點信息。

一圖勝千言:

clipboard.png

藍色的 update 語句表明已經執行並提交的事務,綠色的 delete 語句表示正在執行的 sql,還未提交。此時 slave_replay_log_info 表記錄的 relay log info是**update 語句結束,delete 語句開始以前的位點
(relay_log.01,100)** 。若是遇到系統 crash,slave 實例重啓以後,會刪除已經有的 relaylog,而且 IO thread 會從(relay_log.01,100)對應的 master binlog 位點從新拉取主庫的 binlog,SQL thread 也會從這個位點開始應用 binlog。

3.3 GTID 模式下的 crash safe

和基於位點的複製不一樣,GTID 模式下使用新的複製協議 COM_BINLOG_DUMP_GTID 進行復制。舉個🌰

實例 a 的事務集合 set_a, 實例 b 的事務集合 set_b ,設置 b 爲 a 的從庫的時候,其中的 binlog 協議僞算法以下:

  1. 實例 b 指向主庫實例 a, 基於主備協議創建主從關係
  2. 實例 b 將 GTID 信息發送給實例 a

    UNION(@@global.gtid_executed, Retrieved_gtid_set - last_received_GTID)
  3. 實例 a 計算出 set_b 與 set_a 的差集,也就是存在於 set_a 可是不存在與 set_b 的 GTID 集合,判斷實例 a 本地的 binlog 是否包含了該差集所須要的全部 binlog 事務。

    a 若是不包含,表示實例 a 已經把實例 b 須要的 binlog 刪除了,直接返回報錯。

    b 若是確認所有包含 實例 a 從本地 binlog 文件裏面,找到第一個不在 set_b 的事務,發送給實例 b。

  4. 從這個事務開始,日後讀文件,按順序取 binlog 發送給實例 b。

GTID 模式下,slave crash-safe 運行機制

clipboard.png

藍色 ABC:3 表示已經執行並提交的事務,綠色 ABC:4表示正在執行的事務,此時 slave crash,實例記錄的 gtid_executed=ABC:1-3,系統重啓 relay_log 被刪除。slave 將 UNION(@@global.gtid_executed, null) 發送到主庫,主庫會將 ABC:3 之後的 binlog 傳送給 slave 繼續執行。

注意

重新的複製協議中slave重啓時是基於binlog中的GTID信息進行復制的,並不依賴於mysql.slave_replay_log_info。爲了保障binlog及時落盤slave要設置 雙1模式 **sync_binlog = 1
innodb_flush_log_at_trx_commit = 1**

3.4 如何開啓 crash-safe 特性

經過配置兩個以下兩個參數開啓該特性。

relay_log_info_repository = TABLE
relay_log_recovery = ON

看到這裏是否是有疑問爲何沒有 master.info 相關的參數配置?

其實開啓 slave 的 crash-safe 以後,slave 重啓的時候會自動清空以前的 relay-log,IO thread 從 mysql.slave_relay_log_info 表中記錄的位點開始拉取數據,而不是依賴 slave_master_info 表相關數據。

注意:
若是是 MySQL 5.6.5 或者更早期。slave_master_info 和 slave_relay_log_info 表默認使用 MyISAM 引擎。因此還得修改爲 innodb,以下:

ALTER TABLE mysql.slave_master_info ENGINE=InnoDB;

ALTER TABLE mysql.slave_relay_log_info ENGINE=InnoDB;

3.5 相關參數

  1. 開啓 crash-safe 以後,slave 重啓以後,再也不依賴 master info 相關的參數,因此這兩個參數不作過多討論。不過爲了和 relay log info 存儲一致,推薦存儲 maste-info 到表裏,sync_master_info 保持默認,設置爲比較低的值,在寫壓力比較大的狀況下,會有 IO 損耗。

    master_info_repository =TABLE 
    sync_master_info=0
  2. 開啓 crash-safe 必要參數

    relay_log_info_repository = TABLE
     relay_log_recovery = 1

    這 2 個很少作介紹了,前面已經將的很是透徹。

  3. relay log 相關

    當 relay_log_info_repository=file 時
    更新位點信息的頻率依賴於sync_relay_log_info = N (N>=0):

    a 當 sync_relay_log_info=0 時,MySQL 依賴 OS 系統按期更新。

    b 當 sync_relay_log_info=N時(N>0),
    MySQL server 會在每執行 N 個事務以後調用 fdatasync() 刷 relay-log.info 文件。

    當 relay_log_info_repository=table

    若是 mysql.slave_relay_log_info 是 innodb 存儲引擎,則每次事務更新,系統會自動忽略 sync_relay_log_info 的設置。

    若是 mysql.slave_relay_log_info 是非事務存儲引擎,則

    a 當 sync_relay_log_info=0 時,不更新。

    b 當 sync_relay_log_info=N 時(N>0),
    MySQL server 會在每執行 N 個事務以後調用 fdatasync() 刷 relay-log.info 文件。

    sync_relay_log 控制着 relay-log 的刷新策略,相似 sync_binlog。不過這個參數在開啓 crash-safe 特性以後沒有什麼實質的意義。建議保持該參數爲默認值便可

四 其餘問題

每一個硬幣都有它的兩面性。開啓 crash-safe 會帶來哪些潛在的問題?

1 重啓 slave,從新拉取 relay-log,一主多從的集羣會給主庫帶來 IO 和帶寬壓力。

2 主庫不可用,或者 binlog 被刪除了,slave 找不到所須要的 binlog。

參考文章

[1] https://hackmongo.com/post/cr...

[2] http://dev.mysql.com/doc/refm...

[3] http://dev.mysql.com/doc/refm...

clipboard.png

相關文章
相關標籤/搜索