訂單表的分庫分表方案設計(大數據)

 

 

原創文章,轉載註明出處程序員

 

 

1、兩種方案分庫分表數據庫

 

 通常業界,對訂單數據的分庫分表,筆者瞭解,有兩類思路:按照訂單號來切分、按照用戶id來切分。緩存

 

方案1、按照訂單號來作hash分散訂單數據性能優化

 

     把訂單號看做是一個字符串,作hash,分散到多個服務器去。服務器

     具體到哪一個庫、哪一個表存儲數據呢?訂單號裏面的數字來記錄着。微信

    如今的微信紅包。它的訂單分庫分表,是對訂單號進行hash計算。不是什麼取模、取整數。這樣數據是均勻分散的。併發

    而後訂單號的末尾3個數,裏面是包含了庫名稱、表名稱的。oracle

 

     若是要查詢某用戶的全部訂單呢?異步

 

     因爲是根據訂單號來分散數據的。他的訂單分散在了多個庫、多個表中。性能

 

     總不能去全部的庫,全部的表掃描吧。這樣效率很低。

 

      其實按照uid切分訂單,同樣會遇到查詢的問題。好比要查詢a訂單的信息。分庫分表的規則是按照uid,都不知道數據在哪一個庫,無從查。

 

      後續說明解決思路。

 

     通常使用方案二的比較多,一個用戶的全部訂單,都在一張表裏面,那麼作分頁展現的時候,就容易。

 

 

方案2、按照用戶id打散訂單數據。

 

 

以uid來切分數據,有兩種思路:

 

一種是,某個範圍的uid訂單到哪些庫。0到2千萬uid,對應的訂單數據到a庫、a表。2千萬到4千萬對應的訂單到b庫。

 

爲何這種方案用得比較少呢?

 

容易出現瓶頸嗎。某個範圍內的用戶,下單量比較多,那麼形成這個庫的壓力特別大。其餘庫卻沒什麼壓力。

 

 

第二種是,使用uid取模運算。第二種方案業界用得比較多。

 

一方面、處理簡單,程序上作取模運算就行了。

另外一方面、使用取模的方式,數據比較均勻分散到多個庫去了。不容易出現單個庫性能瓶頸。

 

可是很差處也有:即要擴容的時候,比較麻煩。就須要遷移數據了。

要擴容的時候,爲了減小遷移的數據量,通常擴容是以倍數的形式增長。好比原來是8個庫,擴容的時候,就要增長到16個庫,再次擴容,就增長到32個庫。這樣遷移的數據量,就小不少了。這個問題不算很大問題,畢竟一次擴容,能夠保證比較長的時間,並且使用倍數增長的方式,已經減小了數據遷移量。

 

下面筆者,分析一下按照用戶id取模的方式分庫分表。

 

按照用戶id做爲key來切分訂單數據,具體以下:

 

一、 庫名稱定位:用戶id末尾4位 Mod 32。

  Mod表示除以一個數後,取餘下的數。好比除以32後,餘下8,餘數就是8。

  代碼符號是用%表示:15%4=3。

 

二、表名稱定位:(用戶id末尾4位 Dev 32) Mod 32。

 

  Dev表示除以一個數,取結果的整數。好比獲得結果是25.6,取整就是25。

  代碼用/來表示:$get_int = floor(15/4)。15除以4,是一個小數3.75,向下取整就是3。必定是向下取整,向上取整就變成了4了。

 

 按照上面的規則:總共能夠表示多少張表呢?32個庫*每一個庫32個表=1024張表。若是想表的數量小,就把32改小一些。

 

 

上面是用計算機術語來表示, 下面用通俗的話描述。

 

一、庫名稱計算

 

用戶id的後4位數,取32的模(取模就是除以這個數後,餘多少)。餘下的數,是0-31之間。

 

這樣能夠表示從0-31之間,總共32個數字。用這個32個數字表明着32個庫名稱:order_db_0、order_db_2.........................order_db_31

 

 

二、表名稱計算

 

   最後要存儲定到哪一個表名裏面去呢?

 

  用戶id的最後4位數,除以32,取整數。將整數除以32,獲得餘數,可以表示從0-31之間32個數字,表示表名稱。

 

  表名稱相似這樣:order_tb_一、order_tb_2..........................order_tb_31。一個庫裏面,總共32個表名稱。

 

  好比用戶id:19408064,用最後4位數字8064除以32,獲得是251.9,取它的整數是251。

 

  接着將251除以32,取餘數,餘數爲27。

 

  爲了保持性能,每張表的數據量要控制。單表能夠維持在一千萬-5千萬行的數據。1024*一千萬。哇,能夠表示不少數據了。

 

 

 

3、思考優勢和缺點

 

優勢

 

