攜程數據庫發佈系統演進之路

一、前言

互聯網軟件本身具有快速迭代、持續交付等特點,加上數據庫的表結構(DDL)發佈無法做到灰度發佈,且回退困難、試錯成本高,一個穩定可靠的數據庫發佈系統對於互聯網公司顯得尤其重要。本文將介紹攜程MySQL數據庫發佈系統從無到有,版本不斷迭代的演進之路,希望對讀者有所參考和幫助。

我們先後設計了三個版本,最新的版本具有以下功能和特點:

  • 發佈期間只有一次表鎖,鎖定時間極短,鎖定時間不受表容量影響;
  • Master-Slave複製延遲可控,這點對有讀寫分離架構且數據實時性要求高的業務尤其重要;
  • 自動避開業務高峯,自動識別熱表,確保發佈期間業務基本無影響;
  • 將數據庫規範加入發佈前校驗,對不符合規範的發佈進行攔截;

介紹整個系統之前,首先對攜程數據庫環境和發佈流程做一個簡單的介紹。系統的數據庫環境主要分成Dev、測試環境(含三個子環境,功能性測試(FAT)/壓力測試(LPT)/UAT 三個環境)、Product:

1)數據庫表設計在Dev環境完成,期間包含數據庫規範檢測

2)然後發佈到其它測試環境(FAT→LPT→UAT)

3)測試環境都驗證通過後,最後發佈到生產環境

表發佈流程圖

二、初期(1.0時代)

攜程成立以來一直使用SQL Server 數據庫,2014年左右開始使用MySQL數據庫,爲後面轉型MySQL做準備。這時期接入MySQL的業務量很小,數據量不大,都是非核心業務,所以整個發佈過程可以概括爲“簡單粗暴”:

1)開發人員通過直連DEV環境數據庫,直接對數據庫表進行修改

2)DBA通過自動化工具捕捉到表的變化,將變更同步到測試環境

3)開發測試完後,將變化同步到生產環境

這個階段只是簡單把表的變更傳遞到其他環境,對發佈期間業務和性能方面的影響沒有考慮太多。

1.0 版本發佈流程

三、轉型期(2.0時代)

隨着業務接入MySQL不斷增加,MySQL數據庫越來越多,到2016年下半年爲止,MySQL 數據庫數量已經有800+,很多核心業務也轉到MySQL,包含很多讀寫分離架構。此時原生的DDL發佈已經無法滿足業務需求,這時引入了業界流行的pt-online-schema-change(pt-osc)。

2.0版本發佈流程

pt-osc是percona開發的一款比較成熟的產品,業界使用也較多。其採用觸發器的方式將所有的增量DML應用到了影子表,這種實現方式會加大對語句的開銷,併發過高時甚至會影響數據庫正常提供服務,因此往往會出現發佈一半最後還是不得不終止發佈的現象,線上遇到核心的表或者大表往往需要晚上留守來進行發佈,這極大的提高了DBA的運維負擔。

四、引入gh-ost(3.0時代)

爲了進一步提升發佈穩定性,我們在2017年調研了當時剛開源不久的gh-ost,由於產品非常新,因此做了大量的調研和測試工作,也發現提交了多個高優先級Bug(包括GBK字符集支持、bad connection以及column case-sensitive issue導致數據丟失等),都已得到作者的修復。

那麼gh-ost對比pt-osc具體有哪些優勢呢?下面先簡單介紹下它的兩個最核心的特性。

4.1 Triggerless

在gh-ost出現之前第三方MySQL DDL工具均採用觸發器的方式進行實現,包括前面percona的pt-osc,Facebook的OSC等等。而gh-ost採用的機制和他們完全不同:它通過MySQL binlog來同步數據,gh-ost本身註冊爲一個fake slave,可以從集羣中的master或者slave上拉取binlog,並實時解析,將變更表的所有DML操作都重新apply到影子表上面。因此對於發佈期間變更表上發生的DML操作,可以完全避免由於觸發器而產生的性能開銷,以及鎖的爭搶。

除此之外,一般我們選擇目標發佈機器通常會選擇集羣中slave節點,而slave一般不會承載業務,這樣binlog解析的開銷也不會落在提供業務的master上面,而僅僅是一次異步的DML語句重放。

4.2 Dynamically controllable

另一個最重要的特性是動態調控,這是此前其他第三方開源工具所不具備的。

之前通過pt-osc發佈時,命令執行後參數就沒法修改,除非停止重來。假設發佈進行到90%,突然由於其他各種原因導致服務器負載上升,爲不影響業務,只能選擇將發佈停掉,等性能恢復再重來。

通過pt-osc發佈的表都是很大的表,耗時較長,所以遇到這類場景很尷尬。因此發佈中參數如果可動態調控將變得非常重要。gh-ost另外實現了一個socket server,我們可以在發佈過程中,通過socket和發佈進程進行實時交互,它可以支持實時的暫停,恢復,以及很多參數的動態調整,來適應外界變化。

