【MySQL】MySQL分庫分表詳解

目錄

一、前言

1.1 數據量

1.2 磁盤

1.3 數據庫連接

二、垂直拆分 or 水平拆分?

三、垂直拆分

3.1 垂直分庫

3.2 垂直分表

3.3 垂直拆分的優缺點

四、水平拆分

4.1 水平分表

4.2 水平分庫分表

4.3 水平拆分的優缺點

五、幾種常用的分庫分表的策略

5.1 根據數值範圍

5.2 根據數值取模

5.3 根據地理位置

六、分庫分表後帶來的問題

6.1 事務一致性問題

6.1.1 分佈式事務

6.1.2 最終一致性

6.2 跨節點關聯查詢 join 問題

6.2.1 全局表

6.2.2 字段冗餘

6.2.3 數據組裝

6.2.4 ER分片

6.3 跨節點分頁、排序、函數問題

6.4 全局主鍵避重問題

6.4.1 UUID

6.4.2 結合數據庫維護主鍵ID表

6.4.3 Snowflake分佈式自增ID算法

6.5 數據遷移、擴容問題

七. 什麼時候考慮切分

7.1 能不切分儘量不要切分

7.2 數據量過大,正常運維影響業務訪問

7.3 隨着業務發展,需要對某些字段垂直拆分

7.4 數據量快速增長

7.5 安全性和可用性

八、案例分析

8.1 用戶中心業務場景

8.2 水平切分方法

8.3 非uid的查詢方法

8.3.1 建立非uid屬性到uid的映射關係

8.3.2 前臺與後臺分離

九、使用分庫分表中間件


一、前言

在互聯網還未崛起的時代,我們的傳統應用都有這樣一個特點:訪問量、數據量都比較小,單庫單表都完全可以支撐整個業務。隨着互聯網的發展和用戶規模的迅速擴大,對系統的要求也越來越高。因此傳統的MySQL單庫單表架構的性能問題就暴露出來了。而有下面幾個因素會影響數據庫性能:

1.1 數據量

MySQL單庫數據量在5000萬以內性能比較好,超過閾值後性能會隨着數據量的增大而變弱。MySQL單表數據量是500w-1000w之間性能比較好,超過1000w性能也會下降。

 

1.2 磁盤

因爲單個服務的磁盤空間是有限制的,如果併發壓力下,所有的請求都訪問同一個節點,肯定會對磁盤IO造成非常大的影響。

 

1.3 數據庫連接

數據庫連接是非常稀少的資源,如果一個庫裏既有用戶、商品、訂單相關的數據,當海量用戶同時操作時,數據庫連接就很可能成爲瓶頸。

 

爲了提升性能,所以我們必須要解決上述幾個問題,那就有必要引進分庫分表

 

二、垂直拆分 or 水平拆分?

關係型數據庫本身比較容易成爲系統瓶頸,單機存儲容量、連接數、處理能力都有限。當單表的數據量達到1000W或100G以後,由於查詢維度較多,即使添加從庫、優化索引,做很多操作時性能仍下降嚴重。此時就要考慮對其進行切分了,切分的目的就在於減少數據庫的負擔,縮短查詢時間。