訂單水平分庫分表,爲何要按照用戶id來切分呢?

 

好處:查詢指定用戶的全部訂單,避免了跨庫跨表查詢。

 

 由於,根據一個用戶的id來計算節點,用戶的id是規定不變的,那麼計算出的值永遠是固定的(x庫的x表)

  那麼保存訂單的時候,a用戶的全部訂單,都是在x庫x表裏面。須要查詢a用戶全部訂單時,就不用進行跨庫、跨表去查詢了。

 

缺點

 

 這種方式也不是沒有缺點。

 

  缺點在於:數據分散不均勻,某些表的數據量特別大,某些表的數據量很小。由於某些用戶下單量多,打個比方,1000-2000這個範圍內的用戶,下單特別多,

  而他們的id根據計算規則,都是分到了x庫x表。形成這個表的數據量大,單表的數據量撐到極限後,咋辦呢?

 

  總結一下:每種分庫分表方案也不是十全十美,都是有利有弊的。目前來講,這種使用用戶id來切分訂單數據的方案,仍是被大部分公司給使用。實際效果還不錯。程序員省事,至於數據量暴漲,之後再說呢。畢竟公司業務發展到什麼程度,不知道的,項目存活期多久,將來不肯定。先扛住再說。

 

比較好的方案是否是:又能均勻分散、又能避免單表數據量暴漲方便擴容。之前看過一篇文章介紹過使用節點來存儲分庫分表。筆者暫時沒完整的思路。

 

 

2、查詢需求的考慮

 

 

  

方案一的查詢問題

 

   方案一的狀況下,因爲是按照訂單號作分散數據到多個庫、多個表。若是須要查詢a用戶的全部訂單,咋辦?須要跨庫、跨表查詢。

   這樣效率低。不可行。

   

 

方案二的查詢問題

 

若是是按照uid來切分訂單數據,在實際應用中一些很頻繁的查詢需求像下面這樣:

 

一、後臺、前臺,每每是輸入一個訂單號,查詢這個訂單的數據。select操做

二、而後修改這個訂單的相關狀態。update操做。

 

  因爲是,按照用戶編號將訂單數據分散在各個庫、各個表中。

 

  那輸入訂單號,怎麼知道去哪一個庫、哪一個表查詢呢?不可能全部的庫、全部表都查詢一遍,效率過低,不可行。

 

 

 

 3、解決辦法:創建用戶id和訂單號的索引關係表

 

      不管是根據用戶id來切分訂單,仍是根據訂單號切分數據。總不能十全十美的。

     

      寫到這裏,發現真的沒有一種技術方案是十全十美的,看,使用用戶id來切分訂單,好處是有了,壞處也出來了。

 

      不過不要緊,早要有內心承受:不要以爲技術是天衣無縫的。針對這種狀況,想辦法去解決辦法。

 

    思路:既然是根據訂單號分散訂單數據,若是須要知道某個用戶全部的訂單。只要我能知道了a用戶的全部的訂單號,那麼就能夠根據訂單號定位到表名稱了。

    思路:既然是根據用戶id來分散訂單數據的。那麼只要知道了這個訂單號是誰的(獲得了用戶id),就能知道去哪一個庫、哪一個表查詢數據了。

   

 

      那怎麼知道是誰的呢?創建一個索引關係表,暫且叫作訂單用戶關係索引表order_user_idx。我們命名爲了保持維護性,仍是一看可以知道是幹嗎用的。

 

     存儲的數據包括兩項:訂單號、用戶編號。

 

     這樣輸入訂單號,能夠去查詢索引關係表,獲取到用戶編號。

 

     獲得了用戶編號,問題解決了。訂單信息是根據用戶編號分庫分表的,能夠直接定位到x庫x表了。

 

     當建立訂單的時候,就要把關係插入到表裏面去了。保存關係記錄時,爲了減低用戶等待時間,不須要實時,作成異步。加入到消息隊列中去操做。

    

 

 

     訂單用戶索引關係表的性能優化

 

     

     考慮到,一個用戶的下的訂單多是幾十個,也多是幾百個,隨着時間的推移,會愈來愈多。這個索引關係表,也不能使用單表存儲。

 

     因此對這個訂單用戶關係索引表,也要進行分庫分表:直接根據訂單號取模進行分庫分表。是否是感受挺麻煩了。確實麻煩。不過能解決問題就好。暫時沒想到其餘辦法了。

 

    一個訂單,在建立的時候,就已經分配好給指定用戶了。只是一個關係對應,之後也不會變化。

 

    根據這個特色。訂單用戶索引關係表,其實能夠放到內存中緩存起來應對查詢需求(數據庫那張索引關係表也要有,數據要持久化)。

 

    平時查詢的時候,走內存緩存查詢。若是查詢不到,再走數據庫查詢一下關係。這樣速度就很快了。

 

 

 

    結語:水平分表,其實折騰起來工做量挺大的,切分了後,出現新的問題,代碼查詢又得改,要提供其餘解決辦法。因此常常看到別人說,能不水平分表,儘可能不要分,業務沒達到瓶頸,先用硬件扛住,後面再考慮水平切分數據。看銀行、聯通這些有錢的企業,使用性能強勁的oracle搭配小型機服務器,單表的數據量達到十多億。小型機是專門定製的,幾十萬一臺。性能很強。分庫分表是很耗費時間、當你交易量作到上億規模的時候,那時,公司的實力應該能夠了,經濟方面有足夠的實力聘請經驗豐富的技術來重構。

 

 

 

 

 

 

