sharding:誰都能讀懂的分庫、分表、分區

本文經過大量圖片來分析和描述分庫、分表以及數據庫分區是怎樣進行的。數據庫

1.sharding前的初始數據分佈

在本文中,我打算用高考考生相關信息做爲實驗數據。請無視表的字段是否符合現實,也請無視表的設計是否符合範式。服務器

3張表:架構

  • 考生表,存放全國全部高考考生信息,假設34個省、(直轄)市、(自治區、特別行政)區共3000W考生
  • 學科表,分文理科,共9門課程(語文、數學、英語、歷史、地理、政治、物理、化學、生物)
  • 成績表,存過全國全部考生全部學科成績,每一個學生6門成績,共1.8億條成績數據

三張表放在名爲"gaokao_db"的庫中。因此,它們的結構以下:app

這三張表的大體存儲方式以下:分佈式

這個時候數據存儲方式是單庫多表性能

2.業務分庫

業務分庫:按業務將不一樣表放進不一樣庫。每一個庫能夠放在不一樣數據庫服務器上學習

例如,在這裏將原始數據庫gaokao_db中的3個表分開放進兩個數據庫中,stu_db存放考生表,score_db存放成績表。設計

還有一張學科表放在哪呢?對於那些很小、無需進行切片的表,能夠將多個這樣的表共同放在同一個庫中,也能夠根據聯接特性將其分開放置在常與之進行聯接的庫中。在此處,學科表很小,不必單獨佔用一個庫甚至數據庫服務器,且因爲學科表只會和成績表進行聯接,因此將其放在score_db庫中。3d

業務分庫以下圖:code

stu_db和score_db能夠放在同一數據庫服務器上,也能夠放在不一樣數據庫服務器上,從而在總體上減輕系統的壓力。可是,若是這兩個庫放在不一樣服務器上,由於跨數據庫實例,將無法對stu_db和score_db中的表進行join操做。

通常來講,對於可預見的、不斷增加的數據,業務分庫可能最早進行的sharding。

3.垂直切分

垂直切分:將一個表按照字段分紅多表,每一個表存儲一部分字段。表能夠放在不一樣存儲設備上

其實,在最初設計數據庫的時候,由於是關係型數據庫,或多或少都會去遵照一些設計範式。當設計的數據庫表知足第一範式、第二範式、第三範式等等範式要求時,其實就已經進行了所謂的垂直切分

即便按照範式設計了數據庫表,但有些表是寬表,有不少可能不多使用的字段,這些字段多是按照稀疏列進行管理的,也多是大BLOB後大text字段。此外,表中的字段還能夠劃分爲"熱門字段和冷門字段",例如本文示例中,相比考生號、姓名、所屬地區使用頻繁程度,考生電話號碼可能不多使用、身份證號也不多使用,因此這兩個字段是冷門字段。

因此,當表數據量很大時,即便知足了範式要求,仍是能夠強行將表按字段切開,將熱門字段、冷門字段分開放置在不一樣庫中,這些庫能夠放在不一樣的存儲設備上,避免IO爭搶。

以下圖:

注意,垂直切分後的表,要能進行關聯,因此在此處的其它信息表中加上了考生號字段。

垂直切分實際上是更深一步的範式設計,或者反範式設計。垂直切分帶來的性能提高,主要集中在熱門數據的操做效率上,並且磁盤爭用狀況減小。但若是想要將兩個表中的數據再次聯合起來,性能將比垂直切分前差的多。

另外,有不少人將業務分庫看成垂直切分,其實這都不重要,重要的是知道各類手段是幹嗎的。不過在本文以及我後面的文章,將認爲業務分庫和垂直切分是不一樣sharding的分類。

4.水平切分

水平切分:將大表按條件切分到不一樣表中。每一個表存儲一部分知足條件的行

水平切分一般有幾種經常使用的切分方式:

  1. 直接按字段條件切分
  2. 取模後切分
  3. 按月份、季度、年份切分,或者稱之爲按範圍切分

水平切分對性能提高很是大,不只能夠避開服務器資源爭用,還減少了索引大小以及每一個庫維護的表數據量。

4.1 按字段條件進行切分

例如本文的示例中,按照考生所屬地區對考生表進行水平切分,這是按照字段條件進行切分。

以下圖,由於有34個省、市、區,因此分紅34個考生表,每一個考生表都放在地區命名的庫中。各庫可放在同一數據庫服務器,也能夠放在不一樣數據庫服務器。例如,某些省市區的考生數量少,能夠將多個這樣的庫放在同一個數據庫服務器上,而山東、江西等高考大省,由於考生數量多,能夠單獨放在同一個數據庫服務器上。

