1. 程式人生 > >gh-ost:不一樣的在線表結構變更

gh-ost:不一樣的在線表結構變更

配置 mysql 量變 不依賴 重新 重啟 初始 tab copy

簡介:


2016年8月份,shlomi-noach在GitHub Engineering發文宣布gh-ost開源。gh-ost是什麽?一個不依賴觸發器實現的在線表結構變更工具.

對於數據庫運維人員來說,MySQL的大表表結構變更一直都是個麻煩事,為了盡量不影響業務,業內常用的解決方案無外乎三種,一是利用Percona的pt-online-schema-change,Facebook的OSC等三方工具,二是在備庫修改通過切換實現滾動變更,三則是升級MySQL到5.6/5.7通過官方Online DDL實現部分變更。然而,引入觸發器帶來的鎖競爭問題,主備切換帶來的附加成本以及Online DDL的局限性都不讓DBA省心。

gh-ost的設計號稱無觸發器,可監控,可動態調整暫停等,更重要的是切換方案的優秀設計。下面就介紹下其實現原理和cut-over(新舊表切換)的詳細過程。

原理:


gh-ost不依賴於觸發器,是因為他是通過模擬從庫,在row binlog中獲取增量變更,再異步應用到ghost表的。

官方架構圖如下:

技術分享圖片

<該圖摘自gh-ost>

圖中描述了兩種功能模式:

1.連接主庫直接修改

  • 直連主庫
  • 主庫上創建ghost表
  • 新表(ghost表)上直接alter修改表結構
  • 遷移原表數據到新表
  • 拉取解析binlog事件,應用到新表
  • cut-over階段,用新表替換掉原表

    2.連接從庫間接應用到主庫

  • 連接從庫
  • 校驗完後,在主庫創建新表
  • 遷移原表數據到新表
  • 模擬從庫的從庫,拉取解析增量binlog應用到主庫
  • cut-over階段,用新表替換掉原表

兩者不同的點就在於,通過連接從庫來進行變更,對主庫的性能影響最小

變更流程:


以直連主庫修改為例,詳細介紹gh-ost做了哪些操作:

1.模式:

  • 根據參數配置可選三種變更模式
  • 除了直連主庫和連接從庫以外,還有連接從庫做變更測試

2.校驗:

  • 測試db是否可連通,並且驗證database是否存在
  • 確認連接實例是否正確
  • 權限驗證 show / gh-ost / grants for current_user()
  • binlog驗證,包括row格式驗證和修改binlog格式後的重啟replicate
  • 原表存儲引擎,外鍵,觸發器檢查,行數預估等

3.初始化:

  • 初始化stream的連接,添加binlog的監聽
  • 初始化applier連接,創建ghosttable和changelogtable
  • 判斷是否符合遷移條件,寫入結果到tablesInPlace channel

    4.遷移:

技術分享圖片

遷移過程中,row copy和binlog apply是同時進行,其中原則是binlog apply的優先級一定大於row copy操作的優先級。

5.狀態展示:

Copy: 9451000/10000060 94.5%; Applied: 31; Backlog: 0/100; Time: 8m26s(total), 8m26s(copy); streamer: mysql-bin.000040:68321839; ETA: 29s

6.cut-over:

  • 嘗試lock原表
  • 成功後,進行rename原子性操作,被block住
  • unlock原表,rename完成切換
  • 後續中間表清理工作

遷移和切換的細節實現:


關於gh-ost的實現,這裏只挑了rowcopy和binlog apply的順序問題和rename過程做了詳細解析。

數據遷移過程

在數據遷移的過程中,數據變量有三個,暫且分為,A:來自原表的rowcopy,B:binlog的apply,C:對原表的dml操作。

C操作會記錄binglog從而觸發B操作,所以B操作一定在C操作的後面,因此一般情況下,會有ACB,CBA兩種組合,同時特殊情況如binlog apply延遲,則會有CAB這種組合。

分析三種組合之前要先了解gh-ost在sql改寫方面是如何映射的:

RowCopy原表操作新表操作
select insert ignore into
BinlogApply原表操作新表操作
insert replace into
update update 新表(全行更新)
delete delete

在上述原則的基礎上,我們再來逐個分析不同順序組合的影響:

1.insert 操作

binlog是最權威的,gh-ost的原則是以binlog優先,所以無論任何順序下,數據都是和binlog保持一致,如果rowcopy在後,會insert ignore,如果binlog apply在後會replace into掉。

2.update/delete 操作

一般情況下:

技術分享圖片

ACB組合,即對已經rowcopy過的數據,出現對原表的update/delete操作。這時候會全部通過binlog apply執行,註意binlog apply的update是對某一條記錄的全部列覆蓋更新,所以不會有累加的問題。

技術分享圖片

CBA組合,即對尚未遷移的數據,出現對原表的update/delete操作。這時候對新表的binlog apply會是空操作,具體數據由rowcopy遷移。

特殊情況下:

CAB組合,即先對原表更新完以後,rowcopy在binlog apply之前把數據遷移了過去,而在binlog event過來以後,會再次應用,這裏有問題?其實結合gh-ost的binlog aplly的sql映射規則,insert操作會被replace重新替換掉,update 會更新對應記錄全部行,delete 會是空操作。最終數據還是一致的狀態。

cut-over過程:

在pt-osc或者online ddl中,最後的rename操作一般是耗時比較短,但如果表結構變更過程中,有大查詢進來,那麽在rename操作的時候,會觸發MDL鎖的等待,如果在高峰期,這就是個嚴重的問題。所以gh-ost是怎麽做的呢?

gh-ost利用了MySQL的一個特性,就是原子性的rename請求,在所有被blocked的請求中,優先級永遠是最高的。gh-ost基於此設計了該方案:一個連接對原表加鎖,另啟一個連接嘗試rename操作,此時會被阻塞住,當釋放lock的時候,rename會首先被執行,其他被阻塞的請求會繼續應用到新表。

migrator.go:iterateChunks() 函數來確定何時開始cut-over

具體切換流程如下:

START

  1. 會話A

    1. CREATE table tbl_old

    防止rename過早執行

    1. LOCK TABLES tbl WRITE, tbl_old WRITE

    通過lock_wait_timeout設置為2s控制超時,超時失敗會重試次數為配置default-retries,默認60次

  2. 新的請求進來,關於原表的請求被blocked
  3. RENAME TABLE tbl TO tbl_old, ghost TO tbl , 同樣被blocked
  4. 新的請求進來,關於原表的請求被blocked
  5. 檢查是否有blocked 的RENAME請求,通過show processlist
  6. 會話A: DROP TABLE tbl_old
  7. 會話A: UNLOCK TABLES
  8. RENAME SUCCESS

END

不同階段失敗後如何處理:

  • 如果第一步失敗,退出程序
  • 如果會話A建表成功,加鎖失敗,退出程序,未加鎖
  • rename請求來的時候,會話A死掉,lock會自動釋放,同時因為tbl_old的存在rename也會失敗,所有請求恢復正常
  • rename被blocked的時候,會話A死掉,lock會自動釋放,同樣因為tbl_old的存在,rename會失敗,所有請求恢復正常
  • rename死掉,gh-ost會捕獲不到rename,會話A繼續運行,釋放lock,所有請求恢復正常

gh-ost:不一樣的在線表結構變更