思考1、b2b平臺的訂單分賣家和買家的時候,選擇什麼字段來分庫分表呢?

 

上面討論的狀況是,b2c平臺。訂單的賣家就一個,就是平臺本身。

 

b2b平臺,上面支持開店,買家和賣家都要可以登錄看到本身的訂單。

 

先來看看,分表使用買家id分庫分表和根據賣家id分庫分表,兩種辦法出現的問題

 

若是按買家id來分庫分表。有賣家的商品,會有n個用戶購買,他全部的訂單,會分散到多個庫多個表中去了,賣家查詢本身的全部訂單,跨庫、跨表掃描,性能低下。

若是按賣家id分庫分表。買家會在n個店鋪下單。訂單就會分散在多個庫、多個表中。買家查詢本身全部訂單,一樣要去全部的庫、全部的表搜索,性能低下。

 

因此,不管是按照買家id切分訂單表,仍是按照賣家id切分訂單表。兩邊都不討好。

 

淘寶的作法是拆分買家庫和賣家庫,也就是兩個庫:買家庫、賣家庫。

買家庫,按照用戶的id來分庫分表。賣家庫,按照賣家的id來分庫分表。

其實是經過數據冗餘解決的:一個訂單,在買家庫裏面有,在賣家庫裏面也存儲了一份。下訂單的時候,要寫兩份數據。先把訂單寫入買家庫裏面去,而後經過消息中間件來同步訂單數據到賣家庫裏面去。

 

買家庫的訂單a修改了後,要發異步消息,通知到賣家庫去,更改狀態。

 

 

思考二:那能夠按訂單號來分庫分表嗎?  

 

這樣分庫分表的話,用戶有10個訂單,訂單不見得都在一個庫、一個表裏面。查詢a用戶的全部訂單,就會變得麻煩了。尤爲是要進行分頁展現,分散在不一樣的表,甚至不一樣的數據庫服務器,也比較耗費性能。

 

那麼訂單號裏面,最好是要有分庫分表信息。淘寶的是在訂單號裏面添加了賣家id末2位、買家id末2位。這樣的好處是幹嗎呢?直接定位到具體的庫、具體的表去了?

 

怎麼根據這個呢。由於分庫、分表的規則,買家庫是按照賣家id末尾2位數分,賣家庫是按照賣家id末尾兩位分。

 

因此,只要從訂單號裏面拿到了這些數字信息,就知道在哪一個庫,哪一個表了。

這種辦法,與微信的紅包訂單號是相似的,末尾三位數包含了庫信息、表信息。

 

按照這樣,其實就不必使用訂單號來計算了?

若是是按照用戶id的後4位數取模分散訂單數據。那麼訂單號的生成,能夠在後面加上用戶id的後4位數。

那麼,雖然是按照用戶id來對訂單表分庫分表的。其實能夠直接根據訂單號,知道這個訂單在哪一個庫哪一個表了。

若是是b2b系統,涉及到賣家和買家。那麼能夠把賣家和買家的id後面4位都加進去。不過是否是訂單號太長了?

 

 

思考3、按照訂單的時間來分表如何?

 

一月一張表。一年一張表。用戶的全部訂單,會分散在不一樣的庫、不一樣的表中。

 

按照時間分,在切分訂單數據的時候,業界用得比較少。

 

出現以下兩個問題:

 

一、若是須要分頁查詢某個用戶的全部訂單數據,就會出現跨庫、跨表查詢。效率低。

    能夠作折中:限制只能查一個範圍內的訂單,好比一次只能查詢,一年之內或者一個月之內的訂單。

二、某個時間集中寫入數據,出現瓶頸。如一個月一張表。這個月的訂單量暴漲呢。那麼寫入新的訂單數據都會操做這張表。形成性能低下。影響整個業務系統交易。

真正好的分表方案,儘可能將寫數據分散到多個表去,達到分流效果,系統的併發能力就提升了。

相關文章
相關標籤/搜索