數據庫分佈式核心內容無非就是數據切分(Sharding,以及切分後對數據的定位、整合。數據切分就是將數據分散存儲到多個數據庫中,使得單一數據庫中的數據量變小,通過擴充主機的數量緩解單一數據庫的性能問題,從而達到提升數據庫操作性能的目的。

數據切分根據其切分類型,可以分爲兩種方式:垂直(縱向)切分水平(橫向)切分

當我們單個庫太大時,我們先要看一下是因爲表太多還是數據量太大,如果是表太多,則應該將部分表進行遷移(可以按業務區分),這就是所謂的垂直切分。如果是數據量太大,則需要將表拆成更多的小表,來減少單表的數據量,這就是所謂的水平拆分

 

三、垂直拆分

垂直切分常見有垂直分庫垂直分表兩種。

 

3.1 垂直分庫

垂直分庫針對的是一個系統中的不同業務進行拆分,比如用戶一個庫,商品一個庫,訂單一個庫。 一個購物網站對外提供服務時,會同時對用戶、商品、訂單表進行操作。沒拆分之前, 全部都是落到單一的庫上的,這會讓數據庫的單庫處理能力成爲瓶頸。如果垂直分庫後還是將用戶、商品、訂單放到同一個服務器上,只是分到了不同的庫,這樣雖然會減少單庫的壓力,但是隨着用戶量增大,這會讓整個數據庫的處理能力成爲瓶頸,還有單個服務器的磁盤空間、內存也會受非常大的影響。 所以我們要將其拆分到多個服務器上,這樣上面的問題都解決了,以後也不會面對單機資源問題。這種做法與"微服務治理"的做法相似,每個微服務使用單獨的一個數據庫。

 

3.2 垂直分表

也就是“大表拆小表基於列字段進行的。一般是表中的字段較多,將不常用的, 數據較大,長度較長(比如text類型字段)的字段數據拆分到擴展表。一般是針對那種幾百列的大表,也避免查詢時,數據量太大造成的跨頁問題MySQL底層是通過數據頁存儲的,一條記錄佔用空間過大會導致跨頁(頁溢出),造成額外的性能開銷(IO操作變多)。另外數據庫以頁爲單位將數據加載到內存中,而頁中存儲的是行數據,頁大小固定,一行數據佔用空間越小,頁中存儲的行數據就越多。這樣表中字段長度較短且訪問頻率較高,內存能加載更多的數據,內存命中率更高,減少了磁盤IO,從而提升了數據庫性能

 

3.3 垂直拆分的優缺點

優點:

  • 解決業務系統層面的耦合,業務清晰
  • 與微服務的治理類似,也能對不同業務的數據進行分級管理、維護、監控、擴展等
  • 高併發場景下,垂直切分一定程度的提升IO、數據庫連接數、單機硬件資源的瓶頸

缺點:

  • 部分表無法join,只能通過接口聚合方式解決,提升了開發的複雜度
  • 分佈式事務處理複雜
  • 依然存在單表數據量過大的問題(需要水平切分)

 

四、水平拆分

當一個應用難以再細粒度的垂直切分,或切分後數據量行數巨大,存在單庫讀寫、存儲性能瓶頸,這時候就需要進行水平切分了。

水平切分分爲庫內分表分庫分表,是根據表內數據內在的邏輯關係,將同一個表按不同的條件分散到多個數據庫或多個表中,每個表中只包含一部分數據,從而使得單個表的數據量變小,達到分佈式的效果。如圖所示:

 

4.1 水平分表

和垂直分表有一點類似,不過垂直分表是基於列的,而水平分表是基於全表的。水平拆分可以大大減少單表數據量,提升查詢效率。這裏的水平分表指的是在一個數據庫進行的庫內分表

庫內分表只解決了單一表數據量過大的問題,但沒有將表分佈到不同機器的庫上,因此對於減輕MySQL數據庫的壓力來說,幫助不是很大,大家還是競爭同一個物理機的CPU、內存、網絡IO,最好通過分庫分表來解決。

 

4.2 水平分庫分表

將單張表的數據切分到多個服務器上去,每個服務器具有相同的庫與表,只是表中數據集合不同。 水平分庫分表能夠有效的緩解單機和單庫的性能瓶頸和壓力,突破IO、連接數、硬件資源等的瓶頸。

 

4.3 水平拆分的優缺點

優點:

  • 不存在單庫數據量過大、高併發的性能瓶頸,提升系統穩定性和負載能力
  • 應用端改造較小,不需要拆分業務模塊

缺點:

  • 跨分片的事務一致性難以保證
  • 跨庫的join關聯查詢性能較差
  • 數據多次擴展難度和維護量極大

 

五、幾種常用的分庫分表的策略

5.1 根據數值範圍

按照時間區間或ID區間來切分。例如:按日期將不同月甚至是日的數據分散到不同的庫中;將userId1~9999的記錄分到第一個庫,10000~20000的分到第二個庫,以此類推。某種意義上,某些系統中使用的"冷熱數據分離",將一些使用較少的歷史數據遷移到其他庫中,業務功能上只提供熱點數據的查詢,也是類似的實踐。

這樣的優點在於:

  • 單表大小可控
  • 天然便於水平擴展,後期如果想對整個分片集羣擴容時,只需要添加節點即可,無需對其他分片的數據進行遷移
  • 使用分片字段進行範圍查找時,連續分片可快速定位分片進行快速查詢,有效避免跨分片查詢的問題。

缺點:

  • 熱點數據成爲性能瓶頸。連續分片可能存在數據熱點,例如按時間字段分片,有些分片存儲最近時間段內的數據,可能會被頻繁的讀寫,而有些分片存儲的歷史數據,則很少被查詢

 

5.2 根據數值取模

一般採用hash取模mod的切分方式,例如:將 Customer 表根據 cusno 字段切分到4個庫中,餘數爲0的放到第一個庫,餘數爲1的放到第二個庫,以此類推。這樣同一個用戶的數據會分散到同一個庫中,如果查詢條件帶有cusno字段,則可明確定位到相應庫去查詢。再比如說有用戶表user,將其分成3個表user0,user1,user2.路由規則是對3取模,uid=1,對應到的是user1,uid=2,對應的是user2.

優點:

  • 數據分片相對比較均勻,不容易出現熱點和併發訪問的瓶頸

缺點:

  • 後期分片集羣擴容時,需要遷移舊的數據(使用一致性hash算法能較好的避免這個問題),否則會導致歷史數據失效。
  • 容易面臨跨分片查詢的複雜問題。比如上例中,如果頻繁用到的查詢條件中不帶cusno時,將會導致無法定位數據庫,從而需要同時向4個庫發起查詢,再在內存中合併數據,取最小集返回給應用,分庫反而成爲拖累。

 

5.3 根據地理位置

根據地理位置,將相同地區的放到一張表中,比如華南區一個表,華北一個表。

 

六、分庫分表後帶來的問題

分庫分表能有效的緩解單機和單庫帶來的性能瓶頸和壓力,突破網絡IO、硬件資源、連接數的瓶頸,同時也帶來了一些問題。下面將描述這些技術挑戰以及對應的解決思路。 

6.1 事務一致性問題

6.1.1 分佈式事務

當更新內容同時分佈在不同庫中,不可避免會帶來跨庫事務問題。跨分片事務也是分佈式事務,沒有簡單的方案,一般可使用"XA協議""兩階段提交"處理

分佈式事務的幾種解決方案:

  1. 使用分佈式事務中間件
  2. 使用MySQL自帶的針對跨庫的事務一致性方案(XA),不過性能要比單庫的慢10倍左右。
  3. 能否避免掉跨庫操作(比如將用戶和商品放在同一個庫中)

 

分佈式事務能最大限度保證了數據庫操作的原子性。但在提交事務時需要協調多個節點,推後了提交事務的時間點,延長了事務的執行時間。導致事務在訪問共享資源時發生衝突或死鎖的概率增高。隨着數據庫節點的增多,這種趨勢會越來越嚴重,從而成爲系統在數據庫層面上水平擴展的枷鎖。

6.1.2 最終一致性

對於那些性能要求很高,但對一致性要求不高的系統,往往不苛求系統的實時一致性,只要在允許的時間段內達到最終一致性即可,可採用事務補償的方式與事務在執行中發生錯誤後立即回滾的方式不同,事務補償是一種事後檢查補救的措施,一些常見的實現方法有:對數據進行對賬檢查,基於日誌進行對比,定期同標準數據來源進行同步等等。事務補償還要結合業務系統來考慮。

 

6.2 跨節點關聯查詢 join 問題

切分之前,系統中很多列表和詳情頁所需的數據可以通過sql join來完成。而切分之後,數據可能分佈在不同的節點上,此時join帶來的問題就比較麻煩了,考慮到性能,儘量避免使用join查詢。

解決這個問題的一些方法:

6.2.1 全局表

全局表,也可看做是"數據字典表",就是系統中所有模塊都可能依賴的一些表,爲了避免跨庫join查詢,可以將這類表在每個數據庫中都保存一份。這些數據通常很少會進行修改,所以也不擔心一致性的問題。

 

6.2.2 字段冗餘

一種典型的反範式設計,利用空間換時間,爲了性能而避免join查詢。例如:訂單表保存userId時候,也將userName冗餘保存一份,這樣查詢訂單詳情時就不需要再去查詢"買家user"了。

但這種方法適用場景也有限,比較適用於依賴字段比較少的情況。而冗餘字段的數據一致性也較難保證,就像上面訂單表的例子,買家修改了userName後,是否需要在歷史訂單中同步更新呢?這也要結合實際業務場景進行考慮。

 

6.2.3 數據組裝

在系統層面,分兩次查詢,第一次查詢的結果集中找出關聯數據id,然後根據id發起第二次請求得到關聯數據。最後將獲得到的數據進行字段拼裝。

 

6.2.4 ER分片

關係型數據庫中,如果可以先確定表之間的關聯關係,並將那些存在關聯關係的表記錄存放在同一個分片上,那麼就能較好的避免跨分片join問題。在1:11:n的情況下,通常按照主表的ID主鍵切分。如下圖所示:

這樣一來,Data Node1上面的order訂單表與orderdetail訂單詳情表就可以通過orderId進行局部的關聯查詢了,Data Node2上也一樣。

 

6.3 跨節點分頁、排序、函數問題

跨節點多庫進行查詢時,會出現limit分頁、order by排序等問題。分頁需要按照指定字段進行排序,當排序字段就是分片字段時,通過分片規則就比較容易定位到指定的分片;當排序字段非分片字段時,就變得比較複雜了。需要先在不同的分片節點中將數據進行排序並返回,然後將不同分片返回的結果集進行彙總和再次排序,最終返回給用戶。如圖所示:

上圖中只是取第一頁的數據,對性能影響還不是很大。但是如果取得頁數很大,情況則變得複雜很多,因爲各分片節點中的數據可能是隨機的,爲了排序的準確性,需要將所有節點的前N頁數據都排序好做合併,最後再進行整體的排序,這樣的操作時很耗費CPU和內存資源的,所以頁數越大,系統的性能也會越差。

在使用MaxMinSumCount之類的函數進行計算的時候,也需要先在每個分片上執行相應的函數,然後將各個分片的結果集進行彙總和再次計算,最終將結果返回。如圖所示:

總之因爲我們是將數據分散存儲到不同的庫、表裏的,當我們查詢指定數據列表時,數據來源於不同的子庫或者子表,就必然會引發結果集合並、排序的問題。如果每次查詢都需要排序、合併等操作,性能肯定會受非常大的影響。走緩存是一種解決方案。

 

6.4 全局主鍵避重問題

在分庫分表環境中,由於表中數據同時存在不同數據庫中,主鍵值平時使用的自增長將無用武之地,某個分區數據庫自生成的ID無法保證全局唯一。因此需要單獨設計全局主鍵,以避免跨庫主鍵重複問題。有一些常見的主鍵生成策略:

6.4.1 UUID

UUID標準形式包含32個16進制數字,分爲5段,形式爲8-4-4-4-12的36個字符,例如:550e8400-e29b-41d4-a716-446655440000

UUID是主鍵是最簡單的方案,本地生成,性能高,沒有網絡耗時。但缺點也很明顯,由於UUID非常長,會佔用大量的存儲空間;另外,作爲主鍵建立索引和基於索引進行查詢時都會存在性能問題,在InnoDB下,UUID的無序性會引起數據位置頻繁變動。

 

6.4.2 結合數據庫維護主鍵ID

在數據庫中建立 sequence 表:

CREATE TABLE `sequence` ( 
  `id` bigint(20) unsigned NOT NULL auto_increment, 
  `stub` char(1) NOT NULL default '', 
  PRIMARY KEY  (`id`), 
  UNIQUE KEY `stub` (`stub`) 
) ENGINE=MyISAM;

stub字段設置爲唯一索引,同一stub值在sequence表中只有一條記錄,可以同時爲多張表生成全局ID。sequence表的內容,如下所示:

+-------------------+------+  
| id                | stub | 
+-------------------+------+  
| 72157623227190423 |    a | 
+-------------------+------+ 

使用 MyISAM 存儲引擎而不是 InnoDB,以獲取更高的性能。MyISAM使用的是表級別的鎖,對錶的讀寫是串行的,所以不用擔心在併發時兩次讀取同一個ID值。

當需要全局唯一的64位ID時,執行:

REPLACE INTO sequence (stub) VALUES ('a');  
SELECT LAST_INSERT_ID(); 

這兩條語句是Connection級別的,select last_insert_id() 必須與 replace into 在同一數據庫連接下才能得到剛剛插入的新ID。

使用replace into代替insert into好處是避免了錶行數過大,不需要另外定期清理。

此方案較爲簡單,但缺點也明顯:存在單點問題,強依賴DB,當DB異常時,整個系統都不可用。配置主從可以增加可用性,但當主庫掛了,主從切換時,數據一致性在特殊情況下難以保證。另外性能瓶頸限制在單臺MySQL的讀寫性能。

flickr團隊使用的一種主鍵生成策略,與上面的sequence表方案類似,但更好的解決了單點和性能瓶頸的問題。

這一方案的整體思想是:建立2個以上的全局ID生成的服務器,每個服務器上只部署一個數據庫,每個庫有一張sequence表用於記錄當前全局ID。表中ID增長的步長是庫的數量,起始值依次錯開,這樣能將ID的生成散列到各個數據庫上。如下圖所示:

由兩個數據庫服務器生成ID,設置不同的auto_increment值。第一臺sequence的起始值爲1,每次步長增長2,另一臺的sequence起始值爲2,每次步長增長也是2。結果第一臺生成的ID都是奇數(1, 3, 5, 7 ...),第二臺生成的ID都是偶數(2, 4, 6, 8 ...)。

這種方案將生成ID的壓力均勻分佈在兩臺機器上。同時提供了系統容錯,第一臺出現了錯誤,可以自動切換到第二臺機器上獲取ID。但有以下幾個缺點:系統添加機器,水平擴展時較複雜;每次獲取ID都要讀寫一次DB,DB的壓力還是很大,只能靠堆機器來提升性能。

可以基於flickr的方案繼續優化,使用批量的方式降低數據庫的寫壓力,每次獲取一段區間的ID號段,用完之後再去數據庫獲取,可以大大減輕數據庫的壓力。如下圖所示:

還是使用兩臺DB保證可用性,數據庫中只存儲當前的最大ID。ID生成服務每次批量拉取6個ID,先將max_id修改爲5,當應用訪問ID生成服務時,就不需要訪問數據庫,從號段緩存中依次派發0~5的ID。當這些ID發完後,再將max_id修改爲11,下次就能派發6~11的ID。於是,數據庫的壓力降低爲原來的1/6。

 

6.4.3 Snowflake分佈式自增ID算法

Twitter的snowflake算法解決了分佈式系統生成全局ID的需求,生成64位的Long型數字,組成部分:

  • 第一位未使用
  • 接下來41位是毫秒級時間,41位的長度可以表示69年的時間
  • 5位datacenterId,5位workerId。10位的長度最多支持部署1024個節點
  • 最後12位是毫秒內的計數,12位的計數順序號支持每個節點每毫秒產生4096個ID序列

這樣的好處是:毫秒數在高位,生成的ID整體上按時間趨勢遞增;不依賴第三方系統,穩定性和效率較高,理論上QPS約爲409.6w/s(1000*2^12),並且整個分佈式系統內不會產生ID碰撞;可根據自身業務靈活分配bit位。

不足就在於:強依賴機器時鐘,如果時鐘回撥,則可能導致生成ID重複。

綜上

結合數據庫和snowflake的唯一ID方案,可以參考業界較爲成熟的解法:Leaf——美團點評分佈式ID生成系統,並考慮到了高可用、容災、分佈式下時鐘等問題。

 

6.5 數據遷移、擴容問題

當業務高速發展,面臨性能和存儲的瓶頸時,纔會考慮分片設計,此時就不可避免的需要考慮歷史數據遷移的問題。一般做法是先讀出歷史數據,然後按指定的分片規則再將數據寫入到各個分片節點中。此外還需要根據當前的數據量和QPS,以及業務發展的速度,進行容量規劃,推算出大概需要多少分片(一般建議單個分片上的單表數據量不超過1000W)

如果採用數值範圍分片,只需要添加節點就可以進行擴容了,不需要對分片數據遷移。如果採用的是數值取模分片,針對數據量的遞增,可能需要動態的增加表,此時因爲reHash有可能導致數據遷移問題,則考慮後期的擴容問題就相對比較麻煩

 

. 什麼時候考慮切分

下面講述一下什麼時候需要考慮做數據切分。

7.1 能不切分儘量不要切分

並不是所有表都需要進行切分,主要還是看數據的增長速度。切分後會在某種程度上提升業務的複雜度,數據庫除了承載數據的存儲和查詢外,協助業務更好的實現需求也是其重要工作之一。

不到萬不得已不用輕易使用分庫分表這個大招,避免"過度設計""過早優化"。分庫分表之前,不要爲分而分,先盡力去做力所能及的事情,例如:升級硬件、升級網絡、讀寫分離、索引優化等等。當數據量達到單表的瓶頸時候,再考慮分庫分表。

 

7.2 數據量過大,正常運維影響業務訪問

這裏說的運維,指:

  1. 對數據庫備份,如果單表太大,備份時需要大量的磁盤IO和網絡IO。例如1T的數據,網絡傳輸佔50MB時候,需要20000秒才能傳輸完畢,整個過程的風險都是比較高的
  2. 對一個很大的表進行DDL修改時,MySQL會鎖住全表,這個時間會很長,這段時間業務不能訪問此表,影響很大。如果使用pt-online-schema-change,使用過程中會創建觸發器和影子表,也需要很長的時間。在此操作過程中,都算爲風險時間。將數據表拆分,總量減少,有助於降低這個風險。
  3. 大表會經常訪問與更新,就更有可能出現鎖等待。將數據切分,用空間換時間,變相降低訪問壓力

 

7.3 隨着業務發展,需要對某些字段垂直拆分

舉個例子,假如項目一開始設計的用戶表如下:

id                   bigint             #用戶的ID
name                 varchar            #用戶的名字
last_login_time      datetime           #最近登錄時間
personal_info        text               #私人信息
.....                                   #其他信息字段

在項目初始階段,這種設計是滿足簡單的業務需求的,也方便快速迭代開發。而當業務快速發展時,用戶量從10w激增到10億,用戶非常的活躍,每次登錄會更新 last_login_name 字段,使得 user 表被不斷update,壓力很大。而其他字段:id, name, personal_info 是不變的或很少更新的,此時在業務角度,就要將 last_login_time 拆分出去,新建一個 user_time 表。

personal_info 屬性是更新和查詢頻率較低的,並且text字段佔據了太多的空間。這時候,就要對此垂直拆分出 user_ext 表了。

 

7.4 數據量快速增長

隨着業務的快速發展,單表中的數據量會持續增長,當性能接近瓶頸時,就需要考慮水平切分,做分庫分表了。此時一定要選擇合適的切分規則,提前預估好數據容量

 

7.5 安全性和可用性

雞蛋不要放在一個籃子裏。在業務層面上垂直切分,將不相關的業務的數據庫分隔,因爲每個業務的數據量、訪問量都不同,不能因爲一個業務把數據庫搞掛而牽連到其他業務。利用垂直切分,一個數據庫出現問題,只會影響到部分業務,不會使所有的業務都癱瘓。利用水平切分,當一個數據庫出現問題時,不會影響到100%的用戶,每個庫只承擔業務的一部分數據,這樣整體的可用性就能提高。

 

八、案例分析

8.1 用戶中心業務場景

用戶中心是一個非常常見的業務,主要提供用戶註冊、登錄、查詢/修改等功能,其核心表爲:

User(uid, login_name, passwd, sex, age, nickname)
uid爲用戶ID,  主鍵
login_name, passwd, sex, age, nickname,  用戶屬性

任何脫離業務的架構設計都是耍流氓,在進行分庫分表前,需要對業務場景需求進行梳理:

  • 用戶側:前臺訪問,訪問量較大,需要保證高可用和高一致性。主要有兩類需求:
    • 用戶登錄:通過login_name/phone/email查詢用戶信息,1%請求屬於這種類型
    • 用戶信息查詢:登錄之後,通過uid來查詢用戶信息,99%請求屬這種類型
  • 運營側:後臺訪問,支持運營需求,按照年齡、性別、登陸時間、註冊時間等進行分頁的查詢。是內部系統,訪問量較低,對可用性、一致性的要求不高。

 

8.2 水平切分方法

當數據量越來越大時,需要對數據庫進行水平切分,上文描述的切分方法有"根據數值範圍"和"根據數值取模"。

"根據數值範圍":以主鍵uid爲劃分依據,按uid的範圍將數據水平切分到多個數據庫上。例如:user-db1存儲uid範圍爲0~1000w的數據,user-db2存儲uid範圍爲1000w~2000wuid數據。

  • 優點是:擴容簡單,如果容量不夠,只要增加新db即可。
  • 不足是:請求量不均勻,一般新註冊的用戶活躍度會比較高,所以新的user-db2會比user-db1負載高,導致服務器利用率不平衡

 

"根據數值取模":也是以主鍵uid爲劃分依據,按uid取模的值將數據水平切分到多個數據庫上。例如:user-db1存儲uid取模得1的數據,user-db2存儲uid取模得0uid數據。

  • 優點是:數據量和請求量分佈均均勻
  • 不足是:擴容麻煩,當容量不夠時,新增加db,需要rehash。需要考慮對數據進行平滑的遷移。

 

8.3 uid的查詢方法

水平切分後,對於按uid查詢的需求能很好的滿足,可以直接路由到具體數據庫。而按非uid的查詢,例如login_name,就不知道具體該訪問哪個庫了,此時需要遍歷所有庫,性能會降低很多。

對於用戶側,可以採用"建立非uid屬性到uid的映射關係"的方案;對於運營側,可以採用"前臺與後臺分離"的方案

8.3.1 建立非uid屬性到uid的映射關係

1)映射關係

