Sharding的基本思想就要把一個數據庫切分成多個部分放到不同的數據庫(server)上,從而緩解單一數據庫的性能問題。不太嚴格的講,對于海量數據的數據庫,如果是因為表多而數據多,這時候適合使用垂直切分,即把關系緊密(比如同一模塊)的表切分出來放在一個server上。如果表并不多,但每張表的數據非常多,這時候適合水平切分,即把表的數據按某種規則(比如按ID散列)切分到多個數據庫(server)上。當然,現實中更多是這兩種情況混雜在一起,這時候需要根據實際情況做出選擇,也可能會綜合使用垂直與水平切分,從而將原有數據庫切分成類似矩陣一樣可以無限擴充的數據庫(server)陣列。
需要特別說明的是:當同時進行垂直和水平切分時,切分策略會發生一些微妙的變化。比如:在只考慮垂直切分的時候,被劃分到一起的表之間可以保持任意的關聯關系,因此你可以按“功能模塊”劃分表格,但是一旦引入水平切分之后,表間關聯關系就會受到很大的制約,通常只能允許一個主表(以該表ID進行散列的表)和其多個次表之間保留關聯關系,也就是說:當同時進行垂直和水平切分時,在垂直方向上的切分將不再以“功能模塊”進行劃分,而是需要更加細粒度的垂直切分,而這個粒度與領域驅動設計中的“聚合”概念不謀而合,甚至可以說是完全一致,每個shard的主表正是一個聚合中的聚合根!這樣切分下來你會發現數據庫分被切分地過于分散了(shard的數量會比較多,但是shard里的表卻不多),為了避免管理過多的數據源,充分利用每一個數據庫服務器的資源,可以考慮將業務上相近,并且具有相近數據增長速率(主表數據量在同一數量級上)的兩個或多個shard放到同一個數據源里,每個shard依然是獨立的,它們有各自的主表,并使用各自主表ID進行散列,不同的只是它們的散列取模(即節點數量)必需是一致的.
1、常用的分庫分表中間件
1.1 簡單易用的組件:
-
當當sharding-jdbc
-
蘑菇街TSharding
1.2 強悍重量級的中間件:
-
sharding
-
TDDL Smart Client的方式(淘寶)
-
Atlas(Qihoo 360)
-
alibaba.cobar(是阿里巴巴(B2B)部門開發)
-
MyCAT(基于阿里開源的Cobar產品而研發)
-
Oceanus(58同城數據庫中間件)
-
OneProxy(支付寶首席架構師樓方鑫開發)
-
vitess(谷歌開發的數據庫中間件)
2、分庫分表需要解決的問題
1、事務問題
解決事務問題目前有兩種可行的方案:分布式事務和通過應用程序與數據庫共同控制實現事務下面對兩套方案進行一個簡單的對比。
方案一:使用分布式事務
-
優點: 交由數據庫管理,簡單有效
-
缺點:性能代價高,特別是shard越來越多時
方案二:由應用程序和數據庫共同控制
-
原理:將一個跨多個數據庫的分布式事務分拆成多個僅處 于單個數據庫上面的小事務,并通過應用程序來總控 各個小事務。
-
優點:性能上有優勢
-
缺點:需要應用程序在事務控制上做靈活設計。如果使用 了spring的事務管理,改動起來會面臨一定的困難。
2、跨節點Join的問題
只要是進行切分,跨節點Join的問題是不可避免的。但是良好的設計和切分卻可以減少此類情況的發生。解決這一問題的普遍做法是分兩次查詢實現。在第一次查詢的結果集中找出關聯數據的id,根據這些id發起第二次請求得到關聯數據。
3、跨節點的count,order by,group by以及聚合函數問題
這些是一類問題,因為它們都需要基于全部數據集合進行計算。多數的代理都不會自動處理合并工作。解決方案:與解決跨節點join問題的類似,分別在各個節點上得到結果后在應用程序端進行合并。和join不同的是每個結點的查詢可以并行執行,因此很多時候它的速度要比單一大表快很多。但如果結果集很大,對應用程序內存的消耗是一個問題。
4、數據遷移,容量規劃,擴容等問題
來自淘寶綜合業務平臺團隊,它利用對2的倍數取余具有向前兼容的特性(如對4取余得1的數對2取余也是1)來分配數據,避免了行級別的數據遷移,但是依然需要進行表級別的遷移,同時對擴容規模和分表數量都有限制。總得來說,這些方案都不是十分的理想,多多少少都存在一些缺點,這也從一個側面反映出了Sharding擴容的難度。
5、事務
5.1 分布式事務
-
參考:關于分布式事務、兩階段提交、一階段提交、Best Efforts 1PC模式和事務補償機制的研究
-
優點基于兩階段提交,最大限度地保證了跨數據庫操作的“原子性”,是分布式系統下最嚴格的事務實現方式。實現簡單,工作量小。由于多數應用服務器以及一些獨立的分布式事務協調器做了大量的封裝工作,使得項目中引入分布式事務的難度和工作量基本上可以忽略不計。
-
缺點系統“水平”伸縮的死敵。基于兩階段提交的分布式事務在提交事務時需要在多個節點之間進行協調,最大限度地推后了提交事務的時間點,客觀上延長了事務的執行時間,這會導致事務在訪問共享資源時發生沖突和死鎖的概率增高,隨著數據庫節點的增多,這種趨勢會越來越嚴重,從而成為系統在數據庫層面上水平伸縮的"枷鎖", 這是很多Sharding系統不采用分布式事務的主要原因。
基于Best Efforts 1PC模式的事務
參考spring-data-neo4j的實現。鑒于Best Efforts 1PC模式的性能優勢,以及相對簡單的實現方式,它被大多數的sharding框架和項目采用
5.2 事務補償(冪等值)
對于那些對性能要求很高,但對一致性要求并不高的系統,往往并不苛求系統的實時一致性,只要在一個允許的時間周期內達到最終一致性即可,這使得事務補償機制成為一種可行的方案。事務補償機制最初被提出是在“長事務”的處理中,但是對于分布式系統確保一致性也有很好的參考意義。籠統地講,與事務在執行中發生錯誤后立即回滾的方式不同,事務補償是一種事后檢查并補救的措施,它只期望在一個容許時間周期內得到最終一致的結果就可以了。事務補償的實現與系統業務緊密相關,并沒有一種標準的處理方式。一些常見的實現方式有:對數據進行對帳檢查;基于日志進行比對;定期同標準數據來源進行同步,等等。
6、ID問題
一旦數據庫被切分到多個物理結點上,我們將不能再依賴數據庫自身的主鍵生成機制。一方面,某個分區數據庫自生成的ID無法保證在全局上是唯一的;另一方面,應用程序在插入數據之前需要先獲得ID,以便進行SQL路由.
一些常見的主鍵生成策略
6.1 UUID
使用UUID作主鍵是最簡單的方案,但是缺點也是非常明顯的。由于UUID非常的長,除占用大量存儲空間外,最主要的問題是在索引上,在建立索引和基于索引進行查詢時都存在性能問題。
結合數據庫維護一個Sequence表
此方案的思路也很簡單,在數據庫中建立一個Sequence表,表的結構類似于:
1CREATETABLE`SEQUENCE`(2`table_name`varchar(18)NOTNULL,3`nextid`bigint(20)NOTNULL,4PRIMARYKEY(`table_name`)5)ENGINE=InnoDB
每當需要為某個表的新紀錄生成ID時就從Sequence表中取出對應表的nextid,并將nextid的值加1后更新到數據庫中以備下次使用。此方案也較簡單,但缺點同樣明顯:由于所有插入任何都需要訪問該表,該表很容易成為系統性能瓶頸,同時它也存在單點問題,一旦該表數據庫失效,整個應用程序將無法工作。有人提出使用Master-Slave進行主從同步,但這也只能解決單點問題,并不能解決讀寫比為1:1的訪問壓力問題。
6.2 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、分庫策略
分庫維度確定后,如何把記錄分到各個庫里呢?
8.1 兩種方式:
-
根據數值范圍,比如用戶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,這些框架各有各的優勢與短板,架構師可以在深入調研之后結合項目的實際情況進行選擇,但是總的來說,我個人對于框架的選擇是持謹慎態度的。一方面多數框架缺乏成功案例的驗證,其成熟性與穩定性值得懷疑。另一方面,一些從成功商業產品開源出框架(如阿里和淘寶的一些開源項目)是否適合你的項目是需要架構師深入調研分析的。當然,最終的選擇一定是基于項目特點、團隊狀況、技術門檻和學習成本等綜合因素考量確定的。
-
Server
+關注
關注
0文章
93瀏覽量
24078 -
TDD
+關注
關注
1文章
122瀏覽量
38230 -
MySQL
+關注
關注
1文章
829瀏覽量
26684 -
sharding
+關注
關注
0文章
5瀏覽量
7926
原文標題:分庫分表需要考慮的問題及方案
文章出處:【微信號:DBDevs,微信公眾號:數據分析與開發】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論