在互聯(lián)網(wǎng)時(shí)代,隨著業(yè)務(wù)數(shù)量的暴增和應(yīng)用規(guī)模的不斷擴(kuò)大,無(wú)論是oracle還是mysql這樣子的關(guān)系型數(shù)據(jù)庫(kù),都會(huì)面臨服務(wù)器CPU、磁盤(pán)IO和內(nèi)存的各種瓶頸問(wèn)題。基于此情況,各個(gè)業(yè)務(wù)團(tuán)隊(duì)迫切需要一種數(shù)據(jù)分片的方案將業(yè)務(wù)數(shù)據(jù)量存儲(chǔ)成本分?jǐn)偟匠杀究煽氐母鱾€(gè)普通數(shù)據(jù)庫(kù)服務(wù)器上,數(shù)據(jù)庫(kù)切分的方案便應(yīng)運(yùn)而生。
本文將結(jié)合實(shí)際的業(yè)務(wù)場(chǎng)景進(jìn)行分析(Java自學(xué)網(wǎng)推薦),詳細(xì)闡述選型開(kāi)源組件—ShardingJdbc時(shí)候的一些思考,并最終給讀者呈現(xiàn)業(yè)務(wù)系統(tǒng)集成ShardingJdbc的最終設(shè)計(jì)方案。另外,本文僅為使用開(kāi)源組件ShardingJdbc第一篇幅,后續(xù)篇幅還將繼續(xù)介紹開(kāi)源組件ShardingJdbc的一些其他進(jìn)階用法。
數(shù)據(jù)的切分方式
一般,線上系統(tǒng)的業(yè)務(wù)量不是很大,比如說(shuō)單庫(kù)的數(shù)據(jù)量在百萬(wàn)級(jí)別以下,那么MySQL的單庫(kù)即可完成任何增/刪/改/查的業(yè)務(wù)操作。隨著業(yè)務(wù)的發(fā)展,單個(gè)DB中保存的數(shù)據(jù)量(用戶、訂單、計(jì)費(fèi)明細(xì)和權(quán)限規(guī)則等數(shù)據(jù))呈現(xiàn)指數(shù)級(jí)增長(zhǎng),那么各種業(yè)務(wù)處理操作都會(huì)面臨單DB的IO讀寫(xiě)瓶頸帶來(lái)的性能問(wèn)題。
垂直切分方案
這時(shí)候,我們會(huì)考慮使用對(duì)之前的整個(gè)單DB采用垂直數(shù)據(jù)切分的方案,根據(jù)不同的業(yè)務(wù)類(lèi)型劃分庫(kù)表,比如訂單相關(guān)若干表放在訂單庫(kù),用戶相關(guān)的表放在用戶庫(kù),賬務(wù)明細(xì)相關(guān)的表放在賬務(wù)庫(kù)等。這樣就將數(shù)據(jù)分擔(dān)到了不同的庫(kù)上,達(dá)到專(zhuān)庫(kù)專(zhuān)用的效果。
使用垂直切分方案的主要優(yōu)點(diǎn)如下:
a.拆分后業(yè)務(wù)清晰,符合微服務(wù)的總體設(shè)計(jì)理念;
b.子系統(tǒng)之間的整合與擴(kuò)展相對(duì)容易;
c.按照不同的業(yè)務(wù)類(lèi)型,將不同的庫(kù)表放在不同的數(shù)據(jù)庫(kù)服務(wù)器上,便于管理;
d.根據(jù)業(yè)務(wù)數(shù)據(jù)的“冷”、“熱”狀態(tài),采用不同的緩存和數(shù)據(jù)庫(kù)模式設(shè)計(jì)方案;
垂直切分的缺點(diǎn)如下:
a.跨庫(kù)的Join查詢,需要使用不同子系統(tǒng)的API接口讀取后在內(nèi)存中完成關(guān)聯(lián)查詢,提高系統(tǒng)復(fù)雜度;
b.如果某一種類(lèi)型的業(yè)務(wù)呈現(xiàn)爆發(fā)式地增長(zhǎng),該業(yè)務(wù)對(duì)應(yīng)的庫(kù)表還是會(huì)存在單DB的IO讀寫(xiě)的瓶頸問(wèn)題,從這一點(diǎn)來(lái)說(shuō)垂直切分并未從根本解決單庫(kù)單表數(shù)據(jù)量過(guò)大的問(wèn)題;
c.存在跨庫(kù)事務(wù)的一致性問(wèn)題,解決起來(lái)比較麻煩,當(dāng)然也可以采用分布式事務(wù)來(lái)解決;
水平切分方案
由于本文主題講的是利用開(kāi)源組件ShardingJdbc進(jìn)行數(shù)據(jù)水平切分的實(shí)踐,因此對(duì)于垂直切分方案的一些細(xì)節(jié)問(wèn)題就不做過(guò)多的詳細(xì)介紹了。與垂直切分對(duì)比,這里講的水平切分不是將庫(kù)表根據(jù)業(yè)務(wù)類(lèi)型進(jìn)行分類(lèi)存儲(chǔ),而是將其按照數(shù)據(jù)表中某個(gè)字段或某幾個(gè)字段的某種規(guī)則切分存儲(chǔ)至多個(gè)DB中,在每個(gè)庫(kù)每個(gè)表中所包含的只是其中的一部分?jǐn)?shù)據(jù),所有庫(kù)表加起來(lái)的才是全量的業(yè)務(wù)數(shù)據(jù)。
這種對(duì)數(shù)據(jù)的切分方式,基本可以保證經(jīng)過(guò)水平切分后的單庫(kù)單表存儲(chǔ)的容量不會(huì)太大,從而保證了對(duì)單表的增/刪/改/查的SQL執(zhí)行效率和處理能力。其中,數(shù)據(jù)的分片規(guī)則也是需要重點(diǎn)考慮的,因?yàn)樗鼤?huì)使得數(shù)據(jù)分片在多個(gè)庫(kù)表中是否均勻分布存儲(chǔ)。然而,數(shù)據(jù)水平切分方案為業(yè)務(wù)系統(tǒng)帶來(lái)福音的同時(shí)也給系統(tǒng)構(gòu)建帶來(lái)了一些值得思考的問(wèn)題。
使用水平切分方案的主要優(yōu)點(diǎn)如下:
a.單庫(kù)單表的數(shù)據(jù)容量可以維持在一個(gè)量級(jí),有助于提高業(yè)務(wù)SQL的執(zhí)行效率和系統(tǒng)性能;
b.不管是利用ShardingJdbc組件還是MyCat框架,業(yè)務(wù)系統(tǒng)的應(yīng)用層涉及改造得都較少,只需要根據(jù)實(shí)際的業(yè)務(wù)情況來(lái)設(shè)計(jì)數(shù)據(jù)分片的路由規(guī)則即可;
c.可以提高業(yè)務(wù)系統(tǒng)的穩(wěn)定性和負(fù)載能力;
使用水平切分方案的主要缺點(diǎn)如下:
a.數(shù)據(jù)水平切分后,分布在多庫(kù)多表中,跨庫(kù)Join查詢比較復(fù)雜;
b.分片數(shù)據(jù)的一致性較為難保證;
c.對(duì)于歷史數(shù)據(jù)的遷移和數(shù)據(jù)庫(kù)的擴(kuò)容需要較大的維護(hù)工作量;
選型ShardingJdbc組件的分析
對(duì)于,“流水”/“明細(xì)”一類(lèi)的業(yè)務(wù)數(shù)據(jù),通常的業(yè)務(wù)需求是準(zhǔn)實(shí)時(shí)或者說(shuō)相對(duì)滯后,這些數(shù)據(jù)是按小時(shí)、按日和按月匯總加工處理后生成最終業(yè)務(wù)需求的數(shù)據(jù)(比如用戶賬單、報(bào)表和話單)。此外,由于這一類(lèi)型的業(yè)務(wù)數(shù)據(jù)量普遍較大,比如清算系統(tǒng)的清分明細(xì)、云管平臺(tái)的資源計(jì)量明細(xì)、訂單系統(tǒng)的訂單流水和云計(jì)算主機(jī)資源上報(bào)的性能數(shù)據(jù)等,如果只是使用單庫(kù)單表存儲(chǔ)的普通方案,那么在單表數(shù)據(jù)量達(dá)到千萬(wàn)級(jí)別以上的時(shí)候,單庫(kù)的IO讀寫(xiě)能力將持續(xù)降低,會(huì)成為業(yè)務(wù)系統(tǒng)整體吞吐量和性能的瓶頸。
對(duì)于上述的問(wèn)題,有一些對(duì)DB較為熟悉的同學(xué)第一時(shí)間想到的解決方案,可能會(huì)是MySQL的分區(qū)表。MySQL的分區(qū)表比較適合用于解決業(yè)務(wù)數(shù)據(jù)具有較強(qiáng)時(shí)間序列特點(diǎn),且數(shù)據(jù)量偏大的場(chǎng)景。但是,如果SQL的查詢條件并非基于時(shí)間維度的,那么執(zhí)行起來(lái)的效率并不會(huì)有所改觀,同時(shí)對(duì)于處理單表千萬(wàn)級(jí)別以上數(shù)據(jù)量時(shí),MySQL的分區(qū)表方案還是會(huì)顯得有些“力不從心”。
對(duì)于以上這種“流水”/“明細(xì)”類(lèi)的業(yè)務(wù)數(shù)據(jù),作者還是推薦使用水平切分的方案從根本上來(lái)解決業(yè)務(wù)上遇到的單表數(shù)據(jù)過(guò)大的問(wèn)題。由于該類(lèi)型的業(yè)務(wù)數(shù)據(jù)基本不會(huì)涉及跨庫(kù)的Join連表SQL查詢、只需保證分庫(kù)的本地事務(wù),且并不會(huì)遇到上面水平切分方案中的幾個(gè)需要考慮的問(wèn)題,對(duì)于這樣子的業(yè)務(wù)場(chǎng)景可以考慮使用水平切分的方案。那么,我們應(yīng)該選擇哪一款開(kāi)源的分庫(kù)分表組件,又或者是根據(jù)業(yè)務(wù)情況來(lái)自己定制化研發(fā)呢?
選型分庫(kù)分表中間件的分析
如果業(yè)務(wù)系統(tǒng)設(shè)計(jì)之初打算采用水平切分方案的話,那么有必要好好思考下如何來(lái)選型分庫(kù)分表的中間件。在當(dāng)前業(yè)界開(kāi)源組件中比較流行的兩款代表分別為ShardingJdbc和MyCat。
ShardingJdbc這款分庫(kù)分表組件代表了客戶端類(lèi)的分庫(kù)分表技術(shù)框架,其定位為輕量級(jí)數(shù)據(jù)庫(kù)驅(qū)動(dòng),可以由Spring Boot這樣的業(yè)務(wù)工程直接快速集成,以jar包形式提供服務(wù),無(wú)需額外部署和其他依賴。對(duì)于業(yè)務(wù)系統(tǒng)的開(kāi)發(fā)人員與數(shù)據(jù)庫(kù)運(yùn)維人員無(wú)需改變?cè)械拈_(kāi)發(fā)與運(yùn)維方式。在2.X版本中,采用"半理解"理念的SQL解析引擎,以達(dá)到性能與兼容性的最大平衡。因此,ShardingJdbc即為增強(qiáng)版的JDBC驅(qū)動(dòng),其優(yōu)勢(shì)在于無(wú)需對(duì)原有的業(yè)務(wù)工程進(jìn)行任何改造的基礎(chǔ)上,即可使其擁有分庫(kù)分表的能力,成本較低,易于上手。但是,也有缺點(diǎn),中間件與業(yè)務(wù)應(yīng)用工程綁定在一起,對(duì)應(yīng)用本身有侵入。并且目前只支持Java語(yǔ)言,問(wèn)題難以追蹤。
而MyCat是一個(gè)開(kāi)源的分布式數(shù)據(jù)庫(kù)系統(tǒng)(屬于代理類(lèi)框架),相當(dāng)于一個(gè)實(shí)現(xiàn)了MySQL協(xié)議的數(shù)據(jù)庫(kù)代理服務(wù)器。用戶可以使用MySQL客戶端工具和命令行訪問(wèn),而其后端使用MySQL原生協(xié)議與多個(gè)MySQL數(shù)據(jù)庫(kù)服務(wù)器通信,也可以用JDBC協(xié)議與大多數(shù)主流數(shù)據(jù)庫(kù)服務(wù)器通信。其分庫(kù)分表的方案優(yōu)點(diǎn)在于,能夠處理非常復(fù)雜的需求,不受數(shù)據(jù)庫(kù)訪問(wèn)層原來(lái)實(shí)現(xiàn)的限制,擴(kuò)展性強(qiáng)且對(duì)于應(yīng)用服務(wù)透明且沒(méi)有增加任何額外負(fù)載。其缺點(diǎn)是上線部署需要額外的中間件,增加運(yùn)維成本;應(yīng)用服務(wù)需經(jīng)過(guò)代理來(lái)連接數(shù)據(jù)庫(kù),網(wǎng)絡(luò)上多了一層鏈接的開(kāi)銷(xiāo),性能有損失且有額外風(fēng)險(xiǎn)。
使用ShardingJdbc解決的基本業(yè)務(wù)場(chǎng)景
選擇ShardingJdbc組件后,就需要使用該組件來(lái)解決實(shí)際的問(wèn)題。這一節(jié)主要根據(jù)之前提到的“流水”/“明細(xì)”一類(lèi)的業(yè)務(wù)數(shù)據(jù),同時(shí)結(jié)合ShardingJdbc組件的特點(diǎn)來(lái)進(jìn)行一定的分析,使得讀者對(duì)正確使用ShardingJdbc組件進(jìn)行業(yè)務(wù)系統(tǒng)水平切分有一定的了解。
前面已經(jīng)提到了“流水”/ “明細(xì)”類(lèi)的業(yè)務(wù)數(shù)據(jù),一般是準(zhǔn)實(shí)時(shí)或者說(shuō)相對(duì)滯后,需要按小時(shí)、按日和按月匯總處理后生成最終的業(yè)務(wù)數(shù)據(jù)(如賬單、報(bào)表和話單等)。我們對(duì)“流水”/“明細(xì)型”業(yè)務(wù)數(shù)據(jù)處理過(guò)程中,一般都會(huì)涉及數(shù)據(jù)落庫(kù)(Insert SQL)、數(shù)據(jù)分組匯總和分組查詢(Select+sum(xxx)+Group By SQL)以及刪除數(shù)據(jù)表(Delete SQL)等業(yè)務(wù)處理操作。這里有必要對(duì)這幾種類(lèi)型的SQL進(jìn)行一定的說(shuō)明:
a. 數(shù)據(jù)落庫(kù)(Insert SQL):流水/明細(xì)類(lèi)的數(shù)據(jù)一般都需要先DB持久化處理;以云計(jì)算平臺(tái)中10W臺(tái)虛擬機(jī)同時(shí)產(chǎn)生性能明細(xì)數(shù)據(jù)上報(bào),如果是單庫(kù)單表,則這個(gè)數(shù)據(jù)庫(kù)會(huì)進(jìn)行每小時(shí)會(huì)有10W次的寫(xiě)請(qǐng)求;如果使用100個(gè)分表(分10個(gè)庫(kù),每庫(kù)10個(gè)表)則每個(gè)表平均承受1000次寫(xiě)請(qǐng)求,每個(gè)庫(kù)平均分擔(dān)1W次的請(qǐng)求壓力,這樣子就可以將原來(lái)單庫(kù)單表的寫(xiě)請(qǐng)求壓力成倍的減少;一般業(yè)務(wù)研發(fā)人員使用ShardingJdbc組件,設(shè)置合理的數(shù)據(jù)分片路由規(guī)則,即可使得流水/明細(xì)類(lèi)的數(shù)據(jù)基本均勻落在我們預(yù)先設(shè)置的分庫(kù)分表中。
b. 數(shù)據(jù)分組匯總查詢(Select+sum(xxx)+Group By SQL):由于(a)中持久化至分庫(kù)分表的業(yè)務(wù)數(shù)據(jù)為若干段時(shí)間的業(yè)務(wù)數(shù)據(jù),根據(jù)業(yè)務(wù)需求還需要按日,按周或者按月進(jìn)行累加匯總,因此有必要對(duì)各個(gè)分表中的數(shù)據(jù)執(zhí)行Select+sum(xxx)+Group By的分組匯總SQL;ShardingJdbc組件可以完成SQL的解析、改寫(xiě)、路由和結(jié)果歸并,對(duì)于“Select+sum(xxx)+Group By SQL”的語(yǔ)句,可以遍歷設(shè)置的多個(gè)分庫(kù)分表,對(duì)每個(gè)分庫(kù)分表執(zhí)行SQL后進(jìn)行一個(gè)結(jié)果歸并再返回給業(yè)務(wù)調(diào)用方。
c. 刪除數(shù)據(jù)表(Delete SQL):一般業(yè)務(wù)系統(tǒng)對(duì)會(huì)通過(guò)定時(shí)任務(wù)來(lái)生成明細(xì)數(shù)據(jù)加工處理后的業(yè)務(wù)數(shù)據(jù)(比如用戶賬單、清償明細(xì)、云資源按日按月的話單)。一旦生成這些有效業(yè)務(wù)數(shù)據(jù)后,原來(lái)落庫(kù)的明細(xì)也就沒(méi)有什么業(yè)務(wù)價(jià)值,可以通過(guò)任務(wù)定期刪除或者遷移至歷史庫(kù)的方式來(lái)使得分庫(kù)分表的數(shù)據(jù)水位量級(jí)維持在一定量,因此就需要涉及對(duì)原來(lái)存儲(chǔ)在分庫(kù)分表的明細(xì)數(shù)據(jù)進(jìn)行刪除;ShardingJdbc組件可以根據(jù)“Delete SQL”語(yǔ)句中的篩選條件進(jìn)行規(guī)則路由來(lái)定位某個(gè)分庫(kù)和分表,否則會(huì)刪除所有分庫(kù)中的分表數(shù)據(jù)。
系統(tǒng)集成ShardingJdbc的設(shè)計(jì)
根據(jù)上面對(duì)“流水”/“明細(xì)”型業(yè)務(wù)數(shù)據(jù)的場(chǎng)景分析,基本可以畫(huà)出SpringBoot業(yè)務(wù)工程集成分庫(kù)分表組件ShardingJdbc的架構(gòu)設(shè)計(jì)圖:
從上面業(yè)務(wù)系統(tǒng)架構(gòu)設(shè)計(jì)圖中可以看出,明細(xì)型業(yè)務(wù)數(shù)據(jù)(比如之前提的,清算系統(tǒng)的清分明細(xì)、云管平臺(tái)的資源計(jì)量明細(xì)、訂單系統(tǒng)的訂單流水和云計(jì)算主機(jī)資源上報(bào)的性能數(shù)據(jù)明細(xì))根據(jù)設(shè)置的數(shù)據(jù)分片路由規(guī)則先落庫(kù)至shardingdb00~ shardingdb04五個(gè)分庫(kù)的對(duì)應(yīng)分表中。然后,利用ShardingJdbc組件對(duì)分組匯總查詢SQL的解析、改寫(xiě)、路由和歸并結(jié)果的能力,分別對(duì)五個(gè)庫(kù)中對(duì)應(yīng)業(yè)務(wù)分表中的數(shù)據(jù)匯總累加求出每天/每月同一個(gè)用戶下的資源計(jì)費(fèi)累加值。最后,將這些“加工”后的業(yè)務(wù)數(shù)據(jù)批量插入至共享庫(kù)share_db中,其他定時(shí)任務(wù)再?gòu)墓蚕韼?kù)中讀取并生成最終形式的業(yè)務(wù)數(shù)據(jù)(比如,按月的賬單、話單或者性能計(jì)量值)。其中,對(duì)于異常情況(明細(xì)流水異常、匯總異常和系統(tǒng)異常等),需要將其保存至共享庫(kù)中的異常信息表中。另外,在明細(xì)落庫(kù)之前還需要考慮冪等前置校驗(yàn)的問(wèn)題。對(duì)于ShardingJdbc組件的分庫(kù)分表路由規(guī)則可以參照下圖:
從上面的分庫(kù)分表路由規(guī)則圖上可以看出,預(yù)先設(shè)置了通過(guò)客戶id來(lái)路由定位至分庫(kù),通過(guò)用戶id來(lái)路由定位至分表。這里,由原來(lái)的單庫(kù)單表分成五個(gè)庫(kù)的多分表(每個(gè)庫(kù)中均有testmsgqueuebillrecord0~testmsgqueuebillrecord4五個(gè)分表,這里只是示例,在真實(shí)的業(yè)務(wù)場(chǎng)景下需要根據(jù)業(yè)務(wù)數(shù)據(jù)的總體容量來(lái)設(shè)定分庫(kù)分表的規(guī)模,究竟是分5個(gè)庫(kù)每個(gè)庫(kù)5表,還是分10個(gè)庫(kù)每個(gè)庫(kù)10表來(lái)滿足業(yè)務(wù)要求)。在一般情況下,如果執(zhí)行的SQL為“select * from testmsgqueuebillrecord”就能借助ShardingJdbc組件來(lái)遍歷查完5個(gè)分庫(kù)中的testmsgqueuebillrecord0~4五個(gè)分表,無(wú)需我們自己來(lái)做分庫(kù)分表的遍歷查詢了。
總結(jié)
本文先介紹了目前兩種數(shù)據(jù)切分的主要方案(垂直切分和水平切分)及其優(yōu)缺點(diǎn)。根據(jù)“流水”/“明細(xì)”類(lèi)別的數(shù)據(jù)切分業(yè)務(wù)場(chǎng)景,闡述了業(yè)務(wù)系統(tǒng)設(shè)計(jì)之初選型分庫(kù)分表組件的分析,并介紹了如何利用ShardingJdbc來(lái)解決“數(shù)據(jù)落庫(kù)(Insert SQL)”、“數(shù)據(jù)分組匯總查詢(Select+sum(xxx)+Group By SQL)”和“刪除數(shù)據(jù)表(Delete SQL)”的幾種基本業(yè)務(wù)場(chǎng)景。最后,給出業(yè)務(wù)系統(tǒng)集成ShardingJdbc組件后的架構(gòu)設(shè)計(jì)方案。本文僅僅使用了Sharding-Jdbc組件的核心功能來(lái)解決了一部分基本的業(yè)務(wù)場(chǎng)景問(wèn)題。ShardingJdbc組件還有其他很多高級(jí)的功能(比如讀寫(xiě)分離、最大努力送達(dá)型的柔性事務(wù)、分片路由規(guī)則動(dòng)態(tài)配置和數(shù)據(jù)庫(kù)治理和ShardingProxy等。P.S : 聽(tīng)@亮哥說(shuō),ShardingJdbc 3.0起可以完美支持Batch Insert SQL,很是期待),限于篇幅將在后續(xù)的文章中結(jié)合對(duì)應(yīng)的場(chǎng)景進(jìn)行介紹。限于筆者的才疏學(xué)淺,對(duì)本文內(nèi)容可能還有理解不到位的地方,如有闡述不合理之處還望留言一起探討。