4.3 gh-ost如何工作?

在瞭解完其重要特性後,簡單介紹下其實現原理。

其原理很好理解,首先建兩張表,一張_gho的影子表,gh-ost會將原表數據以及增量數據都應用到這個表,最後會將這個表和原表做次表名切換,另一張是_ghc表,這個表是存放changelog的數據,包括信號標記,心跳等。

其次,gh-ost會開兩個goroutine,一個用於拷貝原表數據,一個用於apply增量的binlog到_gho表,並且兩個goroutine的並行在跑的,也就是不用關心數據是先拷貝過去還是先apply binlog過去。

因爲這裏會對insert語句做調整,首先我們拷貝的insert into會改寫成insert ignore into,而binlog內insert into會改寫成replace into,這樣可以很好的支持兩個goroutine的並行。但這樣的調整能適用所有的DDL嗎?答案是否定的,大家可以思考下,下面案例部分會給出詳細解釋。

最後,當原表數據全部拷貝完成後,gh-ost會進入到表交換階段,採用更加安全的原子交換。

Gh-ost 架構圖

五、如何做到安全發佈?

爲了確保每次發佈符合數據庫規範,確保發佈可以順利完成,發佈前我們做了很多檢查工作,發佈過程中會有線程實時偵聽發佈狀態。通過producer,consumer,listener如下三個組件來協同完成發佈的順利進行。

任務運行架構圖

5.1 運行前——是否能做發佈?

我們消費線程(consumer)會在發佈前做滿足發佈的前置校驗,選擇合適的目標主機進行發佈。

1)MySQL環境變量的校驗:檢查當前實例變量配置是否滿足發佈要求。

2)衝突表校驗:檢查集羣中是否存在已發佈相沖突的表,存在的話自動進行清理。

3)衝突標記文件校驗:檢查發佈機器上是否存在衝突的標記文件,存在的話自動進行清理。

4)磁盤容量校驗:預估集羣所有節點的磁盤空間是否足夠

5)任務並行校驗:檢查集羣是否存在其他發佈,多實例會檢查所有實例所屬集羣是否存在發佈,爲避免並行發佈導致的性能影響,以及磁盤容量難以預估,我們會限制單個集羣只能有串行發佈。

6)DRC成員狀態校驗:對於已接入DRC的DB,會在發佈前先初始化所有成員狀態,並隨機選擇一個成員成爲leader,僅當所有成員所屬集羣均已滿足前置校驗,纔會進入真正發佈階段。

注:DRC(Data Replicate Center),想了解更多DRC相關的技術戳這裏。這裏主要負責支持多數據中心同時發起以及結束髮布流程。

5.2 運行時——進展是否正常?

整個發佈過程採用的是生產消費模型,當每個消費線程運行任務時,同時會生成一個其對應的監聽線程(listener),用於監聽該任務的運行狀態。

1)磁盤容量監聽:當低於某閾值時將終止發佈,並會清理髮布產生的殘留表來釋放空間。

2)服務器性能監聽:當服務器負載過高,將會自動觸發throttle,等性能恢復再重新解除throttle。

3)副本延遲監聽:延遲閾值默認初始1.5s,後續在一個閾值上限內會動態增減,避免延遲一直波動時影響發佈效率,但最終交換前會回置到默認1.5s。

4)時間監聽:當前時間若處於業務高峯期,會通過自動加大nice-ratio的值來進行“限流”,等業務低峯期後再做置回。

5)DRC成員狀態監聽:對於接入DRC的DB,會偵聽partner的運行狀態,等所有成員均已進入postponing狀態後,再由drc選舉出來的leader統一觸發表名交換。

6)集羣拓撲監聽:線上我們往往會碰到正在發佈的DB進行了變更維護,包括主從切換,DB拆分到其他集羣上等等。這時我們發現gh-ost會hang在那,也不會報錯,往往會等到提交發布的人員反饋纔會發現,因此我們這裏加了對集羣拓撲的監聽,來及時發現拓撲的變更並終止發佈。

六、碰到了哪些問題,如何解決?

目前gh-ost接入發佈系統已接近兩年,運行非常穩定。但慢慢的我們會發現原生gh-ost沒辦法滿足我們所有需求,所以做了一些二次開發。

下面通過幾個典型案例來簡要介紹下。

案例1、發佈後自增列值保留

默認gh-ost 發佈時新表並沒有保留原表自增值,部分業務是依賴自增列的值,這種場景會出現較大的問題。

要解決這個問題其實不難,只需要在建_gho表後設置一把AUTO_INCREMENT值即可。我們添加了一個- reset-original-auto-increment 參數開關,默認false,即保留原始自增值。

代碼示例如下,先查找原表的有效自增值,並應用給新的_gho表即可。

案例2、含唯一鍵表發佈