例如:login_name不能直接定位到數據庫,可以建立login_nameuid的映射關係,用索引表或緩存來存儲。當訪問login_name時,先通過映射表查詢出login_name對應的uid,再通過uid定位到具體的庫。

映射表只有兩列,可以承載很多數據,當數據量過大時,也可以對映射表再做水平切分。這類kv格式的索引結構,可以很好的使用cache來優化查詢性能,而且映射關係不會頻繁變更,緩存命中率會很高。

2)基因法

分庫基因:假如通過uid分庫,分爲8個庫,採用uid%8的方式進行路由,此時是由uid的最後3bit來決定這行User數據具體落到哪個庫上,那麼這3bit可以看爲分庫基因。

上面的映射關係的方法需要額外存儲映射表,按非uid字段查詢時,還需要多一次數據庫或cache的訪問。如果想要消除多餘的存儲和查詢,可以通過f函數取login_name的基因作爲uid的分庫基因。生成uid時,參考上文所述的分佈式唯一ID生成方案,再加上最後3位bit值=f(login_name)。當查詢login_name時,只需計算f(login_name)%8的值,就可以定位到具體的庫。不過這樣需要提前做好容量規劃,預估未來幾年的數據量需要分多少庫,要預留一定bit的分庫基因。

 

8.3.2 前臺與後臺分離

