騰訊計費(fèi)平台部爲了解決基于内存的NoSQL解決方案HOLD平台在應對多種業務接入時的不足,結合團隊在MySQL領域多年應用和優化經驗,最終在MySQL存儲引擎基礎上,打造一(yī)套分(fēn)布式SQL系統TDSQL。本文是對該系統架構分(fēn)析。
騰訊計費(fèi)平台部托管着公司90%以上的虛拟賬戶,如QB、Q點、包月服務、遊戲的二級賬戶等,爲了保證能順暢支撐公司各大(dà)業務的實時在線交易,并且在各種災難場景下(xià)數據是一(yī)緻并且可用的,對系統的可用性、一(yī)緻性切換要求非常高,因此計費(fèi)團隊曆來都非常重視高一(yī)緻性存儲系統的建設。
到目前爲止,計費(fèi)高一(yī)緻性存儲層的解決方案大(dà)緻經過了3個階段,本文将分(fēn)享最新的基于MySQL的分(fēn)布式解決方案。
随着業務的發展,基于内存的NoSQL解決方案HOLD平台在高峰期一(yī)天支撐3000億讀寫,證明了分(fēn)布式Cache的巨大(dà)價值;但随着各種業務的接入,NoSQL方案的不足也逐步顯現出來了,如下(xià)所示。
1.适用的業務場景比較有限,僅提供get/set操作,有不少業務場景希望能通過記錄中(zhōng)的其他字段做索引來查詢,比如流水類業務。
2.不是所有的數據都是熱點,一(yī)台64GB内存機器提供的有效内存空間大(dà)概在50GB左右,而采用Fusion卡的機型容量一(yī)般在1TB以上,對比起來,如果所有數據放(fàng)入分(fēn)布式Cache明顯是一(yī)種極大(dà)的浪費(fèi),最合理的當然是熱點在HOLD,冷數據采用基于磁盤的存儲。
3.計費(fèi)平台部多年來在支付領域有了相當多的技術積累,HOLD作爲NoSQL系統功能有限,因此建造一(yī)套更加強大(dà)通用的高一(yī)緻性存儲系統将整個支付領域的實時數據(重點是賬戶數據、用戶訂單數據,以及海量的流水數據)統一(yī)管理起來非常有價值。
基于上面的分(fēn)析,結合我(wǒ)(wǒ)們在MySQL領域多年的應用和優化經驗,最終決定在MySQL存儲引擎基礎之上,打造一(yī)套分(fēn)布式的SQL系統。
1.保持原來的MySQL協議,這樣以前訪問MySQL系統的C++、Java各類系統都不需要修改,DBA能繼續保持原來大(dà)部分(fēn)使用習慣。
2.自動的跨IDC容災切換,同時保證數據一(yī)緻性,對于提交成功的事務保證一(yī)筆不丢,達到銀行級對容災的要求。
3.靈活的容量伸縮機制,對業務透明,解決MySQL本身擴容不靈活的問題。
4.重點支持OLTP類型的在線業務。
整體(tǐ)架構
針對上面的需求,TDSQL最終的結構如圖1所示(與當前大(dà)部分(fēn)中(zhōng)心化的分(fēn)布式系統類似)。
Scheduler作爲集群的管理調度中(zhōng)心,主要功能包括:
1.管理set,提供創建、删除set、set内節點替換等工(gōng)作;
2.所有的DDL操作統一(yī)下(xià)發和調度;
3.監控set内各個節點的存活狀态,當set内主節點故障,發起高一(yī)緻性主備切換流程;
4.監控各個set的CPU、磁盤容量、各個表的資(zī)源消耗情況,必要的時候自動發起擴容流程;
5.Scheduler自身的容災通過ZooKeqzer的選舉機制完成,保證中(zhōng)心控制節點無單點。
Agent模塊負責監控本機MySQL實例的運行情況,主要功能包括:
1.用短連接的方式周期性訪問本機的MySQL實例,檢測是否可讀、可寫,若發生(shēng)異常,會将異常信息上報到ZooKeeper,最終會由上面描述的Scheduler模塊檢測到這個異常情況,從而發起容災切換;
2.檢測主備複制的執行情況,會定期上報主備複制的延時和延遲的事務數,若發生(shēng)了主備切換,自動向新主機重建主備,因此MySQL的主備不需要DBA幹預,對于新增的實例會3.自動采用 xtrabackup通過主機自動重建數據;
4.檢測MySQL實例的CPU利用率和各個表的請求量、數據量、CPU利用率,上報到ZooKeeper,ZooKeeper通過全局的資(zī)源情況抉擇如何擴容、縮容;
5.監控是否有下(xià)發到自身的擴容任務,如有則會執行擴容流程(下(xià)面會有描述);
監控是否要發生(shēng)容災切換,并按計劃執行主備切換流程。
網關基于MySQL Proxy開(kāi)發,在網絡層、連接管理、SQL解析、路由等方面做了大(dà)量優化,主要特點和功能如下(xià):
1.解析SQL,将識别出的DDL語句直接存到ZooKeeper,讓Keeper來統一(yī)調度;
2.Watch ZooKeeper的路由信息,拉取最新的路由表保存到本地文件和内存;
3.将SQL請求路由到對應的set,支持讀寫分(fēn)離(lí);
4.對接入的IP、用戶名、密碼進行鑒權;
5.記錄完整的SQL執行信息,與秒級監控平台對接完成實時的SQL請求的時耗,成功率等指标監控分(fēn)析;
6.對count、distinct、sum、avg、max、min、order by、group by等聚合類SQL一(yī)般需要訪問後端的多個set,網關會分(fēn)析結果并做合并再返回,暫不支持跨set join和分(fēn)布式事務;
7.網關無狀态,既支持與業務部署到一(yī)起,也可以獨立部署(可通過TGW或者LVS做容災)。
自動擴容機制
目前,針對MySQL的擴容,一(yī)般有下(xià)面兩種策略。
1.垂直擴容。一(yī)般通過升級硬件來實現,比如更換更好的CPU,将傳統的sas盤換成FusionIO卡這類,然後針對新硬件調整好參數,在硬件結構變化比較大(dà)的時候,性能甚至能達到上十倍的提升。但垂直擴容有比較大(dà)的局限,就是這種模式随着業務的突增還是比較容易達到瓶頸,特别是面對互聯網海量用戶的時候,所以在互聯網應用場景下(xià),一(yī)般僅将垂直擴容當做一(yī)個輔助的手段。
2.水平擴容。常用的有2種方法,一(yī)是不同的庫或者表部署到不同的實例,二是一(yī)張表需要根據某個字段拆分(fēn)到不同的字表中(zhōng)(數據分(fēn)片),這種策略在互聯網系統中(zhōng)非常常見,很多系統會将這2種水平擴容的方法結合起來使用;
通過上述2種擴容方法的比較,爲了應對海量擴展的需求,應該是重點選用水平擴容的方法。但水平擴容的實現一(yī)般對業務是有感知(zhī)的,比如采用什麽規則來拆表,拆開(kāi)的表放(fàng)到哪些節點,如果某個子表還有瓶頸應該怎麽擴容,擴容是否還需要業務配合等等這些事情如果全部交給業務會比較繁瑣,因此這些需求應該盡量全部交給TDSQL自身來完成,對業務完全透明。
分(fēn)表邏輯
在TDSQL中(zhōng),每個表(邏輯表)可能會拆分(fēn)成多個子表(建表的時候通過在建表語句中(zhōng)嵌入注釋的方式提供一(yī)個shard字段名,最多會拆分(fēn)出1W個子表),每個子表在MySQL上都是一(yī)個真實的物(wù)理表,這裏稱爲一(yī)個shard,因此一(yī)張表的數據可能會按這樣的方式分(fēn)布在多個Set中(zhōng),如圖2所示
每個SQL請求到達網關之後,網關會做詞法和語法解析,重點會解析出shard字段,如果帶了shard字段就可以直接查詢路由表并發送到某個具體(tǐ)的set中(zhōng)。計費(fèi)的OLTP類業務99%的請求都會帶上shard字段;如果某筆請求沒有shard字段,查詢路由之後會将請求發送到所有的shard對應的set中(zhōng),并對所有返回的結果做一(yī)些聚合運算。
擴容流程
上面描述了shard的方式,但是這樣的shard結構不是固定不變的,當Scheduler檢測到某個set,某個表的CPU、磁盤超過阈值之後就會啓動擴容流程。
這裏描述下(xià)具體(tǐ)的擴容流程。
擴容過程中(zhōng)一(yī)般都要盡量避免影響業務,目前來看存在2種比較成熟的策略。
策略1先切後搬:先修改路由,将需要遷走的數據的請求直接發送到新set,在新set交易過程中(zhōng)如發現本地的數據不存在,則去(qù)原set拉取數據,然後再通過一(yī)些離(lí)線的策略将要遷移的數據全量再搬遷一(yī)次,HOID平台就是采用這樣的策略。
策略2先搬後切:讓請求繼續在原set交易,擴容程序首先記錄一(yī)個binlog位置點,并将源set中(zhōng)符合遷移條件的數據全部遷移出去(qù),最後再将搬遷過程中(zhōng)新增的binlog追完,最後修改路由規則,将請求發送到新set。
綜合來看,策略1最大(dà)的優點是假如是因爲壓力大(dà)做的遷移,可能很快就能将部分(fēn)請求發送新set了,實現對原set的壓力分(fēn)擔;策略2實現上在最後的追路由階段需要更多的精細化控制,實現會稍微複雜(zá)點,但策略2有個非常大(dà)的好處就是擴容過程中(zhōng)回滾非常方便,如有異常直接幹掉擴容任務即可。
對于TDSQL這類數據庫業務系統來說,策略1實現會非常麻煩,因爲請求到達新set之後可能需要去(qù)源set拉取數據,這個需要對MySQL本身進行修改;另外(wài)假如一(yī)個批量更新的update操作,可能要往新老set都發送一(yī)次請求,比較複雜(zá),所以最終選擇了策略2。策略2會有更大(dà)的通用性,開(kāi)發模式基本上可以統一(yī)到所有類似的系統。
下(xià)面描述采用策略2具體(tǐ)的擴容流程。假如要将Set1中(zhōng)的t_shard_1的數據遷移一(yī)半到Set4中(zhōng)的t_shard_4(1667-3333)。
後将擴容任務下(xià)發到Set1中(zhōng)的agent模塊,agent檢測到擴容任務之後會采用mysqldump+where條件的方式将t_shard_1中(zhōng)shard号段爲1667-3333的記錄導出來并通過管道用并行的方式插入到Set4(不會在本地存文件,避免引起過多的IO),用mysqldump導出鏡像的時候會有一(yī)個binlog位置。
從mysqldump記錄的binlog位置開(kāi)始讀取binlog并插入到到Set4,追到所有binlog文件末尾的時候(這需要一(yī)個循環,每次循環記錄從開(kāi)始追binlog截止到追到文件結尾消耗的時間,必須保證追單次循環要在幾秒之内完成,避免遺留的binlog太多導緻最後一(yī)次追binlog消耗太多的時間,從而影響業務過久),對原來的表t_shard_1重命名t_shard_5,此時針對這個表不會再有新請求,若還有請求過來都會失敗,然後再追一(yī)次binlog到文件結尾(因爲上面的循環保證了追binlog不會太耗時間了,所以此次會快速完成),然後上報狀态到ZooKeeper,表明擴容任務完成。
Scheduler收到擴容完成的信息之後會修改路由表,最後由網關拉取到新路由完成整體(tǐ)的擴容;從表重命名開(kāi)始到網關拉取到新路由,這段時間這個原始shard不可用,從我(wǒ)(wǒ)們測試結果來看這個不可用的時間是200毫秒左右;如果某個網關異常,拉取不到新路由,繼續訪問老表t_shard_1會一(yī)直失敗,這樣就可以保證數據的一(yī)緻性。
容災機制
對于TDSQL來說,我(wǒ)(wǒ)們希望容災做到自動切換,自動恢複,主備一(yī)緻性(保證業務提交的事務在切換過程不丢失),跨IDC容災。
【MySQL異步複制】
在MySQL發展的早期,就提供了異步複制的技術,隻要寫的壓力不是特别大(dà),在網絡條件較好的情況下(xià),發生(shēng)主備切換基本上能将影響控制到秒級别,因此吸引了很多開(kāi)發者的關注和使用。但這套方案提供的一(yī)緻性保證,對于計費(fèi)或者金融行業是不夠的。
圖4是異步複制的大(dà)緻流程,很顯然主機提交了binlog就會返回給業務成功,沒有保證binlog同步到了備機,這樣在切換的瞬間很有可能丢失這部分(fēn)事務。
【Cluster方案】
除了上面的方案外(wài),開(kāi)源社區還有三個Cluster解決方案,分(fēn)别是Oracle的NDB引擎、Percona XtraDB Cluster和MariaDB Galera Cluster,從公開(kāi)資(zī)料的性能對比上來看,後2者在性能和系統靈活性等方面都強于NDB(同時采用NDB意味着也放(fàng)棄了InnoDB引擎,NDB主要是基于全内存的,并且需要高速網絡環境支持,所以不考慮了);Percona XtraDB Cluster和MariaDB Galera Cluster強同步機制的底層都是采用Galera這套強同步的架構。MariaDB Galera Cluster具有如下(xià)非常吸引人的特性:
1.MariaDB Galera Cluster 是一(yī)套在MySQL InnoDB存儲引擎上面實現multi-master及數據實時同步的系統架構,業務層面無需做讀寫分(fēn)離(lí)工(gōng)作,數據庫讀寫壓力都能按照既定的規則分(fēn)發到各個節點上去(qù);
2.同步複制Synchronous replication:保證節點間數據一(yī)緻性;
3.Active-active multi-master拓撲邏輯:多主的拓撲結構,可以認爲沒有備機的概念;
4.可對集群中(zhōng)任一(yī)節點進行數據讀寫:假如一(yī)個set有3個節點,則3個節點可以同時讀寫,上次完全不用關心主備切換和讀寫分(fēn)離(lí);
5.自動成員(yuán)控制,故障節點自動從集群中(zhōng)移除;
6.自動節點加入;
7.真正并行的複制,基于行級:同一(yī)個表可以在集群中(zhōng)任何節點更新,支持不帶where條件,但一(yī)次更新的記錄條數有限制;
8.每個節點都包含完整的數據副本。
目前來看,Galera是一(yī)套相當完美的方案。但是,在跨IDC的性能測試中(zhōng),其性能下(xià)降比較大(dà),另外(wài),實現方案也比較複雜(zá),目前對它的代碼理解還不夠透徹,所以暫時沒有在計費(fèi)領域大(dà)範圍推廣使用。但我(wǒ)(wǒ)相信這個方向是對的,有吸引力的,随着後續Galera越來越完善,我(wǒ)(wǒ)們對它研究得越透徹,也許有一(yī)天會采用這套方案。
【性能測試和分(fēn)析】
上面的三種複制模式對比測試,數據如圖6所示。
爲什麽性能損耗會這麽嚴重呢?這個看明白(bái)MySQL的網絡模型就清楚了。外(wài)界可查的MySQL最早的公開(kāi)版本應該是1996年的3.1.1.1版本,這麽多年來,網絡模型基本上變化不大(dà),與Apache有點類似,有點區别的是MySQL采用的是每個連接一(yī)個線程的模型,這套模型最大(dà)的好處就是開(kāi)發特别簡單,線程内部都是同步調用,隻要不訪問外(wài)部接口,支撐每秒幾百上千的請求量也基本夠用,因爲大(dà)部分(fēn)情況下(xià)IO是瓶頸。不過随着當前硬件的發展,尤其是SSD、FusionIO的出現,IOPS從200+/s進化到幾十萬甚至百萬次/s,IO基本上不再是瓶頸,若再采用這套模型并采用阻塞的方式調用延遲較大(dà)的外(wài)部接口,則CPU都會阻塞在等網絡應答上了,性能自然上不去(qù)。
不過在MySQL5.6企業版和MariaDB、Percona中(zhōng)都引入了線程池,使得網絡模型靈活了很多,圖7是簡化後的對比模型。
TDSQL采用的強同步方案
從上面的分(fēn)析可知(zhī),半同步半異步是比較輕量級的高一(yī)緻性容災方案,但受限于已有的同步網絡模型,CPU利用不起來。我(wǒ)(wǒ)們如果在線程池基礎之上做一(yī)些修改,參考半同步的思路就可以實現一(yī)個高性能的強同步方案。
目前的做法是采用與Linux内核處理中(zhōng)斷的思路:将上面線程池模型的第三個環節(執行SQL的邏輯)拆成兩個部分(fēn):
1.上半部分(fēn):任務執行到寫binlog爲止,然後将會話(huà)保存到session中(zhōng),接着執行下(xià)一(yī)輪循環去(qù)處理其他請求了,這樣就避免讓線程阻塞等待應答了;
2.然後:MySQL自身負責主備同步的dump線程會将binlog立即發送出去(qù),備機的IO線程收到binlog并寫入到relay log之後,再通過UDP給主機一(yī)個應答;
3.在主機上,開(kāi)一(yī)組線程來處理應答,收到應答之後找到對應的會話(huà),執行下(xià)半部分(fēn)的commit,send應答,綁定到epoll等操作。綁定到epoll之後這個連接又(yòu)可以被其他線程檢測到并執行了。
改造後性能提升明顯,如圖8所示。
數據高可用性保障機制
除上述強同步機制外(wài),TDSQL還做了以下(xià)增強,以提升數據的可用性。
1.推薦一(yī)個set最少配置3個跨IDC的節點,可以按業務的要求對備機開(kāi)放(fàng)查詢服務。
2.支持靈活增加節點,比如覺得3個節點還不夠,可以非常方便地增加節點。TDSQL會自動完成數據的全量和增量複制,此處主要依賴Xtrabackup實現物(wù)理複制,性能測試數據表明:一(yī)個小(xiǎo)時大(dà)概可以拷貝500GB數據到新節點。那麽對于Z3(1.1TB盤,一(yī)般最多用800GB左右),新加入的節點大(dà)概1.5個小(xiǎo)時左右就有了全量數據,此功能也可以用在壞盤等情況下(xià)替換節點的時候使用,非常方便。
3.細心的同學可能會發現上面的強同步還有點小(xiǎo)缺陷:比如主機用kill -9殺掉,那麽可能寫了binlog但沒有來得及發送到遠端,此時當然也不會返回給業務成功,備機上不存在這筆數據,但主機起來之後會多出來這筆事務。我(wǒ)(wǒ)們的做法是對新增的事務根據row格式的binlog做閃回,當然回退不了的比如drop table之類的,就直接提醒運維手工(gōng)确認是否清除數據庫,然後會由Xtrabakcup機制自動從新的備機全量拉取數據重構。
4.節點的監控通過跨IDC部署的ZooKeeper來保證,并且主備切換由一(yī)套自動化的嚴格流程來保證。
接下(xià)來的方向
1.當将高一(yī)緻性容災、高可用性、自動容量伸縮做實後,随着業務的接入,集群的規模會越來越大(dà),TDSQL必将會更加依賴實時的資(zī)源調度、隔離(lí)框架,因此有必要研究如何将TDSQL與Docker結合起來。
2.如前所述,Galera集群是個非常好的發展方向,我(wǒ)(wǒ)們會持續研究并實踐。
3.目前大(dà)部分(fēn)MySQL還在使用單個連接單線程模型,線程池也剛起步,以後随着大(dà)家對性能要求越來越高,這塊也許可以繼續突破,比如結合線程池+協程也許是個很好的方向,如果真能引入協程,也許爲MySQL增加調用外(wài)部接口的結構會靈活很多。
4.TDSQL将數據拆是拆的徹底了,但作爲完整的分(fēn)布式數據庫、合也需要考慮,比如跨庫少量記錄的join,規模受限的分(fēn)布式事務等,目前的做法是數據按小(xiǎo)時入TDW,在TDW上做OLAP分(fēn)析。