分庫分表

http://www.jianshu.com/p/32b3e91aa22c

分庫分表的基本思想

Sharding的基本思想就要把一個數據庫切分成多個部分放到不同的數據庫(server)上,從而緩解單一數據庫的性能問題。不太嚴格的講,對於海量數據的數據庫,如果是因爲表多而數據多,這時候適合使用垂直切分,即把關係緊密(比如同一模塊)的表切分出來放在一個server上。如果表並不多,但每張表的數據非常多,這時候適合水平切分,即把表的數據按某種規則(比如按ID散列)切分到多個數據庫(server)上。當然,現實中更多是這兩種情況混雜在一起,這時候需要根據實際情況做出選擇,也可能會綜合使用垂直與水平切分,從而將原有數據庫切分成類似矩陣一樣可以無限擴充的數據庫(server)陣列。

需要特別說明的是:當同時進行垂直和水平切分時,切分策略會發生一些微妙的變化。比如:在只考慮垂直切分的時候,被劃分到一起的表之間可以保持任意的關聯關係,因此你可以按“功能模塊”劃分表格,但是一旦引入水平切分之後,表間關聯關係就會受到很大的制約,通常只能允許一個主表(以該表ID進行散列的表)和其多個次表之間保留關聯關係,也就是說:當同時進行垂直和水平切分時,在垂直方向上的切分將不再以“功能模塊”進行劃分,而是需要更加細粒度的垂直切分,而這個粒度與領域驅動設計中的“聚合”概念不謀而合,甚至可以說是完全一致,每個shard的主表正是一個聚合中的聚合根!這樣切分下來你會發現數據庫分被切分地過於分散了(shard的數量會比較多,但是shard裏的表卻不多),爲了避免管理過多的數據源,充分利用每一個數據庫服務器的資源,可以考慮將業務上相近,並且具有相近數據增長速率(主表數據量在同一數量級上)的兩個或多個shard放到同一個數據源裏,每個shard依然是獨立的,它們有各自的主表,並使用各自主表ID進行散列,不同的只是它們的散列取模(即節點數量)必需是一致的.

常用的分庫分表中間件

簡單易用的組件:

強悍重量級的中間件:

分庫分表需要解決的問題

1、事務問題

解決事務問題目前有兩種可行的方案:分佈式事務和通過應用程序與數據庫共同控制實現事務下面對兩套方案進行一個簡單的對比。

  • 方案一:使用分佈式事務
    • 優點:交由數據庫管理,簡單有效
    • 缺點:性能代價高,特別是shard越來越多時
  • 方案二:由應用程序和數據庫共同控制
    • 原理:將一個跨多個數據庫的分佈式事務分拆成多個僅處 於單個數據庫上面的小事務,並通過應用程序來總控 各個小事務。
    • 優點:性能上有優勢
    • 缺點:需要應用程序在事務控制上做靈活設計。如果使用 了spring的事務管理,改動起來會面臨一定的困難。

2、跨節點Join的問題

只要是進行切分,跨節點Join的問題是不可避免的。但是良好的設計和切分卻可以減少此類情況的發生。解決這一問題的普遍做法是分兩次查詢實現。在第一次查詢的結果集中找出關聯數據的id,根據這些id發起第二次請求得到關聯數據。

3、跨節點的count,order by,group by以及聚合函數問題

這些是一類問題,因爲它們都需要基於全部數據集合進行計算。多數的代理都不會自動處理合並工作。解決方案:與解決跨節點join問題的類似,分別在各個節點上得到結果後在應用程序端進行合併。和join不同的是每個結點的查詢可以並行執行,因此很多時候它的速度要比單一大表快很多。但如果結果集很大,對應用程序內存的消耗是一個問題。

4、數據遷移,容量規劃,擴容等問題

來自淘寶綜合業務平臺團隊,它利用對2的倍數取餘具有向前兼容的特性(如對4取餘得1的數對2取餘也是1)來分配數據,避免了行級別的數據遷移,但是依然需要進行表級別的遷移,同時對擴容規模和分表數量都有限制。總得來說,這些方案都不是十分的理想,多多少少都存在一些缺點,這也從一個側面反映出了Sharding擴容的難度。

5、事務

分佈式事務