注意上述按字段條件進行水平切分時,表名不變,建立新的按地區命名的庫,將各地區的表放置在對應的庫中

一般,按照字段條件進行水平切分時,其它表也頗有可能也按這個條件進行切分,使得知足條件的表都放在同一個庫中,這樣能保證正常的join操做

例如,上面切分了考生表,還能夠切分紅績表,讓同一個地區的考生表、成績表放在同一個庫中(因此,不能將考生表、成績表進行業務分庫)。

這樣切分後,整個數據的分佈狀況以下:

4.2 按範圍進行切分

對於上面的成績表,若是在此以前已經進行了業務分庫,就沒法讓成績表、考生表同時按照地區進行水平切分。這時能夠進行範圍切分,最多見的範圍切分是按月份、季度、年份進行切分。

例如,本文示例的成績表,能夠按考生號範圍切片,可按考生號取模後切片,也可按學科類別切片。例如,按考生號範圍切片,每張表500W考生共3000W條成績數據,共切成6片。

注意按照範圍(或者取模、年份、月份、季度等)切片後,數據庫的命名。這些庫能夠放在同一個數據庫服務器上,也能夠放在不一樣數據庫服務器上。

若是對成績表按照範圍(或者取模、年份、月份、季度等)切片後,最好對考生表也按照一樣的切分方式進行切片。舉個反例很容易理解,這裏的成績表按照範圍切分了,可是考生表按照地區切分,這兩類庫的名稱之間將失去對應關係,對於數據維護來講可能會增長很大的難度。

按照這種模式的水平切分後,整個數據的分佈狀況以下(假設考生表也按範圍切片):

4.3 取模切分

取模是對數值或能轉換爲數值的字段進行取模,要切分紅幾片,就除幾。

例如,按照取模切分的方式,將本文的考生表切分紅6片。因而:

00000001 % 6 = 1   --> 放進stu_1庫
00000002 % 6 = 2   --> 放進stu_2庫
00000003 % 6 = 3   --> 放進stu_3庫
00000004 % 6 = 4   --> 放進stu_4庫
00000005 % 6 = 5   --> 放進stu_5庫
00000006 % 6 = 0   --> 放進stu_0庫
...
00000101 % 6 = 5   --> 放進stu_5庫
00000102 % 6 = 0   --> 放進stu_0庫
00000103 % 6 = 1   --> 放進stu_1庫
00000104 % 6 = 2   --> 放進stu_2庫
00000105 % 6 = 3   --> 放進stu_3庫
00000106 % 6 = 4   --> 放進stu_4庫
...

注意,取模切片後的表名仍然爲考生表,這些考生表放在對應的庫裏,這些庫能夠單獨放在一個數據庫服務器上,也能夠多個庫一塊兒放在同一個數據庫服務器上。

5.數據庫分區

數據庫分區:將大表進行分區,不一樣分區能夠放置在不一樣存儲設備上,這些分區在邏輯上組成一個大表,對客戶端透明

  1. 分區方式和水平切片是相似的,分區方式也和水平切片方式相似,如範圍切片,取模切片等
  2. 數據庫分區是數據庫自身的特性,切片則是外部強制手段控制完成的
  3. 數據庫分區沒法將分區跨庫,更不能跨數據庫服務器,但能保存在不一樣數據文件從而放置在不一樣存儲設備上
  4. 數據庫分區是數據庫的特性,數據完整性、一致性等實現起來很方便,這一切都是數據庫自身保證的

例如,對考生表按照地區進行分區。

在數據庫切片流行以前,對大表的處理方式就是劃分分區表。數據庫分區相比於切片,最大的缺點在於沒法跨庫、跨服務器,因此在某些方面的壓力獲得不緩解。

6.分庫、分錶帶來的問題

由於分庫、分表能夠將大表切分紅多個片斷,每次檢索時能夠只檢索一小個片斷,且由於這些片斷能夠分開存放在不一樣存儲設備、不一樣數據庫服務器上,它的總體性能獲得了很大的提升。可是,隨之而來很多問題。最主要集中在如下幾個方面。

1.分庫分表自己的複雜性

分庫分表的方式能夠在開發端經過代碼來實現,也能夠在app和數據庫中間使用中間件來實現(如mycat),還能夠直接使用分佈式數據庫(如TiDB、OceanBase)替代傳統關係型數據庫。不管是哪種方案,學習成本、維護成本都有一段陣痛時期。

2.分庫分表讓數據庫系統架構變得複雜

特別是添加中間件的方式,畢竟在app和數據庫中間多了一層,這一層不能出現單點故障。

3.跨節點join問題

