訂單表的分庫(kù)分表方案設(shè)計(jì)(大數(shù)據(jù))
?
一、兩種方案分庫(kù)分表
?一般業(yè)界,對(duì)訂單數(shù)據(jù)的分庫(kù)分表,筆者了解,有兩類(lèi)思路:按照訂單號(hào)來(lái)切分、按照用戶id來(lái)切分。
方案一、按照訂單號(hào)來(lái)做hash分散訂單數(shù)據(jù)
? ? ?把訂單號(hào)看作是一個(gè)字符串,做hash,分散到多個(gè)服務(wù)器去。
? ? ?具體到哪個(gè)庫(kù)、哪個(gè)表存儲(chǔ)數(shù)據(jù)呢?訂單號(hào)里面的數(shù)字來(lái)記錄著。
? ? 現(xiàn)在的微信紅包。它的訂單分庫(kù)分表,是對(duì)訂單號(hào)進(jìn)行hash計(jì)算。不是什么取模、取整數(shù)。這樣數(shù)據(jù)是均勻分散的。
? ? 然后訂單號(hào)的末尾3個(gè)數(shù),里面是包含了庫(kù)名稱(chēng)、表名稱(chēng)的。
? ? ?如果要查詢某用戶的所有訂單呢?
? ? ?由于是根據(jù)訂單號(hào)來(lái)分散數(shù)據(jù)的。他的訂單分散在了多個(gè)庫(kù)、多個(gè)表中。
? ? ?總不能去所有的庫(kù),所有的表掃描吧。這樣效率很低。
? ? ? 其實(shí)按照uid切分訂單,一樣會(huì)遇到查詢的問(wèn)題。比如要查詢a訂單的信息。分庫(kù)分表的規(guī)則是按照uid,都不知道數(shù)據(jù)在哪個(gè)庫(kù),無(wú)從查。
? ? ? 后續(xù)說(shuō)明解決思路。
? ? ?一般使用方案二的比較多,一個(gè)用戶的所有訂單,都在一張表里面,那么做分頁(yè)展示的時(shí)候,就容易。
方案二、按照用戶id打散訂單數(shù)據(jù)。
?
以u(píng)id來(lái)切分?jǐn)?shù)據(jù),有兩種思路:
一種是,某個(gè)范圍的uid訂單到哪些庫(kù)。0到2千萬(wàn)uid,對(duì)應(yīng)的訂單數(shù)據(jù)到a庫(kù)、a表。2千萬(wàn)到4千萬(wàn)對(duì)應(yīng)的訂單到b庫(kù)。
為什么這種方案用得比較少呢?
容易出現(xiàn)瓶頸嗎。某個(gè)范圍內(nèi)的用戶,下單量比較多,那么造成這個(gè)庫(kù)的壓力特別大。其他庫(kù)卻沒(méi)什么壓力。
第二種是,使用uid取模運(yùn)算。第二種方案業(yè)界用得比較多。
一方面、處理簡(jiǎn)單,程序上做取模運(yùn)算就好了。
另一方面、使用取模的方式,數(shù)據(jù)比較均勻分散到多個(gè)庫(kù)去了。不容易出現(xiàn)單個(gè)庫(kù)性能瓶頸。
但是不好處也有:即要擴(kuò)容的時(shí)候,比較麻煩。就需要遷移數(shù)據(jù)了。
要擴(kuò)容的時(shí)候,為了減少遷移的數(shù)據(jù)量,一般擴(kuò)容是以倍數(shù)的形式增加。比如原來(lái)是8個(gè)庫(kù),擴(kuò)容的時(shí)候,就要增加到16個(gè)庫(kù),再次擴(kuò)容,就增加到32個(gè)庫(kù)。這樣遷移的數(shù)據(jù)量,就小很多了。這個(gè)問(wèn)題不算很大問(wèn)題,畢竟一次擴(kuò)容,可以保證比較長(zhǎng)的時(shí)間,而且使用倍數(shù)增加的方式,已經(jīng)減少了數(shù)據(jù)遷移量。
?
下面筆者,分析一下按照用戶id取模的方式分庫(kù)分表。
按照用戶id作為key來(lái)切分訂單數(shù)據(jù),具體如下:
1、 庫(kù)名稱(chēng)定位:用戶id末尾4位 Mod 32。
? Mod表示除以一個(gè)數(shù)后,取余下的數(shù)。比如除以32后,余下8,余數(shù)就是8。
? 代碼符號(hào)是用%表示:15%4=3。
2、表名稱(chēng)定位:(用戶id末尾4位 Dev 32) Mod 32。
? Dev表示除以一個(gè)數(shù),取結(jié)果的整數(shù)。比如得到結(jié)果是25.6,取整就是25。
? 代碼用/來(lái)表示:$get_int = floor(15/4)。15除以4,是一個(gè)小數(shù)3.75,向下取整就是3。一定是向下取整,向上取整就變成了4了。
?按照上面的規(guī)則:總共可以表示多少?gòu)埍砟兀?2個(gè)庫(kù)*每個(gè)庫(kù)32個(gè)表=1024張表。如果想表的數(shù)量小,就把32改小一些。
上面是用計(jì)算機(jī)術(shù)語(yǔ)來(lái)表示,?下面用通俗的話描述。
?
1、庫(kù)名稱(chēng)計(jì)算
用戶id的后4位數(shù),取32的模(取模就是除以這個(gè)數(shù)后,余多少)。余下的數(shù),是0-31之間。
這樣可以表示從0-31之間,總共32個(gè)數(shù)字。用這個(gè)32個(gè)數(shù)字代表著32個(gè)庫(kù)名稱(chēng):order_db_0、order_db_2.........................order_db_31
2、表名稱(chēng)計(jì)算
? ?最后要存儲(chǔ)定到哪個(gè)表名里面去呢?
? 用戶id的最后4位數(shù),除以32,取整數(shù)。將整數(shù)除以32,得到余數(shù),能夠表示從0-31之間32個(gè)數(shù)字,表示表名稱(chēng)。
? 表名稱(chēng)類(lèi)似這樣:order_tb_1、order_tb_2..........................order_tb_31。一個(gè)庫(kù)里面,總共32個(gè)表名稱(chēng)。
? 比如用戶id:19408064,用最后4位數(shù)字8064除以32,得到是251.9,取它的整數(shù)是251。
? 接著將251除以32,取余數(shù),余數(shù)為27。
? 為了保持性能,每張表的數(shù)據(jù)量要控制。單表可以維持在一千萬(wàn)-5千萬(wàn)行的數(shù)據(jù)。1024*一千萬(wàn)。哇,可以表示很多數(shù)據(jù)了。
三、思考優(yōu)點(diǎn)和缺點(diǎn)
優(yōu)點(diǎn)
訂單水平分庫(kù)分表,為什么要按照用戶id來(lái)切分呢?
好處:查詢指定用戶的所有訂單,避免了跨庫(kù)跨表查詢。
?因?yàn)?,根?jù)一個(gè)用戶的id來(lái)計(jì)算節(jié)點(diǎn),用戶的id是規(guī)定不變的,那么計(jì)算出的值永遠(yuǎn)是固定的(x庫(kù)的x表)
? 那么保存訂單的時(shí)候,a用戶的所有訂單,都是在x庫(kù)x表里面。需要查詢a用戶所有訂單時(shí),就不用進(jìn)行跨庫(kù)、跨表去查詢了。
缺點(diǎn)
?這種方式也不是沒(méi)有缺點(diǎn)。
? 缺點(diǎn)在于:數(shù)據(jù)分散不均勻,某些表的數(shù)據(jù)量特別大,某些表的數(shù)據(jù)量很小。因?yàn)槟承┯脩粝聠瘟慷啵騻€(gè)比方,1000-2000這個(gè)范圍內(nèi)的用戶,下單特別多,
? 而他們的id根據(jù)計(jì)算規(guī)則,都是分到了x庫(kù)x表。造成這個(gè)表的數(shù)據(jù)量大,單表的數(shù)據(jù)量撐到極限后,咋辦呢?
? 總結(jié)一下:每種分庫(kù)分表方案也不是十全十美,都是有利有弊的。目前來(lái)說(shuō),這種使用用戶id來(lái)切分訂單數(shù)據(jù)的方案,還是被大部分公司給使用。實(shí)際效果還不錯(cuò)。程序員省事,至于數(shù)據(jù)量暴漲,以后再說(shuō)呢。畢竟公司業(yè)務(wù)發(fā)展到什么程度,不知道的,項(xiàng)目存活期多久,未來(lái)不確定。先扛住再說(shuō)。
比較好的方案是不是:又能均勻分散、又能避免單表數(shù)據(jù)量暴漲方便擴(kuò)容。以前看過(guò)一篇文章介紹過(guò)使用節(jié)點(diǎn)來(lái)存儲(chǔ)分庫(kù)分表。筆者暫時(shí)沒(méi)完整的思路。
二、查詢需求的考慮
?
??
方案一的查詢問(wèn)題
? ?方案一的情況下,由于是按照訂單號(hào)做分散數(shù)據(jù)到多個(gè)庫(kù)、多個(gè)表。如果需要查詢a用戶的所有訂單,咋辦?需要跨庫(kù)、跨表查詢。
? ?這樣效率低。不可行。
方案二的查詢問(wèn)題
如果是按照uid來(lái)切分訂單數(shù)據(jù),在實(shí)際應(yīng)用中一些很頻繁的查詢需求像下面這樣:
1、后臺(tái)、前臺(tái),往往是輸入一個(gè)訂單號(hào),查詢這個(gè)訂單的數(shù)據(jù)。select操作
2、然后修改這個(gè)訂單的相關(guān)狀態(tài)。update操作。
? 由于是,按照用戶編號(hào)將訂單數(shù)據(jù)分散在各個(gè)庫(kù)、各個(gè)表中。
? 那輸入訂單號(hào),怎么知道去哪個(gè)庫(kù)、哪個(gè)表查詢呢?不可能所有的庫(kù)、所有表都查詢一遍,效率太低,不可行。
?三、解決辦法:建立用戶id和訂單號(hào)的索引關(guān)系表
? ? ? 無(wú)論是根據(jù)用戶id來(lái)切分訂單,還是根據(jù)訂單號(hào)切分?jǐn)?shù)據(jù)??偛荒苁赖?。
? ? ? 寫(xiě)到這里,發(fā)現(xiàn)真的沒(méi)有一種技術(shù)方案是十全十美的,看,使用用戶id來(lái)切分訂單,好處是有了,壞處也出來(lái)了。
? ? ? 不過(guò)沒(méi)關(guān)系,早要有心里承受:不要覺(jué)得技術(shù)是完美無(wú)缺的。針對(duì)這種情況,想辦法去解決辦法。
? ??思路:既然是根據(jù)訂單號(hào)分散訂單數(shù)據(jù),如果需要知道某個(gè)用戶所有的訂單。只要我能知道了a用戶的所有的訂單號(hào),那么就可以根據(jù)訂單號(hào)定位到表名稱(chēng)了。
? ? 思路:既然是根據(jù)用戶id來(lái)分散訂單數(shù)據(jù)的。那么只要知道了這個(gè)訂單號(hào)是誰(shuí)的(得到了用戶id),就能知道去哪個(gè)庫(kù)、哪個(gè)表查詢數(shù)據(jù)了。
? ? ? 那怎么知道是誰(shuí)的呢?建立一個(gè)索引關(guān)系表,暫且叫做訂單用戶關(guān)系索引表order_user_idx。咱們命名為了保持維護(hù)性,還是一看能夠知道是干嘛用的。
? ? ?存儲(chǔ)的數(shù)據(jù)包括兩項(xiàng):訂單號(hào)、用戶編號(hào)。
? ? ?這樣輸入訂單號(hào),可以去查詢索引關(guān)系表,獲取到用戶編號(hào)。
? ? ?得到了用戶編號(hào),問(wèn)題解決了。訂單信息是根據(jù)用戶編號(hào)分庫(kù)分表的,可以直接定位到x庫(kù)x表了。
? ? ?當(dāng)創(chuàng)建訂單的時(shí)候,就要把關(guān)系插入到表里面去了。保存關(guān)系記錄時(shí),為了減低用戶等待時(shí)間,不需要實(shí)時(shí),做成異步。加入到消息隊(duì)列中去操作。
?訂單用戶索引關(guān)系表的性能優(yōu)化
?
? ? ?
? ? ?考慮到,一個(gè)用戶的下的訂單可能是幾十個(gè),也可能是幾百個(gè),隨著時(shí)間的推移,會(huì)越來(lái)越多。這個(gè)索引關(guān)系表,也不能使用單表存儲(chǔ)。
? ? ?所以對(duì)這個(gè)訂單用戶關(guān)系索引表,也要進(jìn)行分庫(kù)分表:直接根據(jù)訂單號(hào)取模進(jìn)行分庫(kù)分表。是不是感覺(jué)挺麻煩了。確實(shí)麻煩。不過(guò)能解決問(wèn)題就好。暫時(shí)沒(méi)想到其他辦法了。
? ? 一個(gè)訂單,在創(chuàng)建的時(shí)候,就已經(jīng)分配好給指定用戶了。只是一個(gè)關(guān)系對(duì)應(yīng),以后也不會(huì)變化。
? ? 根據(jù)這個(gè)特點(diǎn)。訂單用戶索引關(guān)系表,其實(shí)可以放到內(nèi)存中緩存起來(lái)應(yīng)對(duì)查詢需求(數(shù)據(jù)庫(kù)那張索引關(guān)系表也要有,數(shù)據(jù)要持久化)。
? ? 平時(shí)查詢的時(shí)候,走內(nèi)存緩存查詢。如果查詢不到,再走數(shù)據(jù)庫(kù)查詢一下關(guān)系。這樣速度就很快了。
? ? 結(jié)語(yǔ):水平分表,其實(shí)折騰起來(lái)工作量挺大的,切分了后,出現(xiàn)新的問(wèn)題,代碼查詢又得改,要提供其他解決辦法。所以經(jīng)??吹絼e人說(shuō),能不水平分表,盡量不要分,業(yè)務(wù)沒(méi)達(dá)到瓶頸,先用硬件扛住,后面再考慮水平切分?jǐn)?shù)據(jù)??淬y行、聯(lián)通這些有錢(qián)的企業(yè),使用性能強(qiáng)勁的oracle搭配小型機(jī)服務(wù)器,單表的數(shù)據(jù)量達(dá)到十多億。小型機(jī)是專(zhuān)門(mén)定制的,幾十萬(wàn)一臺(tái)。性能很強(qiáng)。分庫(kù)分表是很耗費(fèi)時(shí)間、當(dāng)你交易量做到上億規(guī)模的時(shí)候,那時(shí),公司的實(shí)力應(yīng)該可以了,經(jīng)濟(jì)方面有足夠的實(shí)力聘請(qǐng)經(jīng)驗(yàn)豐富的技術(shù)來(lái)重構(gòu)。
思考一、b2b平臺(tái)的訂單分賣(mài)家和買(mǎi)家的時(shí)候,選擇什么字段來(lái)分庫(kù)分表呢?
上面討論的情況是,b2c平臺(tái)。訂單的賣(mài)家就一個(gè),就是平臺(tái)自己。
b2b平臺(tái),上面支持開(kāi)店,買(mǎi)家和賣(mài)家都要能夠登陸看到自己的訂單。
先來(lái)看看,分表使用買(mǎi)家id分庫(kù)分表和根據(jù)賣(mài)家id分庫(kù)分表,兩種辦法出現(xiàn)的問(wèn)題
如果按買(mǎi)家id來(lái)分庫(kù)分表。有賣(mài)家的商品,會(huì)有n個(gè)用戶購(gòu)買(mǎi),他所有的訂單,會(huì)分散到多個(gè)庫(kù)多個(gè)表中去了,賣(mài)家查詢自己的所有訂單,跨庫(kù)、跨表掃描,性能低下。
如果按賣(mài)家id分庫(kù)分表。買(mǎi)家會(huì)在n個(gè)店鋪下單。訂單就會(huì)分散在多個(gè)庫(kù)、多個(gè)表中。買(mǎi)家查詢自己所有訂單,同樣要去所有的庫(kù)、所有的表搜索,性能低下。
所以,無(wú)論是按照買(mǎi)家id切分訂單表,還是按照賣(mài)家id切分訂單表。兩邊都不討好。
淘寶的做法是拆分買(mǎi)家?guī)旌唾u(mài)家?guī)?,也就是兩個(gè)庫(kù):買(mǎi)家?guī)?、賣(mài)家?guī)臁?/p>
買(mǎi)家?guī)?,按照用戶的id來(lái)分庫(kù)分表。賣(mài)家?guī)?,按照賣(mài)家的id來(lái)分庫(kù)分表。
實(shí)際上是通過(guò)數(shù)據(jù)冗余解決的:一個(gè)訂單,在買(mǎi)家?guī)炖锩嬗校谫u(mài)家?guī)炖锩嬉泊鎯?chǔ)了一份。下訂單的時(shí)候,要寫(xiě)兩份數(shù)據(jù)。先把訂單寫(xiě)入買(mǎi)家?guī)炖锩嫒?,然后通過(guò)消息中間件來(lái)同步訂單數(shù)據(jù)到賣(mài)家?guī)炖锩嫒ァ?/p>
買(mǎi)家?guī)斓挠唵蝍修改了后,要發(fā)異步消息,通知到賣(mài)家?guī)烊ィ臓顟B(tài)。
思考二:那可以按訂單號(hào)來(lái)分庫(kù)分表嗎? ?
這樣分庫(kù)分表的話,用戶有10個(gè)訂單,訂單不見(jiàn)得都在一個(gè)庫(kù)、一個(gè)表里面。查詢a用戶的所有訂單,就會(huì)變得麻煩了。尤其是要進(jìn)行分頁(yè)展示,分散在不同的表,甚至不同的數(shù)據(jù)庫(kù)服務(wù)器,也比較耗費(fèi)性能。
那么訂單號(hào)里面,最好是要有分庫(kù)分表信息。淘寶的是在訂單號(hào)里面添加了賣(mài)家id末2位、買(mǎi)家id末2位。這樣的好處是干嘛呢?直接定位到具體的庫(kù)、具體的表去了?
怎么根據(jù)這個(gè)呢。因?yàn)榉謳?kù)、分表的規(guī)則,買(mǎi)家?guī)焓前凑召u(mài)家id末尾2位數(shù)分,賣(mài)家?guī)焓前凑召u(mài)家id末尾兩位分。
所以,只要從訂單號(hào)里面拿到了這些數(shù)字信息,就知道在哪個(gè)庫(kù),哪個(gè)表了。
這種辦法,與微信的紅包訂單號(hào)是類(lèi)似的,末尾三位數(shù)包含了庫(kù)信息、表信息。
按照這樣,其實(shí)就沒(méi)必要使用訂單號(hào)來(lái)計(jì)算了?
如果是按照用戶id的后4位數(shù)取模分散訂單數(shù)據(jù)。那么訂單號(hào)的生成,可以在后面加上用戶id的后4位數(shù)。
那么,雖然是按照用戶id來(lái)對(duì)訂單表分庫(kù)分表的。其實(shí)可以直接根據(jù)訂單號(hào),知道這個(gè)訂單在哪個(gè)庫(kù)哪個(gè)表了。
如果是b2b系統(tǒng),涉及到賣(mài)家和買(mǎi)家。那么可以把賣(mài)家和買(mǎi)家的id后面4位都加進(jìn)去。不過(guò)是不是訂單號(hào)太長(zhǎng)了?
思考三、按照訂單的時(shí)間來(lái)分表如何?
一月一張表。一年一張表。用戶的所有訂單,會(huì)分散在不同的庫(kù)、不同的表中。
按照時(shí)間分,在切分訂單數(shù)據(jù)的時(shí)候,業(yè)界用得比較少。
出現(xiàn)如下兩個(gè)問(wèn)題:
1、如果需要分頁(yè)查詢某個(gè)用戶的所有訂單數(shù)據(jù),就會(huì)出現(xiàn)跨庫(kù)、跨表查詢。效率低。
? ? 可以做折中:限制只能查一個(gè)范圍內(nèi)的訂單,比如一次只能查詢,一年以內(nèi)或者一個(gè)月以內(nèi)的訂單。
2、某個(gè)時(shí)間集中寫(xiě)入數(shù)據(jù),出現(xiàn)瓶頸。如一個(gè)月一張表。這個(gè)月的訂單量暴漲呢。那么寫(xiě)入新的訂單數(shù)據(jù)都會(huì)操作這張表。造成性能低下。影響整個(gè)業(yè)務(wù)系統(tǒng)交易。
真正好的分表方案,盡量將寫(xiě)數(shù)據(jù)分散到多個(gè)表去,達(dá)到分流效果,系統(tǒng)的并發(fā)能力就提高了。
--------------------- Created By 王滔 專(zhuān)注于互聯(lián)網(wǎng)系統(tǒng)開(kāi)發(fā) 原創(chuàng)文章,轉(zhuǎn)載注明出處, -----------------