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





















