參考: [關於分佈式事務、兩階段提交、一階段提交、Best Efforts 1PC模式和事務補償機制的研究](http://blog.csdn.net/bluishglc/article/details/7612811)
  • 優點
    1. 基於兩階段提交,最大限度地保證了跨數據庫操作的“原子性”,是分佈式系統下最嚴格的事務實現方式。
    2. 實現簡單,工作量小。由於多數應用服務器以及一些獨立的分佈式事務協調器做了大量的封裝工作,使得項目中引入分佈式事務的難度和工作量基本上可以忽略不計。
  • 缺點
    1. 系統“水平”伸縮的死敵。基於兩階段提交的分佈式事務在提交事務時需要在多個節點之間進行協調,最大限度地推後了提交事務的時間點,客觀上延長了事務的執行時間,這會導致事務在訪問共享資源時發生衝突和死鎖的概率增高,隨着數據庫節點的增多,這種趨勢會越來越嚴重,從而成爲系統在數據庫層面上水平伸縮的"枷鎖", 這是很多Sharding系統不採用分佈式事務的主要原因。

基於Best Efforts 1PC模式的事務

參考spring-data-neo4j的實現。鑑於Best Efforts 1PC模式的性能優勢,以及相對簡單的實現方式,它被大多數的sharding框架和項目採用

事務補償(冪等值)

對於那些對性能要求很高,但對一致性要求並不高的系統,往往並不苛求系統的實時一致性,只要在一個允許的時間週期內達到最終一致性即可,這使得事務補償機制成爲一種可行的方案。事務補償機制最初被提出是在“長事務”的處理中,但是對於分佈式系統確保一致性也有很好的參考意義。籠統地講,與事務在執行中發生錯誤後立即回滾的方式不同,事務補償是一種事後檢查並補救的措施,它只期望在一個容許時間週期內得到最終一致的結果就可以了。事務補償的實現與系統業務緊密相關,並沒有一種標準的處理方式。一些常見的實現方式有:對數據進行對帳檢查;基於日誌進行比對;定期同標準數據來源進行同步,等等。

6、ID問題

一旦數據庫被切分到多個物理結點上,我們將不能再依賴數據庫自身的主鍵生成機制。一方面,某個分區數據庫自生成的ID無法保證在全局上是唯一的;另一方面,應用程序在插入數據之前需要先獲得ID,以便進行SQL路由.
一些常見的主鍵生成策略

UUID

使用UUID作主鍵是最簡單的方案,但是缺點也是非常明顯的。由於UUID非常的長,除佔用大量存儲空間外,最主要的問題是在索引上,在建立索引和基於索引進行查詢時都存在性能問題。

結合數據庫維護一個Sequence表

此方案的思路也很簡單,在數據庫中建立一個Sequence表,表的結構類似於:

CREATE TABLE `SEQUENCE` (  
    `table_name` varchar(18) NOT NULL,  
    `nextid` bigint(20) NOT NULL,  
    PRIMARY KEY (`table_name`)  
) ENGINE=InnoDB

每當需要爲某個表的新紀錄生成ID時就從Sequence表中取出對應表的nextid,並將nextid的值加1後更新到數據庫中以備下次使用。此方案也較簡單,但缺點同樣明顯:由於所有插入任何都需要訪問該表,該表很容易成爲系統性能瓶頸,同時它也存在單點問題,一旦該表數據庫失效,整個應用程序將無法工作。有人提出使用Master-Slave進行主從同步,但這也只能解決單點問題,並不能解決讀寫比爲1:1的訪問壓力問題。

Twitter的分佈式自增ID算法Snowflake

在分佈式系統中,需要生成全局UID的場合還是比較多的,twitter的snowflake解決了這種需求,實現也還是很簡單的,除去配置信息,核心代碼就是毫秒級時間41位 機器ID 10位 毫秒內序列12位。

* 10---0000000000 0000000000 0000000000 0000000000 0 --- 00000 ---00000 ---000000000000

在上面的字符串中,第一位爲未使用(實際上也可作爲long的符號位),接下來的41位爲毫秒級時間,然後5位datacenter標識位,5位機器ID(並不算標識符,實際是爲線程標識),然後12位該毫秒內的當前毫秒內的計數,加起來剛好64位,爲一個Long型。

這樣的好處是,整體上按照時間自增排序,並且整個分佈式系統內不會產生ID碰撞(由datacenter和機器ID作區分),並且效率較高,經測試,snowflake每秒能夠產生26萬ID左右,完全滿足需要。

7、跨分片的排序分頁

一般來講,分頁時需要按照指定字段進行排序。當排序字段就是分片字段的時候,我們通過分片規則可以比較容易定位到指定的分片,而當排序字段非分片字段的時候,情況就會變得比較複雜了。爲了最終結果的準確性,我們需要在不同的分片節點中將數據進行排序並返回,並將不同分片返回的結果集進行彙總和再次排序,最後再返回給用戶。如下圖所示:



上面圖中所描述的只是最簡單的一種情況(取第一頁數據),看起來對性能的影響並不大。但是,如果想取出第10頁數據,情況又將變得複雜很多,如下圖所示:



有些讀者可能並不太理解,爲什麼不能像獲取第一頁數據那樣簡單處理(排序取出前10條再合併、排序)。其實並不難理解,因爲各分片節點中的數據可能是隨機的,爲了排序的準確性,必須把所有分片節點的前N頁數據都排序好後做合併,最後再進行整體的排序。很顯然,這樣的操作是比較消耗資源的,用戶越往後翻頁,系統性能將會越差。
那如何解決分庫情況下的分頁問題呢?有以下幾種辦法:

如果是在前臺應用提供分頁,則限定用戶只能看前面n頁,這個限制在業務上也是合理的,一般看後面的分頁意義不大(如果一定要看,可以要求用戶縮小範圍重新查詢)。

如果是後臺批處理任務要求分批獲取數據,則可以加大page size,比如每次獲取5000條記錄,有效減少分頁數(當然離線訪問一般走備庫,避免衝擊主庫)。

分庫設計時,一般還有配套大數據平臺彙總所有分庫的記錄,有些分頁查詢可以考慮走大數據平臺。

8、分庫策略

分庫維度確定後,如何把記錄分到各個庫裏呢?
一般有兩種方式:

  • 根據數值範圍,比如用戶Id爲1-9999的記錄分到第一個庫,10000-20000的分到第二個庫,以此類推。
  • 根據數值取模,比如用戶Id mod n,餘數爲0的記錄放到第一個庫,餘數爲1的放到第二個庫,以此類推。

優劣比較:
評價指標按照範圍分庫按照Mod分庫
庫數量前期數目比較小,可以隨用戶/業務按需增長前期即根據mode因子確定庫數量,數目一般比較大
訪問性能前期庫數量小,全庫查詢消耗資源少,單庫查詢性能略差前期庫數量大,全庫查詢消耗資源多,單庫查詢性能略好
調整庫數量比較容易,一般只需爲新用戶增加庫,老庫拆分也隻影響單個庫困難,改變mod因子導致數據在所有庫之間遷移
數據熱點新舊用戶購物頻率有差異,有數據熱點問題新舊用戶均勻到分佈到各個庫,無熱點
實踐中,爲了處理簡單,選擇mod分庫的比較多。同時二次分庫時,爲了數據遷移方便,一般是按倍數增加,比如初始4個庫,二次分裂爲8個,再16個。這樣對於某個庫的數據,一半數據移到新庫,剩餘不動,對比每次只增加一個庫,所有數據都要大規模變動。
補充下,mod分庫一般每個庫記錄數比較均勻,但也有些數據庫,存在超級Id,這些Id的記錄遠遠超過其他Id,比如在廣告場景下,某個大廣告主的廣告數可能佔總體很大比例。如果按照廣告主Id取模分庫,某些庫的記錄數會特別多,對於這些超級Id,需要提供單獨庫來存儲記錄。

9、分庫數量

分庫數量首先和單庫能處理的記錄數有關,一般來說,Mysql 單庫超過5000萬條記錄,Oracle單庫超過1億條記錄,DB壓力就很大(當然處理能力和字段數量/訪問模式/記錄長度有進一步關係)。

在滿足上述前提下,如果分庫數量少,達不到分散存儲和減輕DB性能壓力的目的;如果分庫的數量多,好處是每個庫記錄少,單庫訪問性能好,但對於跨多個庫的訪問,應用程序需要訪問多個庫,如果是併發模式,要消耗寶貴的線程資源;如果是串行模式,執行時間會急劇增加。

最後分庫數量還直接影響硬件的投入,一般每個分庫跑在單獨物理機上,多一個庫意味多一臺設備。所以具體分多少個庫,要綜合評估,一般初次分庫建議分4-8個庫。

10、路由透明

分庫從某種意義上來說,意味着DB schema改變了,必然影響應用,但這種改變和業務無關,所以要儘量保證分庫對應用代碼透明,分庫邏輯儘量在數據訪問層處理。當然完全做到這一點很困難,具體哪些應該由DAL負責,哪些由應用負責,這裏有一些建議:

對於單庫訪問,比如查詢條件指定用戶Id,則該SQL只需訪問特定庫。此時應該由DAL層自動路由到特定庫,當庫二次分裂時,也只要修改mod 因子,應用代碼不受影響。

對於簡單的多庫查詢,DAL負責彙總各個數據庫返回的記錄,此時仍對上層應用透明。

11、使用框架還是自主研發

目前市面上的分庫分表中間件相對較多,其中基於代理方式的有MySQL Proxy和Amoeba,基於Hibernate框架的是Hibernate Shards,基於jdbc的有噹噹sharding-jdbc,基於mybatis的類似maven插件式的有蘑菇街的蘑菇街TSharding,通過重寫spring的ibatis template類是Cobar Client,這些框架各有各的優勢與短板,架構師可以在深入調研之後結合項目的實際情況進行選擇,但是總的來說,我個人對於框架的選擇是持謹慎態度的。一方面多數框架缺乏成功案例的驗證,其成熟性與穩定性值得懷疑。另一方面,一些從成功商業產品開源出框架(如阿里和淘寶的一些開源項目)是否適合你的項目是需要架構師深入調研分析的。當然,最終的選擇一定是基於項目特點、團隊狀況、技術門檻和學習成本等綜合因素考量確定的。

參考文章:
Twitter-Snowflake,64位自增ID算法詳解
數據庫分庫分表(sharding)系列(五) 一種支持自由規劃無須數據遷移和修改路由代碼的Sharding擴容方案
數據庫分庫分表(sharding)系列(四) 多數據源的事務處理
數據庫分庫分表(sharding)系列(三) 關於使用框架還是自主開發以及sharding實現層面的考量
數據庫分庫分表(sharding)系列(二) 全局主鍵生成策略
數據庫分庫分表(sharding)系列(一) 拆分實施策略和示例演示
關於垂直切分Vertical Sharding的粒度
數據庫Sharding的基本思想和切分策略


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