阿里巴巴數(shù)據(jù)庫分庫分表的實踐
1、阿里巴巴分布式數(shù)據(jù)層平臺發(fā)展和演變
業(yè)務(wù)數(shù)據(jù)從原來的單庫單表模式變成了數(shù)據(jù)被拆分到多個數(shù)據(jù)庫,甚至多個表中,如果在數(shù)據(jù)訪問層做一下功能的封裝和管控,所有分庫分表的邏輯和數(shù)據(jù)的跨庫操作都交給應(yīng)用的開發(fā)人員來實現(xiàn),則對開發(fā)人員的要求變得相對高一點,稍有不慎,可能會對平臺的業(yè)務(wù)包括數(shù)據(jù)帶來較大的影響。
在2006年阿里巴巴B2B團(tuán)隊以開源方式研發(fā)了Cobar這一關(guān)系型數(shù)據(jù)的分布式處理系統(tǒng)。該系統(tǒng)在很大程度上解決了最初使用Oracle數(shù)據(jù)庫因為存儲數(shù)據(jù)變得越來越大帶來的擴(kuò)展性問題,并且為開發(fā)人員提供了一個使用相對簡單的用戶體驗,在當(dāng)時Cobar平均每天處理近50億次的SQL操作。但隨著阿里巴巴業(yè)務(wù)場景越來越復(fù)雜,Cobar平臺功能上的約束對滿足某些業(yè)務(wù)場景顯得力不從心,例如:
1)不支持跨庫情況下的連接、分頁、排序、子查詢操作。
2)SET語句執(zhí)行會被忽略,處理事務(wù)和字符集設(shè)置除外。
3)分庫情況下,insert語句必須包含拆分字段列名。
4)分庫情況下,update語句不能更新拆分字段的值。
5)不支持SAVEPOINT操作。
6)使用JDBC時,不支持rewriteBatchedStatements=true參數(shù)設(shè)置(默認(rèn)為false)。
7)使用JDBC時,不支持useServerPrepStmts=true參數(shù)設(shè)置(默認(rèn)為false)。
8)使用JDBC時,BLOB、BINARY、VARBINARY字段不能使用setBlob()或setBinaryStream()方法設(shè)置參數(shù)。
2008年阿里巴巴內(nèi)部基于淘寶業(yè)務(wù)發(fā)展的需要,在Cobar的基礎(chǔ)上重新研發(fā)了分布式數(shù)據(jù)層框架TDDL(Taobao Distributed Data Layer,外號:頭都大了),針對分庫分表場景,提供了對各種業(yè)務(wù)場景的支持更加完善,開發(fā)人員體驗更加友好,管控能力大幅提升。
目前TDDL已經(jīng)成為阿里巴巴集團(tuán)內(nèi)部業(yè)務(wù)默認(rèn)使用的分布式數(shù)據(jù)層中間件,支撐著今天阿里巴巴上千個應(yīng)用,平均每天SQL調(diào)用超千億次。從架構(gòu)角度(如圖5-3所示),TDDL沿襲了Cobar之前在應(yīng)用和后端數(shù)據(jù)庫之間的定位,通過增加對SQL的解析實現(xiàn)了更為精準(zhǔn)的路由控制,以及對跨庫join、統(tǒng)計等計算的支持,彌補(bǔ)了之前Cobar在功能上的約束和限制,成為一個完整支持SQL語法兼容的平臺。
圖5-3TDDL架構(gòu)示意圖
三層數(shù)據(jù)源每層都按JDBC規(guī)范實現(xiàn),使得對前端應(yīng)用沒有任何代碼侵入。
Matrix層(TDataSource)實現(xiàn)分庫分表邏輯,底下持有多個GroupDs實例。
Group層(TGroupDataSource)實現(xiàn)數(shù)據(jù)庫的主備/讀寫分離邏輯,底下持有多個AtomDs實例。
Atom層(TAtomDataSource)實現(xiàn)數(shù)據(jù)庫連接(ip、port、password、connec-
tionProperties)等信息的動態(tài)推送,持有原子的數(shù)據(jù)源。
通過TDDL實現(xiàn)一次來自應(yīng)用的SQL請求,完整的交互流程(如圖5-4所示)中體現(xiàn)了各個服務(wù)組件所起到的作用。
圖5-4TDDL針對一次SQL請求完整處理流程
正是有了這樣的架構(gòu)和設(shè)計,特別是增加了對SQL語義的解析,使得TDDL相比之前的Cobar在功能上提升了一個新的層級,對于Cobar不支持的跨庫數(shù)據(jù)聚合、子查詢、group by、order by等特性都有了很好的支持,從而成為在分庫分表技術(shù)業(yè)界被很多技術(shù)同仁認(rèn)可的一套分布式數(shù)據(jù)層框架,總結(jié)來說,TDDL提供了以下優(yōu)點:
- 數(shù)據(jù)庫主備和動態(tài)切換。
- 帶權(quán)重的讀寫分離。
- 單線程讀重試。
- 集中式數(shù)據(jù)源信息管理和動態(tài)變更。
- 支持MySQL和Oracle數(shù)據(jù)庫。
- 基于JDBC規(guī)范,很容易擴(kuò)展支持實現(xiàn)JDBC規(guī)范的數(shù)據(jù)源。
- 無Server、client-jar形式存在,應(yīng)用直連數(shù)據(jù)庫。
- 讀寫次數(shù),并發(fā)度流程控制,動態(tài)變更。
- 可分析的日志打印,日志流控,動態(tài)變更。
隨著阿里巴巴集團(tuán)業(yè)務(wù)的多元化,特別是對于除電商領(lǐng)域以外業(yè)務(wù)的不斷擴(kuò)展和并購,TDDL這種無Server的模式對于故障的定位和對運(yùn)行環(huán)境的要求(必須是阿里巴巴內(nèi)部服務(wù)環(huán)境),支持這些新興業(yè)務(wù)有了不少困難,所以在2014年,阿里巴巴已經(jīng)研發(fā)出新一代分布式數(shù)據(jù)庫產(chǎn)品DRDS(Distributed Relational Database Service),該產(chǎn)品相比TDDL在業(yè)務(wù)場景的支持、故障的定位、運(yùn)維管控等方面又有了一個全面的提升,今天DRDS已經(jīng)成為阿里云上用于解決關(guān)系型數(shù)據(jù)庫線性擴(kuò)展問題的標(biāo)準(zhǔn)云產(chǎn)品,服務(wù)了幾百家阿里巴巴集團(tuán)外部的客戶。
2、數(shù)據(jù)盡可能平均拆分
不管是采用何種分庫分表框架或平臺,其核心的思路都是將原本保存在單表中太大的數(shù)據(jù)進(jìn)行拆分,將這些數(shù)據(jù)分散保存到多個數(shù)據(jù)庫的多個表中,避免因為單表數(shù)據(jù)太大給數(shù)據(jù)的訪問帶來讀寫性能的問題。所以在分庫分表場景下,最重要的一個原則就是被拆分的數(shù)據(jù)盡可能的平均拆分到后端的數(shù)據(jù)庫中,如果拆分得不均勻,還會產(chǎn)生數(shù)據(jù)訪問熱點,同樣存在熱點數(shù)據(jù)因為增長過快而又面臨數(shù)據(jù)單表數(shù)據(jù)過大的問題。
而對于數(shù)據(jù)以什么樣的維度進(jìn)行拆分,大家看到很多場景中都是對業(yè)務(wù)數(shù)據(jù)的ID(大部分場景此ID是以自增的方式)進(jìn)行哈希取模的方式將數(shù)據(jù)進(jìn)行平均拆分,這個簡單的方式確實在很多場景下都是非常合適的拆分方法,但并不是在所有的場景中這樣拆分的方式都是最優(yōu)選擇。也就是說數(shù)據(jù)如何拆分并沒有所謂的金科玉律,更多的是需要結(jié)合業(yè)務(wù)數(shù)據(jù)的結(jié)構(gòu)和業(yè)務(wù)場景來決定。
下面以大家最熟悉的電商訂單數(shù)據(jù)拆分為例,訂單是任何一個電商平臺中都會有的業(yè)務(wù)數(shù)據(jù),每個淘寶或天貓用戶在平臺上提交訂單后都會在平臺后端生成訂單相關(guān)的數(shù)據(jù),一般記錄一條訂單數(shù)據(jù)的數(shù)據(jù)庫表結(jié)構(gòu)如圖5-5所示。
訂單數(shù)據(jù)主要由三張數(shù)據(jù)庫表組成,主訂單表對應(yīng)的就是用戶的一個訂單,每提交一次都會生成一個主訂單表的數(shù)據(jù)。在有些情況下,用戶可能在一個訂單中選擇不同賣家的商品,而每個賣家又會按照該訂單中是自己提供的商品計算相關(guān)的商品優(yōu)惠(比如滿88元免快遞費(fèi))以及安排相關(guān)的物流配送,所以會出現(xiàn)子訂單的概念,即一個主訂單會由多個子訂單組成,而真正對應(yīng)到具體每個商品的訂單信息,則是保存在訂單詳情表中。
圖5-5 訂單相關(guān)數(shù)據(jù)表結(jié)構(gòu)示意
如果一個電商平臺的業(yè)務(wù)發(fā)展健康的話,訂單數(shù)據(jù)是比較容易出現(xiàn)因為單個數(shù)據(jù)庫表中的數(shù)據(jù)太大而造成性能的瓶頸,所以需要對它進(jìn)行數(shù)據(jù)庫的拆分。此時從理論上對訂單拆分是可以由兩個維度進(jìn)行的,一個維度是通過訂單ID(一般為自增ID)取模的方式,即以訂單ID為分庫分表鍵;一個是通過買家用戶ID的維度進(jìn)行哈希取模,即以買家用戶ID為分庫分表鍵。
兩種方案做一下對比:
如果是按照訂單ID取模的方式,比如按64取模,則可以保證主訂單數(shù)據(jù)以及相關(guān)的子訂單、訂單詳情數(shù)據(jù)平均落入到后端的64個數(shù)據(jù)庫中,原則上很好地滿足了數(shù)據(jù)盡可能平均拆分的原則。
通過采用買家用戶ID哈希取模的方式,比如也是按64取模,技術(shù)上則也能保證訂單數(shù)據(jù)拆分到后端的64個數(shù)據(jù)庫中,但這里就會出現(xiàn)一個業(yè)務(wù)場景中帶來的一個問題,就是如果有些賣家是交易量非常大的(這樣的群體不在少數(shù)),那這些賣家產(chǎn)生的訂單數(shù)據(jù)量(特別是訂單詳情表的數(shù)據(jù)量)會比其他賣家要多出不少,也就是會出現(xiàn)數(shù)據(jù)不平均的現(xiàn)象,最終導(dǎo)致這些賣家的訂單數(shù)據(jù)所在的數(shù)據(jù)庫會相對其他數(shù)據(jù)庫提早進(jìn)入到數(shù)據(jù)歸檔(為了避免在線交易數(shù)據(jù)庫的數(shù)據(jù)的增大帶來數(shù)據(jù)庫性能問題,淘寶將3個月內(nèi)的訂單數(shù)據(jù)保存進(jìn)在線交易數(shù)據(jù)庫中,超過3個月的訂單會歸檔到后端專門的歸檔數(shù)據(jù)庫)。
所以從對“數(shù)據(jù)盡可能平均拆分”這條原則來看,按照訂單ID取模的方式看起來是更能保證訂單數(shù)據(jù)進(jìn)行平均拆分,但我們暫且不要這么快下結(jié)論,讓我們繼續(xù)從下面幾條原則和最佳實踐角度多思考不同的拆分維度帶來的優(yōu)缺點。
3、盡量減少事務(wù)邊界
不管是TDDL平臺還是DRDS,采用分庫分表的方式將業(yè)務(wù)數(shù)據(jù)拆分后,如果每一條SQL語句中都能帶有分庫分表鍵,如圖5-6所示是以自增的訂單ID以8取模,將訂單平均分布到8個數(shù)據(jù)庫的訂單表中,通過分布式服務(wù)層在對于SQL解析后都能精準(zhǔn)地將這條SQL語句推送到該數(shù)據(jù)所在的數(shù)據(jù)庫上執(zhí)行,數(shù)據(jù)庫將執(zhí)行的結(jié)果再返回給分布式服務(wù)層,分布式服務(wù)層再將結(jié)果返回給應(yīng)用,整個數(shù)據(jù)庫訪問的過程跟之前的單機(jī)數(shù)據(jù)庫操作沒有任何差別。這個是在數(shù)據(jù)進(jìn)行了分庫分表拆分后,SQL語句執(zhí)行效率最高的方式。
圖5-6DRDS對帶分庫分表鍵的SQL請求處理
但不是所有的業(yè)務(wù)場景在進(jìn)行數(shù)據(jù)庫訪問時每次都能帶分庫分表鍵的。比如在買家中心的界面中,要顯示買家test1過去三個月的訂單列表信息,因為該買家test1的訂單按訂單ID取模的方式分布到了不同的數(shù)據(jù)庫中,此時SQL語句中就沒有了分庫分表鍵值,則出現(xiàn)了如圖5-7所示的情況,分布式數(shù)據(jù)層會將獲取test1訂單的SQL語句推送到后端所有數(shù)據(jù)庫中執(zhí)行,然后將后端數(shù)據(jù)庫返回的結(jié)果在分布式數(shù)據(jù)層進(jìn)行聚合后再返回給前端應(yīng)用。
圖5-7DRDS對不帶分庫分表鍵的SQL請求進(jìn)行全表掃描處理
此時就出現(xiàn)了我們所說的全表掃描。此時我們來解釋一下這里“事務(wù)邊界”的定義,所謂的事務(wù)邊界即是指單個SQL語句在后端數(shù)據(jù)庫上同時執(zhí)行的數(shù)量,上面示例中就是事務(wù)邊界大的典型示例,即一條SQL語句同時被推送到后端所有數(shù)據(jù)庫中運(yùn)行。事務(wù)邊界的數(shù)量越大,會給系統(tǒng)帶來以下弊端:
系統(tǒng)的鎖沖突概率越高。如果事務(wù)邊界大的SQL請求比較多,在一次SQL請求處理過程中自然對于后端的數(shù)據(jù)庫操作的數(shù)據(jù)庫記錄覆蓋比較廣,當(dāng)有多個類似的SQL請求并行執(zhí)行時,則出現(xiàn)數(shù)據(jù)鎖造成的資源訪問互斥的概率會大大增加。
系統(tǒng)越難以擴(kuò)展。如果有大量的SQL請求都是這樣全表掃描,或者從極端角度說明這個問題,如果每一次的SQL請求都需要全表掃描執(zhí)行,你會發(fā)現(xiàn)整個平臺的數(shù)據(jù)庫連接數(shù)量是取決于后端單個數(shù)據(jù)庫的連接能力,也就意味著整個數(shù)據(jù)庫的能力是無法通過增加后端數(shù)據(jù)庫實例來擴(kuò)展的。所以如果有大量的全表掃描的SQL請求對于系統(tǒng)的擴(kuò)展能力會帶來不小的影響。
整體性能越低。對于性能,這里想強(qiáng)調(diào)的是對系統(tǒng)整體性能的影響,而不是單次SQL的性能。應(yīng)用發(fā)送獲取買家test1訂單列表SQL的請求(如圖5-8步驟①)時,分布式數(shù)據(jù)層會并行的將這條SQL語句推送(如圖5-8步驟②)到后端8臺數(shù)據(jù)庫上運(yùn)行,因為訂單數(shù)據(jù)進(jìn)行了平均的拆分,單個數(shù)據(jù)庫訂單表的數(shù)據(jù)量大小都使得數(shù)據(jù)庫處于最佳性能表現(xiàn)的狀態(tài),所以意味著每一個數(shù)據(jù)庫返回的計算結(jié)果都是在一個可期望的時間內(nèi)(比如100毫秒),將結(jié)果返回到分布式數(shù)據(jù)層(如圖5-8步驟③),分布式數(shù)據(jù)層將從各個數(shù)據(jù)庫返回來的結(jié)果在內(nèi)存中進(jìn)行聚合或排序等操作(如圖5-8步驟④),最后返回訂單列表給應(yīng)用(如圖5-8步驟⑤)。
圖5-8DRDS對需全表掃描操作的SQL請求處理流程
整個SQL執(zhí)行的過程包含了5個步驟,仔細(xì)看看,你會發(fā)現(xiàn)一次帶分庫分表鍵執(zhí)行的SQL過程也會經(jīng)歷這5個步驟,區(qū)別只是在②③步驟是并行的方式同時跟多個后端數(shù)據(jù)庫進(jìn)行交互,但在時間上帶來的影響幾乎是毫秒級的;而第④個步驟是可能造成差異的一個點,如果像示例中一個用戶的訂單信息可能最多幾千條,對于幾千條數(shù)據(jù)的內(nèi)存聚合操作,處理時間也是毫秒級的,所以這樣一次全表掃描的操作,用戶的體驗是完全無感知的,跟訪問單機(jī)數(shù)據(jù)庫的體驗是沒有差異的。但如果在第④個步驟中確實遇到對大數(shù)據(jù)量(比如幾十萬、幾百萬條數(shù)據(jù))的聚合、排序、分組等計算時,則會占用較大的內(nèi)存和CPU計算資源,如果這樣類型的SQL請求比較頻繁的話,就會給分布式數(shù)據(jù)層帶來較大的資源占用,從而導(dǎo)致整體分布式服務(wù)的處理性能受到影響。
很多人對于全表掃描會有一些誤解,甚至認(rèn)為出現(xiàn)全表掃描對于系統(tǒng)來說是完全不能接受的。其實全表掃描在真實的業(yè)務(wù)場景中很難完全避免(也可以做到完全避免,但會帶來其他方面的問題,后面會有說明),對于在分布式數(shù)據(jù)層的內(nèi)存中進(jìn)行數(shù)據(jù)量不大的聚合這類的SQL請求,如果不是高并發(fā)同時請求的情況下,比如對訂單進(jìn)行復(fù)雜的條件檢索,如圖5-9所示,就一定需要采用全表掃描的方式,將查詢語句同時推送到后端的數(shù)據(jù)庫中才能實現(xiàn)該場景的要求,但因為調(diào)用不會特別頻繁,而且計算的數(shù)據(jù)量不會太大,所以整體不會給數(shù)據(jù)庫整體性能帶來太大的影響。
圖5-9 訂單搜索是典型的多條件查詢場景
如果是高并發(fā)情況下同時請求的話,為了數(shù)據(jù)庫整體的擴(kuò)展能力,則要考慮下面描述的異構(gòu)索引手段來避免這樣的情況發(fā)生。對于在內(nèi)存中要進(jìn)行大數(shù)據(jù)量聚合操作和計算的SQL請求,如果這類SQL的不是大量并發(fā)或頻繁調(diào)用的話,平臺本身的性能影響也不會太大,如果這類SQL請求有并發(fā)或頻繁訪問的要求,則要考慮采用其他的平臺來滿足這一類場景的要求,比如Hadoop這類做大數(shù)據(jù)量離線分析的產(chǎn)品,如果應(yīng)用對請求的實時性要求比較高,則可采用如內(nèi)存數(shù)據(jù)庫或HBase這類平臺,這一部分的內(nèi)容不在本書中討論。
4、異構(gòu)索引表盡量降低全表掃描頻率
還是基于訂單數(shù)據(jù)的分庫分表場景,按照訂單ID取模雖然很好地滿足了訂單數(shù)據(jù)均勻地保存在后端數(shù)據(jù)庫中,但在買家查看自己訂單的業(yè)務(wù)場景中,就出現(xiàn)了全表掃描的情況,而且買家查看自己訂單的請求是非常頻繁的,必然給數(shù)據(jù)庫帶來擴(kuò)展或性能的問題,有違“盡量減少事務(wù)邊界”這一原則。其實這類場景還有很多,比如賣家要查看與自己店鋪相關(guān)的訂單信息,同樣也會出現(xiàn)上述所說的大量進(jìn)行全表掃描的SQL請求。
針對這類場景問題,最常用的是采用“異構(gòu)索引表”的方式解決,即采用異步機(jī)制將原表內(nèi)的每一次創(chuàng)建或更新,都換另一個維度保存一份完整的數(shù)據(jù)表或索引表。本質(zhì)上這是互聯(lián)網(wǎng)公司很多時候都采用的一個解決思路:“拿空間換時間”。
也就是應(yīng)用在創(chuàng)建或更新一條按照訂單ID為分庫分表鍵的訂單數(shù)據(jù)時,也會再保存一份按照買家ID為分庫分表鍵的訂單索引數(shù)據(jù),如圖5-10所示,其結(jié)果就是同一買家的所有訂單索引表都保存在同一數(shù)據(jù)庫中,這就是給訂單創(chuàng)建了異構(gòu)索引表。
圖5-10 訂單異構(gòu)索引表
這時再來看看買家test1在獲取訂單信息進(jìn)行頁面展現(xiàn)時,應(yīng)用對于數(shù)據(jù)庫的訪問流程就發(fā)生了如圖的5-11變化。
在有了訂單索引表后,應(yīng)用首先會通過當(dāng)前買家ID(以圖示中test1為例),首先到訂單索引表中搜索出test1的所有訂單索引表(步驟①),因為步驟②SQL請求中帶了以buyer_ID的分庫分表鍵,所以一次是效率最高的單庫訪問,獲取到了買家test1的所有訂單索引表列表并由DRDS返回到了前端應(yīng)用(步驟③和④),應(yīng)用在拿到返回的索引列表后,獲取到訂單的ID列表(1,5,8),在發(fā)送一次獲取真正訂單列表的請求(步驟⑤),同樣在步驟⑥的SQL語句的條件中帶了分庫分表鍵order_ID的列表值,所以DRDS可以精確地將此SQL請求發(fā)送到后端包含in列表值中訂單ID的數(shù)據(jù)庫,而不會出現(xiàn)全表掃描的情況,最終通過兩次訪問效率最高的SQL請求代替了之前需要進(jìn)行全表掃描的問題。
圖5-11 基于訂單索引表實現(xiàn)買家訂單列表查看流程示意
這時你可能會指出,為什么不是將訂單的完整數(shù)據(jù)按照買家ID維度進(jìn)行一次分庫保存,這樣就只需要進(jìn)行一次按買家ID維度進(jìn)行數(shù)據(jù)庫的訪問就獲取到訂單的信息?這是一個好問題,其實淘寶的訂單數(shù)據(jù)就是在異構(gòu)索引表中全復(fù)制的,即訂單按照買家ID維度進(jìn)行分庫分表的訂單索引表跟以訂單ID維度進(jìn)行分庫分表的訂單表中的字段完全一樣,這樣確實避免了多一次的數(shù)據(jù)庫訪問。但一般來說,應(yīng)用可能會按照多個維度創(chuàng)建多個異構(gòu)索引表,比如為了避免買家查看自己的訂單時頻繁進(jìn)行全表掃描,實際中還會以買家ID的維度進(jìn)行異構(gòu)索引表的建立,所以采用這樣數(shù)據(jù)全復(fù)制的方法會帶來大量的數(shù)據(jù)冗余,從而增加不少數(shù)據(jù)庫存儲成本。
另外,在某些場景中,在獲取主業(yè)務(wù)表的列表時,可能需要依賴此業(yè)務(wù)表所在數(shù)據(jù)庫的子業(yè)務(wù)表信息,比如訂單示例中的主、子訂單,因為是以訂單ID的維度進(jìn)行了分庫分表,所以該訂單相關(guān)的子訂單、訂單明細(xì)表都會保存在同一個數(shù)據(jù)庫中,如果我們僅僅是對主訂單信息做了數(shù)據(jù)全復(fù)制的異構(gòu)保存,還是通過一次對這張異構(gòu)表的數(shù)據(jù)進(jìn)行查詢獲取包含了子訂單信息的訂單列表時,就會出現(xiàn)跨庫join的問題,其對分布式數(shù)據(jù)層帶來的不良影響其實跟之前所說的全表掃描是一樣的。所以我們還是建議采用僅僅做異構(gòu)索引表,而不是數(shù)據(jù)全復(fù)制,同時采用兩次SQL請求的方式解決出現(xiàn)全表掃描的問題。
實現(xiàn)對數(shù)據(jù)的異步索引創(chuàng)建有多種實現(xiàn)方式,一種是從數(shù)據(jù)庫層采用數(shù)據(jù)復(fù)制的方式實現(xiàn);另一種是如圖5-12所示在應(yīng)用層實現(xiàn),在這一層實現(xiàn)異構(gòu)索引數(shù)據(jù)的創(chuàng)建,就必然會帶來分布式事務(wù)的問題。
圖5-12 精衛(wèi)實現(xiàn)數(shù)據(jù)同步的流程圖
這里給大家介紹的是在數(shù)據(jù)庫層實現(xiàn)異構(gòu)索引的方式,也是阿里巴巴內(nèi)部目前采用的方式,通過一款名為精衛(wèi)填海(簡稱精衛(wèi))的產(chǎn)品實現(xiàn)了數(shù)據(jù)的異構(gòu)復(fù)制。本質(zhì)上精衛(wèi)是一個基于MySQL的實時數(shù)據(jù)復(fù)制框架,可以通過圖形界面配置的方式就可以實現(xiàn)異構(gòu)數(shù)據(jù)復(fù)制的需求。除了在同步異構(gòu)索引數(shù)據(jù)的場景外,可以認(rèn)為精衛(wèi)是一個MySQL的數(shù)據(jù)觸發(fā)器+分發(fā)管道。
數(shù)據(jù)從源數(shù)據(jù)庫向目標(biāo)數(shù)據(jù)庫的過程中,可能需要對數(shù)據(jù)進(jìn)行一些過濾和轉(zhuǎn)換,精衛(wèi)本身的結(jié)構(gòu)分為抽取器(Extractor)、管道(Pipeline)、分發(fā)器(Applier),數(shù)據(jù)從抽取器流入管道,管道中有過濾器可以執(zhí)行對數(shù)據(jù)的一些過濾的操作,然后再交由分發(fā)器寫入到目標(biāo),如圖5-12所示。
精衛(wèi)平臺通過抽取器(Extractor)獲取到訂單數(shù)據(jù)創(chuàng)建在MySQL數(shù)據(jù)庫中產(chǎn)生的binlog日志(binlog日志會記錄對數(shù)據(jù)發(fā)生或潛在發(fā)生更改的SQL語句,并以二進(jìn)制的形式保存在磁盤中),并轉(zhuǎn)換為event對象,用戶可通過精衛(wèi)自帶的過濾器(Filter)(比如字段過濾、轉(zhuǎn)換等)或基于接口自定義開發(fā)的過濾器對event對象中的數(shù)據(jù)進(jìn)行處理,最終通過分發(fā)器(Applier)將結(jié)果轉(zhuǎn)換為發(fā)送給DRDS的SQL語句,通過精衛(wèi)實現(xiàn)異構(gòu)索引數(shù)據(jù)的過程如圖5-13所示。
雖然精衛(wèi)平臺在系統(tǒng)設(shè)計和提供的功能不算復(fù)雜,但其實但凡跟數(shù)據(jù)相關(guān)的平臺就不會簡單。這里不會對精衛(wèi)核心的組件和機(jī)制做更詳細(xì)的介紹,只是將精衛(wèi)多年來能力演變后,目前提供的核心功能做一下介紹,為有志在該領(lǐng)域深耕細(xì)作的技術(shù)同仁多一些思路和借鑒。
圖5-13 采用精衛(wèi)平臺實現(xiàn)異構(gòu)索引表流程示意
(1)多線程管道實現(xiàn)
在精衛(wèi)平臺應(yīng)用的早期,數(shù)據(jù)的同步均是采用單線程管道任務(wù)模式,即如
圖5-12中對binlog進(jìn)行單線程的處理。隨著業(yè)務(wù)的發(fā)展,需要同步的數(shù)據(jù)量越來越大,單純的單線程管道任務(wù)已經(jīng)成為系統(tǒng)的瓶頸,后來開發(fā)了對多線程管道任務(wù)的支持(如圖5-14所示)。
圖5-14 精衛(wèi)支持多線程管道數(shù)據(jù)同步
但多線程管道就會帶來數(shù)據(jù)同步的順序問題。在對binlog數(shù)據(jù)進(jìn)行多線程并行處理后,就不能保證在源數(shù)據(jù)庫中執(zhí)行的SQL語句在目標(biāo)數(shù)據(jù)庫的順序一致,這樣在某些業(yè)務(wù)場景中一定會出現(xiàn)數(shù)據(jù)不一致性的問題。對于這個問題,目前精衛(wèi)中提供的解決思路是保證同一條記錄或針對同一分庫表發(fā)生的數(shù)據(jù)同步按照順序執(zhí)行。
如果最后發(fā)送到分布式數(shù)據(jù)層的SQL語句中沒有分庫鍵,則通過對“庫名+表名+主鍵值”哈希后對線程數(shù)取模,這樣就能讓同一條記錄的數(shù)據(jù)同步事件處理都會在同一線程中順序執(zhí)行,保證了該記錄多次變更的順序性,但是不保證不同記錄間的順序。如果SQL語句中有分庫鍵,則通過“庫名+分庫鍵值”哈希后對線程數(shù)取模,效果是保證不同邏輯表針對相同分庫邏輯的記錄變化順序。
(2)數(shù)據(jù)的安全
凡是牽涉數(shù)據(jù)的操作,數(shù)據(jù)的安全一定是最重要的。如何保證在分布式環(huán)境下同步任務(wù)效率最大化,同時保證服務(wù)的穩(wěn)定和數(shù)據(jù)的安全,是很多此類平臺精益求精、力求突破的方向。
平臺穩(wěn)定性保障。為了保證同步任務(wù)執(zhí)行的效率最大化,同時互相不會因為資源會搶占或某些同步任務(wù)的異常對其他任務(wù)造成影響,在精衛(wèi)的系統(tǒng)設(shè)計中,支持多個服務(wù)節(jié)點作為任務(wù)執(zhí)行的集群,通過統(tǒng)一的任務(wù)調(diào)度系統(tǒng)(Zookeeper集群),將任務(wù)分配到集群中的各節(jié)點并行執(zhí)行。
為了保證任務(wù)間不會因為同步任務(wù)性能或異常造成互相的干擾,采用了每個同步任務(wù)都是獨(dú)立Java進(jìn)程的方式運(yùn)行,出現(xiàn)異常該任務(wù)自動終止。任務(wù)調(diào)度系統(tǒng)會定期輪詢?nèi)蝿?wù)列表,發(fā)現(xiàn)任務(wù)缺少立即搶占式啟動該任務(wù)。
心跳+報警。運(yùn)行集群與ZooKeeper采用定時心跳的方式,將集群節(jié)點的運(yùn)行狀態(tài)以及任務(wù)完成的位點(即目前同步任務(wù)處理binlog的進(jìn)度信息)信息同步到Zookeeper上,如果心跳信息異常或位點時間落后過大則立即報警。在抽取器和分發(fā)器發(fā)生任何錯誤復(fù)制任務(wù)立即轉(zhuǎn)變成STANDBY狀態(tài),集群中其他機(jī)器上的服務(wù)在感知后會立即將自己啟動,繼續(xù)執(zhí)行前一復(fù)制任務(wù)。
MySQL主備切換。利用比對主備數(shù)據(jù)庫的狀態(tài)信息,通過以下順序,采用手工的方式處理MySQL出現(xiàn)主備切換時進(jìn)行同步任務(wù)的恢復(fù):
1)查看新主庫的當(dāng)前位點Show master status,獲取到PA狀態(tài)。
2)查看老主庫拉去新主庫的位置Show slave status,獲取到PR狀態(tài)。
3)如果PR>PA,直接用新主庫的位點PA切換到新主庫上讀取。
如果希望通過自動化的方式,實現(xiàn)的思路則可利用binlog里的serverId和時間戳,發(fā)現(xiàn)dump的binlog中的serverId發(fā)生變化記錄變化時間戳,然后在給定的MySQL服務(wù)器中查找到有同樣變化的數(shù)據(jù)庫,根據(jù)探測到的serverId發(fā)生變化的時間戳進(jìn)行回溯,在新的機(jī)器符合條件的位點進(jìn)行dump。
MySQL異常掛掉。利用數(shù)據(jù)庫上binlog文件修改時間,按照以下順序采取手工的方式進(jìn)行整個文件回溯:
1)在數(shù)據(jù)庫所在的服務(wù)器上找到服務(wù)掛掉的時間點。
2)到新的主機(jī)上查看找到服務(wù)掛掉時間點之前最近的binlog文件。
3)從這個文件的位點開始進(jìn)行回溯。
如果希望通過自動化的方式自動進(jìn)行恢復(fù),可同樣借鑒MySQL主備切換中提到的自動化實現(xiàn)思路。
(3)友好的用戶自服務(wù)接入體驗
精衛(wèi)平臺是整個電商業(yè)務(wù)實現(xiàn)數(shù)據(jù)實時同步復(fù)制的統(tǒng)一平臺,負(fù)責(zé)來自上千個不同應(yīng)用的需求,如果每一個應(yīng)用的接入都需要平臺的技術(shù)人員給予入門的培訓(xùn)和支持都是非常大的工作量,也會影響到前端應(yīng)用的用戶體驗。所以提供一個用戶體驗友好,自帶常用功能的平臺,能針對大部分的業(yè)務(wù)需求可以讓應(yīng)用方在界面上通過配置的方式就能實現(xiàn),大大降低接入開發(fā)成本。
如圖5-15所示,精衛(wèi)平臺給應(yīng)用方客戶提供了Web的配置界面,可讓用戶針對需要同步的數(shù)據(jù)源進(jìn)行設(shè)置,并對數(shù)據(jù)同步的事件類型(增、刪、改)和是否進(jìn)行分表以及分庫分表鍵列等進(jìn)行設(shè)置。
精衛(wèi)平臺的數(shù)據(jù)庫分發(fā)器支持一些高級功能,如字段過濾、字段映射、action轉(zhuǎn)換等,如果自帶功能不滿足需求,可以上傳包含自己的業(yè)務(wù)邏輯的過濾代碼。這些功能的使用也提供了界面的方式,讓用戶對源數(shù)據(jù)庫表中的字段如何映射到目標(biāo)數(shù)據(jù)庫表進(jìn)行設(shè)置(如圖5-16所示)。
圖5-15 精衛(wèi)支持界面配置不同數(shù)據(jù)源間的數(shù)據(jù)同步
圖5-16 精衛(wèi)提供的自服務(wù)體驗提升數(shù)據(jù)同步服務(wù)接入效率
正是有了這樣簡單易用的用戶體驗,使得精衛(wèi)平臺在應(yīng)用的接入效率和用戶滿意度上都有非常不錯的表現(xiàn)。
(4)平臺管控和統(tǒng)計
在精衛(wèi)的平臺中,每天都運(yùn)行著上千億次的數(shù)據(jù)同步和復(fù)制任務(wù),必然需要對這些任務(wù)的執(zhí)行有一個清晰的管控,甚至可以從中找出對業(yè)務(wù)數(shù)據(jù)變化的趨勢。實現(xiàn)的方法是定時輪詢Zookeeper集群中對應(yīng)任務(wù)的節(jié)點進(jìn)行監(jiān)控,如圖5-17所示。目前提供以下三個方面監(jiān)控:
心跳監(jiān)控。
延遲堆積監(jiān)控。
任務(wù)狀態(tài)、數(shù)據(jù)監(jiān)控(TPS、異常)等。
圖5-17 精衛(wèi)平臺提供的數(shù)據(jù)同步監(jiān)控
采用類似精衛(wèi)這樣的平臺實現(xiàn)數(shù)據(jù)異構(gòu)索引的好處是,不需要在各個前端應(yīng)用層的代碼中去實現(xiàn),只需統(tǒng)一通過精衛(wèi)平臺實現(xiàn)。有了這樣專業(yè)的平臺來實現(xiàn)數(shù)據(jù)同步的效率、服務(wù)高可用性、任務(wù)管控、統(tǒng)計等,能提供更好的服務(wù)。但設(shè)計這樣的平臺確實需要掌握數(shù)據(jù)庫相關(guān)知識,以及任務(wù)調(diào)度、平臺管控等技術(shù),甚至需要在各種復(fù)雜場景中逐步打磨和完善技術(shù)。所以如果有些企業(yè)還沒有這樣數(shù)據(jù)同步的專業(yè)平臺,通常會建議采用通過在應(yīng)用層實現(xiàn)數(shù)據(jù)的異構(gòu)索引,具體實現(xiàn)方式在6.3節(jié)中重點闡述。
5、將多條件頻繁查詢引入搜索引擎平臺
采用數(shù)據(jù)異構(gòu)索引的方式在實戰(zhàn)中基本能解決和避免90%以上的跨join或全表掃描的情況,是在分布式數(shù)據(jù)場景下,提升數(shù)據(jù)庫服務(wù)性能和處理吞吐能力的最有效技術(shù)手段。但在某些場景下,比如淘寶商品的搜索(如圖5-18)和高級搜索(如圖5-19),因為商品搜索幾乎是訪問淘寶用戶都會進(jìn)行的操作,所以調(diào)用非常頻繁,如果采用SQL語句的方式在商品數(shù)據(jù)庫進(jìn)行全表掃描的操作,則必然對數(shù)據(jù)庫的整體性能和數(shù)據(jù)庫連接資源帶來巨大的壓力。
圖5-18 淘寶網(wǎng)商品全文搜索
圖5-19 淘寶網(wǎng)商品高級搜索
所以面對此類場景,我們不建議采用數(shù)據(jù)庫的方式提供這樣的搜索服務(wù),而是采用專業(yè)的搜索引擎平臺來行使這樣的職能,實現(xiàn)的架構(gòu)如圖5-20所示。
圖5-20 全文搜索實現(xiàn)示意圖
阿里巴巴有自身的主搜索平臺,該平臺承載了淘寶、天貓、一淘、1688、神馬搜索等搜索業(yè)務(wù),其核心功能跟業(yè)界開源工具,如Iucene、Solr、ElasticSearch等搜索引擎類似,但在數(shù)據(jù)同步(從數(shù)據(jù)庫到搜索引擎)、索引創(chuàng)建算法、查詢執(zhí)行計劃、排序算法等方面針對商品搜索這樣的場景做了相應(yīng)的調(diào)整和功能增強(qiáng)。該搜索平臺目前已經(jīng)以阿里云上OpenSearch產(chǎn)品的形態(tài),給有此類搜索需求的客戶提供強(qiáng)大的搜索服務(wù),更多關(guān)于該平臺詳細(xì)的資料可訪問開放搜索服務(wù)的官方網(wǎng)站:https://www.aliyun.com/product/opensearch。
6、簡單就是美
在真實的世界中,選擇的困難往往是因為充滿著各種誘惑,選擇A方案,有這些好處;而選擇B方案,也會有另外一些好處。
如果在“盡量減小事務(wù)邊界”與“數(shù)據(jù)盡可能平均拆分”兩個原則間發(fā)生了沖突,那么請選擇“數(shù)據(jù)盡可能平均拆分”作為優(yōu)先考慮原則,因為事務(wù)邊界的問題相對來說更好解決,無論是做全表掃描或做異構(gòu)索引復(fù)制都是可以解決的。而寫入或單機(jī)容量如果出現(xiàn)不均衡,那么處理起來難度就比較大。
盡管復(fù)雜的切分規(guī)則或數(shù)據(jù)的異構(gòu)索引能夠給系統(tǒng)的性能和擴(kuò)展性帶來顯著的收益,但其后面所帶來的系統(tǒng)運(yùn)維復(fù)雜度上升也是不能忽視的一個結(jié)果。
如果為每一個存在跨join或全表掃描的場景都采用數(shù)據(jù)異構(gòu)索引的方式,整個數(shù)據(jù)庫出現(xiàn)大量數(shù)據(jù)冗余,數(shù)據(jù)一致性的保障也會帶來挑戰(zhàn),同時數(shù)據(jù)庫間的業(yè)務(wù)邏輯關(guān)系也變得非常復(fù)雜,給數(shù)據(jù)庫運(yùn)維帶來困難和風(fēng)險,從而對數(shù)據(jù)庫運(yùn)維人員的要求和依賴會非常高,所以從系統(tǒng)風(fēng)險的角度考慮,以82法則,在實際中,我們僅針對那些在80%情況下訪問的那20%的場景進(jìn)行如數(shù)據(jù)異構(gòu)索引這樣的處理,達(dá)到這類場景的性能最優(yōu)化,而對其他80%偶爾出現(xiàn)跨庫join、全表掃描的場景,采用最為簡單直接的方式往往是就最有效的方式。