Sharding的基本思想就要把一個數據庫切分紅多個部分放到不一樣的數據庫(server)上,從而緩解單一數據庫的性能問題。不太嚴格的講,對於海量數據的數據庫,若是是由於表多而數據多,這時候適合使用垂直切分,即把關係緊密(好比同一模塊)的表切分出來放在一個server上。若是表並很少,但每張表的數據很是多,這時候適合水平切分,即把表的數據按某種規則(好比按ID散列)切分到多個數據庫(server)上。固然,現實中更可能是這兩種狀況混雜在一塊兒,這時候須要根據實際狀況作出選擇,也可能會綜合使用垂直與水平切分,從而將原有數據庫切分紅相似矩陣同樣能夠無限擴充的數據庫(server)陣列。html
須要特別說明的是:當同時進行垂直和水平切分時,切分策略會發生一些微妙的變化。好比:在只考慮垂直切分的時候,被劃分到一塊兒的表之間能夠保持任意的關聯關係,所以你能夠按「功能模塊」劃分表格,可是一旦引入水平切分以後,表間關聯關係就會受到很大的制約,一般只能容許一個主表(以該表ID進行散列的表)和其多個次表之間保留關聯關係,也就是說:當同時進行垂直和水平切分時,在垂直方向上的切分將再也不以「功能模塊」進行劃分,而是須要更加細粒度的垂直切分,而這個粒度與領域驅動設計中的「聚合」概念不謀而合,甚至能夠說是徹底一致,每一個shard的主表正是一個聚合中的聚合根!這樣切分下來你會發現數據庫分被切分地過於分散了(shard的數量會比較多,可是shard裏的表卻很少),爲了不管理過多的數據源,充分利用每個數據庫服務器的資源,能夠考慮將業務上相近,而且具備相近數據增加速率(主表數據量在同一數量級上)的兩個或多個shard放到同一個數據源裏,每一個shard依然是獨立的,它們有各自的主表,並使用各自主表ID進行散列,不一樣的只是它們的散列取模(即節點數量)必需是一致的.java
解決事務問題目前有兩種可行的方案:分佈式事務和經過應用程序與數據庫共同控制實現事務下面對兩套方案進行一個簡單的對比。mysql
- 方案一:使用分佈式事務
- 優勢:交由數據庫管理,簡單有效
- 缺點:性能代價高,特別是shard愈來愈多時
- 方案二:由應用程序和數據庫共同控制
- 原理:將一個跨多個數據庫的分佈式事務分拆成多個僅處 於單個數據庫上面的小事務,並經過應用程序來總控 各個小事務。
- 優勢:性能上有優點
- 缺點:須要應用程序在事務控制上作靈活設計。若是使用 了spring的事務管理,改動起來會面臨必定的困難。
只要是進行切分,跨節點Join的問題是不可避免的。可是良好的設計和切分卻能夠減小此類狀況的發生。解決這一問題的廣泛作法是分兩次查詢實現。在第一次查詢的結果集中找出關聯數據的id,根據這些id發起第二次請求獲得關聯數據。git
這些是一類問題,由於它們都須要基於所有數據集合進行計算。多數的代理都不會自動處理合並工做。解決方案:與解決跨節點join問題的相似,分別在各個節點上獲得結果後在應用程序端進行合併。和join不一樣的是每一個結點的查詢能夠並行執行,所以不少時候它的速度要比單一大表快不少。但若是結果集很大,對應用程序內存的消耗是一個問題。github
來自淘寶綜合業務平臺團隊,它利用對2的倍數取餘具備向前兼容的特性(如對4取餘得1的數對2取餘也是1)來分配數據,避免了行級別的數據遷移,可是依然須要進行表級別的遷移,同時對擴容規模和分表數量都有限制。總得來講,這些方案都不是十分的理想,多多少少都存在一些缺點,這也從一個側面反映出了Sharding擴容的難度。算法
分佈式事務
參考: [關於分佈式事務、兩階段提交、一階段提交、Best Efforts 1PC模式和事務補償機制的研究](http://blog.csdn.net/bluishglc/article/details/7612811)
- 優勢
- 基於兩階段提交,最大限度地保證了跨數據庫操做的「原子性」,是分佈式系統下最嚴格的事務實現方式。
- 實現簡單,工做量小。因爲多數應用服務器以及一些獨立的分佈式事務協調器作了大量的封裝工做,使得項目中引入分佈式事務的難度和工做量基本上能夠忽略不計。
- 缺點
- 系統「水平」伸縮的死敵。基於兩階段提交的分佈式事務在提交事務時須要在多個節點之間進行協調,最大限度地推後了提交事務的時間點,客觀上延長了事務的執行時間,這會致使事務在訪問共享資源時發生衝突和死鎖的機率增高,隨着數據庫節點的增多,這種趨勢會愈來愈嚴重,從而成爲系統在數據庫層面上水平伸縮的"枷鎖", 這是不少Sharding系統不採用分佈式事務的主要緣由。
基於Best Efforts 1PC模式的事務
參考spring-data-neo4j的實現。鑑於Best Efforts 1PC模式的性能優點,以及相對簡單的實現方式,它被大多數的sharding框架和項目採用spring
事務補償(冪等值)
對於那些對性能要求很高,但對一致性要求並不高的系統,每每並不苛求系統的實時一致性,只要在一個容許的時間週期內達到最終一致性便可,這使得事務補償機制成爲一種可行的方案。事務補償機制最初被提出是在「長事務」的處理中,可是對於分佈式系統確保一致性也有很好的參考意義。籠統地講,與事務在執行中發生錯誤後當即回滾的方式不一樣,事務補償是一種過後檢查並補救的措施,它只指望在一個允許時間週期內獲得最終一致的結果就能夠了。事務補償的實現與系統業務緊密相關,並無一種標準的處理方式。一些常見的實現方式有:對數據進行對賬檢查;基於日誌進行比對;按期同標準數據來源進行同步,等等。sql
一旦數據庫被切分到多個物理結點上,咱們將不能再依賴數據庫自身的主鍵生成機制。一方面,某個分區數據庫自生成的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左右,徹底知足須要。
通常來說,分頁時須要按照指定字段進行排序。當排序字段就是分片字段的時候,咱們經過分片規則能夠比較容易定位到指定的分片,而當排序字段非分片字段的時候,狀況就會變得比較複雜了。爲了最終結果的準確性,咱們須要在不一樣的分片節點中將數據進行排序並返回,並將不一樣分片返回的結果集進行彙總和再次排序,最後再返回給用戶。以下圖所示:
上面圖中所描述的只是最簡單的一種狀況(取第一頁數據),看起來對性能的影響並不大。可是,若是想取出第10頁數據,狀況又將變得複雜不少,以下圖所示:
有些讀者可能並不太理解,爲何不能像獲取第一頁數據那樣簡單處理(排序取出前10條再合併、排序)。其實並不難理解,由於各分片節點中的數據多是隨機的,爲了排序的準確性,必須把全部分片節點的前N頁數據都排序好後作合併,最後再進行總體的排序。很顯然,這樣的操做是比較消耗資源的,用戶越日後翻頁,系統性能將會越差。
那如何解決分庫狀況下的分頁問題呢?有如下幾種辦法:若是是在前臺應用提供分頁,則限定用戶只能看前面n頁,這個限制在業務上也是合理的,通常看後面的分頁意義不大(若是必定要看,能夠要求用戶縮小範圍從新查詢)。
若是是後臺批處理任務要求分批獲取數據,則能夠加大page size,好比每次獲取5000條記錄,有效減小分頁數(固然離線訪問通常走備庫,避免衝擊主庫)。
分庫設計時,通常還有配套大數據平臺彙總全部分庫的記錄,有些分頁查詢能夠考慮走大數據平臺。
八、分庫策略
分庫維度肯定後,如何把記錄分到各個庫裏呢?
通常有兩種方式:
- 根據數值範圍,好比用戶Id爲1-9999的記錄分到第一個庫,10000-20000的分到第二個庫,以此類推。
- 根據數值取模,好比用戶Id mod n,餘數爲0的記錄放到第一個庫,餘數爲1的放到第二個庫,以此類推。
優劣比較:
評價指標按照範圍分庫按照Mod分庫
庫數量前期數目比較小,能夠隨用戶/業務按需增加前期即根據mode因子肯定庫數量,數目通常比較大
訪問性能前期庫數量小,全庫查詢消耗資源少,單庫查詢性能略差前期庫數量大,全庫查詢消耗資源多,單庫查詢性能略好
調整庫數量比較容易,通常只需爲新用戶增長庫,老庫拆分也隻影響單個庫困難,改變mod因子致使數據在全部庫之間遷移
數據熱點新舊用戶購物頻率有差別,有數據熱點問題新舊用戶均勻到分佈到各個庫,無熱點
實踐中,爲了處理簡單,選擇mod分庫的比較多。同時二次分庫時,爲了數據遷移方便,通常是按倍數增長,好比初始4個庫,二次分裂爲8個,再16個。這樣對於某個庫的數據,一半數據移到新庫,剩餘不動,對比每次只增長一個庫,全部數據都要大規模變更。
補充下,mod分庫通常每一個庫記錄數比較均勻,但也有些數據庫,存在超級Id,這些Id的記錄遠遠超過其餘Id,好比在廣告場景下,某個大廣告主的廣告數可能佔整體很大比例。若是按照廣告主Id取模分庫,某些庫的記錄數會特別多,對於這些超級Id,須要提供單獨庫來存儲記錄。
分庫數量首先和單庫能處理的記錄數有關,通常來講,Mysql 單庫超過5000萬條記錄,Oracle單庫超過1億條記錄,DB壓力就很大(固然處理能力和字段數量/訪問模式/記錄長度有進一步關係)。
在知足上述前提下,若是分庫數量少,達不到分散存儲和減輕DB性能壓力的目的;若是分庫的數量多,好處是每一個庫記錄少,單庫訪問性能好,但對於跨多個庫的訪問,應用程序須要訪問多個庫,若是是併發模式,要消耗寶貴的線程資源;若是是串行模式,執行時間會急劇增長。
最後分庫數量還直接影響硬件的投入,通常每一個分庫跑在單獨物理機上,多一個庫意味多一臺設備。因此具體分多少個庫,要綜合評估,通常初次分庫建議分4-8個庫。
分庫從某種意義上來講,意味着DB schema改變了,必然影響應用,但這種改變和業務無關,因此要儘可能保證分庫對應用代碼透明,分庫邏輯儘可能在數據訪問層處理。固然徹底作到這一點很困難,具體哪些應該由DAL負責,哪些由應用負責,這裏有一些建議:
對於單庫訪問,好比查詢條件指定用戶Id,則該SQL只需訪問特定庫。此時應該由DAL層自動路由到特定庫,當庫二次分裂時,也只要修改mod 因子,應用代碼不受影響。
對於簡單的多庫查詢,DAL負責彙總各個數據庫返回的記錄,此時仍對上層應用透明。
目前市面上的分庫分表中間件相對較多,其中基於代理方式的有MySQL Proxy和Amoeba,基於Hibernate框架的是Hibernate Shards,基於jdbc的有噹噹sharding-jdbc,基於mybatis的相似maven插件式的有蘑菇街的蘑菇街TSharding,經過重寫spring的ibatis template類是Cobar Client,這些框架各有各的優點與短板,架構師能夠在深刻調研以後結合項目的實際狀況進行選擇,可是總的來講,我我的對於框架的選擇是持謹慎態度的。一方面多數框架缺少成功案例的驗證,其成熟性與穩定性值得懷疑。另外一方面,一些從成功商業產品開源出框架(如阿里和淘寶的一些開源項目)是否適合你的項目是須要架構師深刻調研分析的。固然,最終的選擇必定是基於項目特色、團隊情況、技術門檻和學習成本等綜合因素考量肯定的。