1. 程式人生 > >訂單表的分庫分表方案設計(大資料)

訂單表的分庫分表方案設計(大資料)

原創文章,轉載註明出處

一、兩種方案分庫分表

 一般業界,對訂單資料的分庫分表,筆者瞭解,有兩類思路:按照訂單號來切分、按照使用者id來切分。

方案一、按照訂單號來做hash分散訂單資料

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

     具體到哪個庫、哪個表儲存資料呢?訂單號裡面的數字來記錄著。

    現在的微信紅包。它的訂單分庫分表,是對訂單號進行hash計算。不是什麼取模、取整數。這樣資料是均勻分散的。

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

     如果要查詢某使用者的所有訂單呢?

     由於是根據訂單號來分散資料的。他的訂單分散在了多個庫、多個表中。

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

      其實按照uid切分訂單,一樣會遇到查詢的問題。比如要查詢a訂單的資訊。分庫分表的規則是按照uid,都不知道資料在哪個庫,無從查。

      後續說明解決思路。

     一般使用方案二的比較多,一個使用者的所有訂單,都在一張表裡面,那麼做分頁展示的時候,就容易。

方案二、按照使用者id打散訂單資料。

以uid來切分資料,有兩種思路:

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

為什麼這種方案用得比較少呢?

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

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

一方面、處理簡單,程式上做取模運算就好了。

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

但是不好處也有:即要擴容的時候,比較麻煩。就需要遷移資料了。

要擴容的時候,為了減少遷移的資料量,一般擴容是以倍數的形式增加。比如原來是8個庫,擴容的時候,就要增加到16個庫,再次擴容,就增加到32個庫。這樣遷移的資料量,就小很多了。這個問題不算很大問題,畢竟一次擴容,可以保證比較長的時間,而且使用倍數增加的方式,已經減少了資料遷移量。

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

按照使用者id作為key來切分訂單資料,具體如下:

1、 庫名稱定位:使用者id末尾4位 Mod 32。

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

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

2、表名稱定位:(使用者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改小一些。

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

1、庫名稱計算

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

這樣可以表示從0-31之間,總共32個數字。用這個32個數字代表著32個庫名稱:order_db_0、order_db_2.........................order_db_31

2、表名稱計算

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

  使用者id的最後4位數,除以32,取整數。將整數除以32,得到餘數,能夠表示從0-31之間32個數字,表示表名稱。

  表名稱類似這樣:order_tb_1、order_tb_2..........................order_tb_31。一個庫裡面,總共32個表名稱。

  比如使用者id:19408064,用最後4位數字8064除以32,得到是251.9,取它的整數是251。

  接著將251除以32,取餘數,餘數為27。

  為了保持效能,每張表的資料量要控制。單表可以維持在一千萬-5千萬行的資料。1024*一千萬。哇,可以表示很多資料了。

三、思考優點和缺點

優點

訂單水平分庫分表,為什麼要按照使用者id來切分呢?

好處:查詢指定使用者的所有訂單,避免了跨庫跨表查詢。

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

  那麼儲存訂單的時候,a使用者的所有訂單,都是在x庫x表裡面。需要查詢a使用者所有訂單時,就不用進行跨庫、跨表去查詢了。

缺點

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

  缺點在於:資料分散不均勻,某些表的資料量特別大,某些表的資料量很小。因為某些使用者下單量多,打個比方,1000-2000這個範圍內的使用者,下單特別多,

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

  總結一下:每種分庫分表方案也不是十全十美,都是有利有弊的。目前來說,這種使用使用者id來切分訂單資料的方案,還是被大部分公司給使用。實際效果還不錯。程式設計師省事,至於資料量暴漲,以後再說呢。畢竟公司業務發展到什麼程度,不知道的,專案存活期多久,未來不確定。先扛住再說。

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

二、查詢需求的考慮

方案一的查詢問題

   方案一的情況下,由於是按照訂單號做分散資料到多個庫、多個表。如果需要查詢a使用者的所有訂單,咋辦?需要跨庫、跨表查詢。

   這樣效率低。不可行。

方案二的查詢問題

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

1、後臺、前臺,往往是輸入一個訂單號,查詢這個訂單的資料。select操作

2、然後修改這個訂單的相關狀態。update操作。

  由於是,按照使用者編號將訂單資料分散在各個庫、各個表中。

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

 三、解決辦法:建立使用者id和訂單號的索引關係表

      無論是根據使用者id來切分訂單,還是根據訂單號切分資料。總不能十全十美的。

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

      不過沒關係,早要有心裡承受:不要覺得技術是完美無缺的。針對這種情況,想辦法去解決辦法。

    思路:既然是根據訂單號分散訂單資料,如果需要知道某個使用者所有的訂單。只要我能知道了a使用者的所有的訂單號,那麼就可以根據訂單號定位到表名稱了。

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

      那怎麼知道是誰的呢?建立一個索引關係表,暫且叫做訂單使用者關係索引表order_user_idx。咱們命名為了保持維護性,還是一看能夠知道是幹嘛用的。

     儲存的資料包括兩項:訂單號、使用者編號。

     這樣輸入訂單號,可以去查詢索引關係表,獲取到使用者編號。

     得到了使用者編號,問題解決了。訂單資訊是根據使用者編號分庫分表的,可以直接定位到x庫x表了。

     當建立訂單的時候,就要把關係插入到表裡面去了。儲存關係記錄時,為了減低使用者等待時間,不需要實時,做成非同步。加入到訊息佇列中去操作。

     訂單使用者索引關係表的效能優化

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

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

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

    根據這個特點。訂單使用者索引關係表,其實可以放到記憶體中快取起來應對查詢需求(資料庫那張索引關係表也要有,資料要持久化)。

    平時查詢的時候,走記憶體快取查詢。如果查詢不到,再走資料庫查詢一下關係。這樣速度就很快了。

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

思考一、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位都加進去。不過是不是訂單號太長了?

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

一月一張表。一年一張表。使用者的所有訂單,會分散在不同的庫、不同的表中。

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

出現如下兩個問題:

1、如果需要分頁查詢某個使用者的所有訂單資料,就會出現跨庫、跨表查詢。效率低。

    可以做折中:限制只能查一個範圍內的訂單,比如一次只能查詢,一年以內或者一個月以內的訂單。

2、某個時間集中寫入資料,出現瓶頸。如一個月一張表。這個月的訂單量暴漲呢。那麼寫入新的訂單資料都會操作這張表。造成效能低下。影響整個業務系統交易。

真正好的分表方案,儘量將寫資料分散到多個表去,達到分流效果,系統的併發能力就提高了。