作為MySQL DBA,相信我們大家都會對大表變更(大於10G 以上的)比較頭疼,尤其是某些DDL會鎖表,影響業務可持續性。目前通用的方案使用Percona 公司開源的pt-osc 工具解決導致鎖表的操作,還有一款github基於go語言開發的gh-ost。本文主要介紹gh-ost使用方法,其工作原理放到下一篇文章介紹。
二 使用2.1 gh-ost介紹gh-ost 作為一個偽裝的備庫,可以從主庫/備庫上拉取 binlog,過濾之後重新應用到主庫上去,相當於主庫上的增量操作通過 binlog 又應用回主庫本身,不過是應用在幽靈表上。
其大致的工作過程:
1 gh-ost 首先連接到主庫上,根據 alter 語句創建幽靈表,
2 然後作為一個備庫連接到其中一個真正的備庫或者主庫上(根據具體的參數來定),一邊在主庫上拷貝已有的數據到幽靈表,一邊從備庫上拉取增量數據的 binlog,然後不斷的把 binlog 應用回主庫。
3 等待全部數據同步完成,進行cut-over 幽靈表和原表切換。圖中 cut-over 是最後一步,鎖住主庫的源表,等待 binlog 應用完畢,然後替換 gh-ost 表為源表。gh-ost 在執行中,會在原本的 binlog event 裡面增加以下 hint 和心跳包,用來控制整個流程的進度,檢測狀態等。
當然gh-ost 也會做很多前置的校驗檢查,比如binlog_format ,表的主鍵和唯一鍵,是否有外鍵等等
這種架構帶來諸多好處,例如:
整個流程異步執行,對於源表的增量數據操作沒有額外的開銷,高峰期變更業務對性能影響小。
降低寫壓力,觸發器操作都在一個事務內,gh-ost 應用 binlog 是另外一個連接在做。
可停止,binlog 有位點記錄,如果變更過程發現主庫性能受影響,可以立刻停止拉binlog,停止應用 binlog,穩定之後繼續應用。
可測試,gh-ost 提供了測試功能,可以連接到一個備庫上直接做 Online DDL,在備庫上觀察變更結果是否正確,再對主庫操作,心裡更有底。不過不推薦在備庫直接操作。
2.2 gh-ost 操作模式a. 連接到從庫,在主庫做遷移這是 gh-ost 默認的工作方式。gh-ost 將會檢查從庫狀態,找到集群結構中的主庫並連接,接下來進行遷移操作:
行數據在主庫上讀寫
讀取從庫的二進位日誌,將變更應用到主庫
在從庫收集表格式,欄位&索引,行數等信息
在從庫上讀取內部的變更事件(如心跳事件)
在主庫切換表
如果你的主庫的日誌格式是 SBR,工具也可以正常工作。但從庫必須啟用二級制日誌( log_bin, log_slave_updates) 並且設置 binlog_format=ROW 。
b. 連接到主庫
直接連接到主庫構造slave,在主庫上進行copy數據和應用binlog,通過指定 --allow-on-master 參數即可。當然主庫的binlog模式必須是row模式。
c. 在從庫遷移/測試該模式會在從庫執行遷移操作。gh-ost 會簡單的連接到主庫,此後所有的操作都在從庫執行,不會對主庫進行任何的改動。整個操作過程中,gh-ost 將控制速度保證從庫可以及時的進行數據同步
--migrate-on-replica 表示 gh-ost 會直接在從庫上進行遷移操作。即使在複製運行階段也可以進行表的切換操作。
--test-on-replica 表示 遷移操作只是為了測試在切換之前複製會停止,然後會進行切換操作,然後在切換回來,你的原始表最終還是原始表。兩個表都會保存下來,複製操作是停止的。你可以對這兩個表進行一致性檢查等測試操作。
三 實踐https://github.com/github/gh-ost
3.1 參數說明:這裡列出比較重要的參數,大家可以通過如下命令獲取比較詳細的參數以及其解釋。
gh-ost --help
-allow-master-master:是否允許gh-ost運行在雙主複製架構中,一般與-assume-master-host參數一起使用
-allow-nullable-unique-key:允許gh-ost在數據遷移依賴的唯一鍵可以為NULL,默認為不允許為NULL的唯一鍵。如果數據遷移(migrate)依賴的唯一鍵允許NULL值,則可能造成數據不正確,請謹慎使用。
-allow-on-master:允許gh-ost直接運行在主庫上。默認gh-ost連接的從庫。
-alter string:DDL語句
-assume-master-host string:為gh-ost指定一個主庫,格式為」ip:port」或者」hostname:port」。在這主主架構裡比較有用,或則在gh-ost發現不到主的時候有用。
-assume-rbr:確認gh-ost連接的資料庫實例的binlog_format=ROW的情況下,可以指定-assume-rbr,這樣可以禁止從庫上運行stop slave,start slave,執行gh-ost用戶也不需要SUPER權限。
-chunk-size int:在每次迭代中處理的行數量(允許範圍:100-100000),默認值為1000。
-concurrent-rowcount:該參數如果為True(默認值),則進行row-copy之後,估算統計行數(使用explain select count(*)方式),並調整ETA時間,否則,gh-ost首先預估統計行數,然後開始row-copy。
-conf string:gh-ost的配置文件路徑。
-critical-load string:一系列逗號分隔的status-name=values組成,當MySQL中status超過對應的values,gh-ost將會退出。-critical-load Threads_connected=20,Connections=1500,指的是當MySQL中的狀態值Threads_connected>20,Connections>1500的時候,gh-ost將會由於該資料庫嚴重負載而停止並退出。
-critical-load-hibernate-seconds int :負載達到critical-load時,gh-ost在指定的時間內進入休眠狀態。 它不會讀/寫任何來自任何伺服器的任何內容。
-critical-load-interval-millis int:當值為0時,當達到-critical-load,gh-ost立即退出。當值不為0時,當達到-critical-load,gh-ost會在-critical-load-interval-millis秒數後,再次進行檢查,再次檢查依舊達到-critical-load,gh-ost將會退出。
-cut-over string:選擇cut-over類型:atomic/two-step,atomic(默認)類型的cut-over是github的算法,two-step採用的是facebook-OSC的算法。
-cut-over-exponential-backoff
-cut-over-lock-timeout-seconds int:gh-ost在cut-over階段最大的鎖等待時間,當鎖超時時,gh-ost的cut-over將重試。(默認值:3)
-database string:資料庫名稱。
-default-retries int:各種操作在panick前重試次數。(默認為60)
-dml-batch-size int:在單個事務中應用DML事件的批量大小(範圍1-100)(默認值為10)
-exact-rowcount:準確統計表行數(使用select count(*)的方式),得到更準確的預估時間。
-execute:實際執行alter&migrate表,默認為noop,不執行,僅僅做測試並退出,如果想要ALTER TABLE語句真正落實到資料庫中去,需要明確指定-execute
-exponential-backoff-max-interval int
-force-named-cut-over:如果為true,則'unpostpone | cut-over'交互式命令必須命名遷移的表
-heartbeat-interval-millis int:gh-ost心跳頻率值,默認為500
-initially-drop-ghost-table:gh-ost操作之前,檢查並刪除已經存在的ghost表。該參數不建議使用,請手動處理原來存在的ghost表。默認不啟用該參數,gh-ost直接退出操作。
-initially-drop-old-table:gh-ost操作之前,檢查並刪除已經存在的舊錶。該參數不建議使用,請手動處理原來存在的ghost表。默認不啟用該參數,gh-ost直接退出操作。
-initially-drop-socket-file:gh-ost強制刪除已經存在的socket文件。該參數不建議使用,可能會刪除一個正在運行的gh-ost程序,導致DDL失敗。
-max-lag-millis int:主從複製最大延遲時間,當主從複製延遲時間超過該值後,gh-ost將採取節流(throttle)措施,默認值:1500s。
-max-load string:逗號分隔狀態名稱=閾值,如:'Threads_running=100,Threads_connected=500'. When status exceeds threshold, app throttles writes
-migrate-on-replica:gh-ost的數據遷移(migrate)運行在從庫上,而不是主庫上。
-nice-ratio float:每次chunk時間段的休眠時間,範圍[0.0…100.0]。0:每個chunk時間段不休眠,即一個chunk接著一個chunk執行;1:每row-copy 1毫秒,則另外休眠1毫秒;0.7:每row-copy 10毫秒,則另外休眠7毫秒。
-ok-to-drop-table:gh-ost操作結束後,刪除舊錶,默認狀態是不刪除舊錶,會存在_tablename_del表。
-panic-flag-file string:當這個文件被創建,gh-ost將會立即退出。
-password string :MySQL密碼
-port int :MySQL埠,最好用從庫
-postpone-cut-over-flag-file string:當這個文件存在的時候,gh-ost的cut-over階段將會被推遲,數據仍然在複製,直到該文件被刪除。
-skip-foreign-key-checks:確定你的表上沒有外鍵時,設置為'true',並且希望跳過gh-ost驗證的時間-skip-renamed-columns ALTER
-switch-to-rbr:讓gh-ost自動將從庫的binlog_format轉換為ROW格式。
-table string:表名
-throttle-additional-flag-file string:當該文件被創建後,gh-ost操作立即停止。該參數可以用在多個gh-ost同時操作的時候,創建一個文件,讓所有的gh-ost操作停止,或者刪除這個文件,讓所有的gh-ost操作恢復。
-throttle-control-replicas string:列出所有需要被檢查主從複製延遲的從庫。
-throttle-flag-file string:當該文件被創建後,gh-ost操作立即停止。該參數適合控制單個gh-ost操作。-throttle-additional-flag-file string適合控制多個gh-ost操作。
-throttle-query string:節流查詢。每秒鐘執行一次。當返回值=0時不需要節流,當返回值>0時,需要執行節流操作。該查詢會在數據遷移(migrated)伺服器上操作,所以請確保該查詢是輕量級的。
-timestamp-old-table:在舊錶名中使用時間戳。這會使舊錶名稱具有唯一且無衝突的交叉遷移。
-user string :MYSQL用戶
3.2 執行ddl測試例子 對test.t1 重建表 alter table t1 engine=innodb;
/opt/gh-ost/bin/gh-ost \
--max-load=Threads_running=20 \
--critical-load=Threads_running=50 \
--critical-load-interval-millis=5000 \
--chunk-size=1000 \
--user="root" \
--password="" \
--host='127.0.0.1' \
--port=3316 \
--database="test" \
--table="b" \
--verbose \
--alter="engine=innodb" \
--assume-rbr \
--cut-over=default \
--cut-over-lock-timeout-seconds=1 \
--dml-batch-size=10 \
--allow-on-master \
--concurrent-rowcount \
--default-retries=10 \
--heartbeat-interval-millis=2000 \
--panic-flag-file=/tmp/ghost.panic.flag \
--postpone-cut-over-flag-file=/tmp/ghost.postpone.flag \
--timestamp-old-table \
--execute 2>&1 | tee /tmp/rebuild_t1.log
操作過程中會生成兩個中間狀態的表 _b_ghc, _b_gho,其中 _b_ghc 是記錄gh-ost 執行過程的表,其記錄類似如下:
_b_gho 是目標表,也即應用ddl語句的幽靈表。
特別說明,上面的命令其實是在我們的生產線上直接使用的。一般我們針對幾百G的大表做歸檔刪除數據之後要重建表,以便減少表空間大小。重建完,進行cut-over 切換幽靈表和原表時,默認不刪除幽靈表。因為直接刪除上百G 會對磁碟IO有一定的影響.
其他的請各位同行根據自己的情況去調整合適的參數,注意以下兩個參數。
--ok-to-drop-table:gh-ost操作結束後,刪除舊錶,默認狀態是不刪除舊錶,會存在_tablename_del表。
--timestamp-old-table 最終rename的時候表名會加上時間戳後綴,每次執行的時候都會生成一個新的表名。
3.3 gh-ost 的特性gh-ost 擁有眾多特性,比如 輕量級,可暫停,可動態控制,可審計,可測試,等等 ,我們可以通過操作特定的文件對正在執行的gh-ost命令進行動態調整 。
暫停/恢復
我們可以通過創建/刪除 throttle-additional-flag-file 指定的文件 /tmp/gh-ost.throttle 控制gh-ost對binlog 應用.
限流
gh-ost 可以通過 unix socket 文件或者TCP埠(可配置)的方式來監聽請求,DBA可以在命令運行後更改相應的參數,參考下面的例子:
打開限流
echo throttle | socat - /tmp/gh-ost.test.b.sock
_b_ghc 中會多一條記錄
331 | 2019-08-31 23:23:00 | throttle at 1567264980930907070 | done throttling
關閉限流
no-throttle | socat - /tmp/gh-ost.test.b.sock
_b_ghc 中會多一條記錄
347 | 2019-08-31 23:24:09 | throttle at 1567265049830789079 | commanded by user
改變執行參數: chunk-size= 1024, max-lag-millis=100, max-load=Thread_running=23 這些參數都可以在運行時動態調整。
echo chunk-size=1024 | socat - /tmp/gh-ost.test.b.sock
echo max-lag-millis=100 | socat - /tmp/gh-ost.test.b.sock
echo max-load=Thread_running=23 | socat - /tmp/gh-ost.test.b.sock
終止運行
我們通過來過創建panic-flag-file指定的文件,立即終止正在執行的gh-ostmin
創建文件/tmp/ghost.panic.flag
gh-ost log提示
2019-08-31 22:50:52.701 FATAL Found panic-file /tmp/ghost.panic.flag. Aborting without cleanup
注意停止gh-ost操作會有遺留表 xxx_ghc, xxx_gho 還有socket文件,管理cut-over的文件,如果你需要執行兩次請務必檢查指定目錄是否存在這些文件,並且清理掉文件和表
四 與pt-osc的對比從功能,穩定性和性能上來看,兩種工具各有千秋,雖然在高並發寫的情況下,gh-ost 應用binlog會出現性能較低不如pt-osc的情況。不過gh-ost更靈活,支持我們根據實際情況動態調整。
推薦兩個blog的文章吧,大家可以根據自己的實際場景去選擇使用哪個工具。
https://www.cnblogs.com/zping/p/8876148.html
https://blog.csdn.net/poxiaonie/article/details/75331916
五 總結總體來講 gh-ost 是一款非常出色的開源產品,感謝github為我們MySQL DBA 提供了一種解決大表ddl的工具,歡迎還沒使用的朋友試用該工具。
參考文章
https://www.cnblogs.com/zhoujinyi/p/9187421.html
https://segmentfault.com/a/1190000006158503
-The End-
本公眾號長期關注於資料庫技術以及性能優化,故障案例分析,資料庫運維技術知識分享,個人成長和自我管理等主題,歡迎掃碼關注。