當進行了業務分庫,或者其它切片方式將庫放置在不一樣數據庫實例上時,由於跨了實例,將沒法進行join操做。

4.擴容和數據遷移艱難

對於那些以範圍、取模方式作水平切分的大表,擴容以及擴容時的數據遷移很艱難。須要解決幾個問題:

  • 擴容到多少節點比較知足本身的指望。
  • 擴容時,哪些數據須要從舊節點清洗掉,哪些數據須要從舊節點遷移到新節點。
  • 如何實如今線遷移。

例如本來按照4個節點取模分片,如今出現了瓶頸,想要擴容成6個節點。

第一個問題,從4個節點擴容爲6個節點,在總體上大體能提高50%的性能。

第二個問題和第三個問題。看以下數據分佈狀況

擴容前         擴容後
0 % 4 = 0     0 % 6 = 0
1 % 4 = 1     1 % 6 = 1
2 % 4 = 2     2 % 6 = 2
3 % 4 = 3     3 % 6 = 3
4 % 4 = 0     4 % 6 = 4   -> 從0節點遷移到4節點
5 % 4 = 1     5 % 6 = 5   -> 從1節點遷移到5節點
6 % 4 = 2     6 % 6 = 0   -> 從2節點遷移到0節點
7 % 4 = 3     7 % 6 = 1   -> 從3節點遷移到1節點
8 % 4 = 0     8 % 6 = 2   -> 從0節點遷移到2節點
9 % 4 = 1     9 % 6 = 3   -> 從1節點遷移到3節點
10 % 4 = 2    10 % 6 = 4  -> 從2節點遷移到4節點
11 % 4 = 3    11 % 6 = 5  -> 從3節點遷移到5節點
12 % 4 = 0    12 % 6 = 0
13 % 4 = 1    13 % 6 = 1
14 % 4 = 2    14 % 6 = 2
15 % 4 = 3    15 % 6 = 3
16 % 4 = 0    16 % 6 = 4  -> 從0節點遷移到4節點

可見,每12條數據就要從舊節點遷移8條數據,並且這8掉數據仍是在各個節點之間交叉遷移。這使得數據遷移很是複雜,不是想加幾個節點就加幾個節點,讓擴容變得再也不爲所欲爲。一種比較好的解決方案是雙倍擴容,例如從4節點擴容爲8節點。

擴容前         擴容後
0 % 4 = 0     0 % 8 = 0
1 % 4 = 1     1 % 8 = 1
2 % 4 = 2     2 % 8 = 2
3 % 4 = 3     3 % 8 = 3
4 % 4 = 0     4 % 8 = 4  -> 從0節點遷移到4節點
5 % 4 = 1     5 % 8 = 5  -> 從1節點遷移到5節點
6 % 4 = 2     6 % 8 = 6  -> 從2節點遷移到6節點
7 % 4 = 3     7 % 8 = 7  -> 從3節點遷移到7節點
8 % 4 = 0     8 % 8 = 0
9 % 4 = 1     9 % 8 = 1
10 % 4 = 2    10 % 8 = 2
11 % 4 = 3    11 % 8 = 3
12 % 4 = 0    12 % 8 = 4  -> 從0節點遷移到4節點
13 % 4 = 1    13 % 8 = 5  -> 從1節點遷移到5節點
14 % 4 = 2    14 % 8 = 6  -> 從2節點遷移到6節點
15 % 4 = 3    15 % 8 = 7  -> 從3節點遷移到7節點
16 % 4 = 0    16 % 8 = 0

這樣每8條數據遷移4條,且須要遷移的數據不會在各節點之間交叉。這樣遷移要方便的的,並且性能提高100%。可是由於要遷移的數據量較大,遷移速度較慢,並且每次擴容都採起雙倍擴容,必需要考慮服務器成本。

還有一種比較流行的"業務雙寫"遷移法。相比於雙倍擴容法,它仍然很複雜。它的遷移過程大概是這樣的:

  1. 加入新節點。
  2. 將業務寫入過程按照舊規則和新規則同時寫到新舊節點(業務雙寫)。例如4節點擴容到6節點時,id=2000的數據(假設以前沒有該數據)將同時寫入到0節點和2節點,id=2003將同時寫入3節點和5節點。
  3. 遷移舊數據。
  4. 應用新規則,將新節點向外提供服務。
  5. 清洗舊數據。

雙寫能保證遷移數據的過程仍然持續在線提供服務。可是,那些已存在的舊數據遷移仍然較爲複雜,須要仔細琢磨要遷移哪些數據,以及遷移到哪一個節點,這點必須把控好。

相關文章
相關標籤/搜索