對於用戶側,主要需求是以單行查詢爲主,需要建立login_name/phone/email到uid的映射關係,可以解決這些字段的查詢問題。

而對於運營側,很多批量分頁且條件多樣的查詢,這類查詢計算量大,返回數據量大,對數據庫的性能消耗較高。此時,如果和用戶側公用同一批服務或數據庫,可能因爲後臺的少量請求,佔用大量數據庫資源,而導致用戶側訪問性能降低或超時。

這類業務最好採用"前臺與後臺分離"的方案,運營側後臺業務抽取獨立的servicedb,解決和前臺業務系統的耦合。由於運營側對可用性、一致性的要求不高,可以不訪問實時庫,而是通過binlog異步同步數據到運營庫進行訪問。在數據量很大的情況下,還可以使用ES搜索引擎或Hive來滿足後臺複雜的查詢方式。

 

九、使用分庫分表中間件

站在巨人的肩膀上能省力很多,目前分庫分表已經有一些較爲成熟的開源解決方案:

簡單介紹其中的兩個中簡介:

Mycat

Mycat發展到現在,適用的場景已經很豐富,而且不斷有新用戶給出新的創新性的方案,以下是幾個典型的應用場景:

  • 單純的讀寫分離,此時配置最爲簡單,支持讀寫分離,主從切換
  • 分庫分表,對於超過1000萬的表進行分片,最大支持1000億的單表分片
  • 多租戶應用,每個應用一個庫,但應用程序只連接Mycat,從而不改造程序本身,實現多租戶化報表系統,藉助於Mycat的分表能力,處理大規模報表的統計
  • 替代Hbase,分析大數據作爲海量數據實時查詢的一種簡單有效方案,比如100億條頻繁查詢的記錄需要在3秒內查詢出來結果,除了基於主鍵的查詢,還可能存在範圍查詢或其他屬性查詢,此時Mycat可能是最簡單有效的選擇.

Sharding-JDBC

噹噹網開發的簡單易用、輕量級的中間件。

 

一些分庫分表中間件的簡介圖:


參考資料:https://www.cnblogs.com/butterfly100/p/9034281.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章