我們知道唯一鍵發佈有兩大前提,首先,表中已有的存量數據必須滿足新增的唯一鍵約束;其次,發佈過程中出現的DML增量數據也需保證滿足新增的唯一鍵約束。

默認gh-ost對錶添加唯一鍵是無法保證數據的完整性的。爲什麼呢?前面我們簡單提過gh-ost發佈會做語句轉換,並且rowCopy 中insert into 會轉爲 insert ignore into,而binlogApply中insert into會轉爲replace into。當表結構變更中包含新增唯一鍵的話,這種轉換就顯然不夠了,它會將衝突數據全部自然抹掉,而這顯然是不合理的,是很嚴重的data integrity問題。

工具的預期應該是出現數據衝突即退出,說明這個發佈並沒有發佈條件。而官方並沒有做唯一索引發布的特殊支持,那我們是否可以實現這一部分邏輯?問題的關鍵在於我們要對原主鍵繼續支持insert ignore into/ replace into的邏輯保證數據一致且不失敗,另外新增唯一鍵部分又不能通過這種邏輯處理,保證衝突數據要及時發現。

後面通過分析我們想了一種方案,首先通過如下一條正則解析命令是否包含新增唯一鍵。

其次對寫入邏輯進行如下改寫:

1)原數據拷貝(rowCopy)從insert ignore into 調整爲 insert into … andnot exists PK的方式,如下示例。

2)DML增量應用(binlogApply)從 replace into 調整爲 delete from + insert into的方式,如下示例。

下面對原數據拷貝(A),原表DML(B),Binlog應用到新表© 三個過程先後順序不固定時做下推演。首先C肯定在B後面,因此可能的順序是ABC,BCA,BAC 三種可能情況。

原表b, 2個列,col1 PK,col2 計劃新增Uniquekey,原表數據是(1,a), (3,c)。

ABC:先完成拷貝,再對原表DML,最後應用binlog

BCA:先原表DML,再應用binlog,最後拷貝

BAC:先原表DML,再拷貝,最後應用binlog

經過過程推演,我們發現這個方案可以解決新增唯一鍵時可能存在的問題。

案例3、活學活用,大表發佈+數據清理

我們經常會碰到一些大表的發佈,發佈系統一般會對超大表做攔截,建議清理些無效數據。那這裏分爲兩個過程,即先清理無效數據,再進行發佈。那我們是否可以將這兩個過程合併發佈呢?答案是可行的,而且可以極大的提升發佈效率。

邏輯可以很容易理解,見下圖,即拷貝你所需要的數據,而增量部分不做變化。我們可以加個參數-where-reserve-clause,代表你需要的數據。那這裏有一個問題,拷貝範圍是先去根據-where-reserve-clause去限定,還是實際insert的時候去限定?有何區別?

發佈+清理邏輯圖

區別在於如果根據-where-reserve-clause去限定範圍的性能很差,往往查主鍵範圍需要花很久,如果主鍵範圍又很分散,那選擇先查這個範圍是比較差的。而如果實際insert的時候去限定實際需寫入的數據的話,則只是在每個chunk 寫入時附加上這個條件,可能一個chunk沒有一條數據符合條件,那即產生一次空跑,也沒有任何影響。

但如果用戶明確知道要保留的主鍵範圍,那先去限定範圍可以避免大量的空跑。因此添加了-force-query-migration-range-values-on-master來確定使用哪種方式,而具體選擇需具體案例具體分析。

除此之外,我們知道數據清理表空間並不會自動瘦身,往往需要配合optimize table來進行表收縮。而添加的這個功能本身既支持數據清理,又支持表結構變更,而支持了表結構變更也就支持了表收縮。因此對-alter做了下擴展,允許noop,來支持不變更結構僅數據清理或者表收縮等場景。

下面有個線上數據清理的測試數據對比(表大小在300GB左右,需清理80%左右的數據):

表大小 總行數/保留行數 處理方式 耗時 備註
290GB 68666w/17327w 數據清理工具 25h 後續還需optimize
320GB 75128w/19542w gh-ost 2h30m 後續清理老表即可

通過對比,我們可以看到效率提升了10倍以上,其中還不算optimize的開銷。

七、結語

以上是攜程數據庫發佈系統的整個演進過程,希望對讀者有所參考和幫助,新的3.0MySQL數據庫發佈系統從2018年開始研發上線並持續改進,功能上已經較爲完善,適應了業務快速迭代的要求,規避了發佈可能造成的業務故障,覆蓋了攜程絕大多數類型的DDL。

面向未來,我們的發佈系統會持續改進:更加友好的交互、更加智能的throttle,我們已經在路上。

作者介紹

天浩,攜程數據庫專家,專注數據庫自動化運維研發工作。

曉軍,攜程數據庫專家,主要負責運維及分佈式數據庫研究。

本文轉載自公衆號攜程技術(ID:ctriptech)。

原文鏈接

攜程數據庫發佈系統演進之路

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章