Sharding
隨著業(yè)務發(fā)展挪钓、用戶量的增長是越,技術團隊都會遇到數據量大、查詢緩慢碌上、大數據量的存儲問題倚评。一般來說,Mysql單庫超過5000萬條記錄馏予,Oracle單庫超過1億條記錄天梧,DB壓力就很大,當然容量大小和具體業(yè)務霞丧、字段數量呢岗、訪問模式、數據內容等都有進一步關系蛹尝。如果僅僅是查詢緩慢后豫,可以先從sql優(yōu)化、緩存突那、讀寫分離等手段解決挫酿。當數據量增長超過單庫限制時,很容易出現(xiàn)性能問題愕难,會自然而想到分庫分表存儲數據早龟,即sharding惫霸。
sharding分類
sharding指的是數據分片存儲,和數據庫的主備葱弟、讀寫分離不是一個問題壹店。
主備是為了提高系統(tǒng)的高可用性;讀寫分離是應對某些情況下芝加,讀性能的優(yōu)化和要求茫打。sharding的基本思想就要把一個數據庫切分成多個部分放到不同的數據庫上,從而緩解單一數據庫的性能問題妖混。sharding可以大概分成兩類,垂直切分和水平切分轮洋。
-
垂直切分
制市。對于海量數據的數據庫,如果是因為表多而數據多弊予,而每張表的數據量相差不大祥楣,這時候適合使用垂直切分,即把關系緊密(比如同一模塊)的一些表切分出來放在一個庫汉柒,從而將原來的單庫切分成多個庫误褪。 -
水平切分
。對于表不多碾褂、但每張表的數據非常多的情形兽间,適合水平切分,即把表的數據按某種規(guī)則(比如按ID散列)切分到多個庫上正塌。 -
通常情況下嘀略,需要同時考慮水平切分和垂直切分
,甚至也可以單庫內部做表拆分乓诽,實際的切分原則都需要考慮自己的實際情況帜羊。當同時進行垂直和水平切分時,切分策略會發(fā)生一些微妙的變化鸠天。比如:在只考慮垂直切分的時候讼育,被劃分到一起的表之間可以保持任意的關聯(lián)關系,因此你可以按“功能模塊”劃分表格稠集,但是一旦引入水平切分之后奶段,表間關聯(lián)關系就會受到很大的制約,通常只能允許一個主表(以該表ID進行散列的表)和其多個次表之間保留關聯(lián)關系巍杈,也就是說:當同時進行垂直和水平切分時忧饭,在垂直方向上的切分將不再以“功能模塊”進行劃分,而是需要更加細粒度的垂直切分筷畦,而這個粒度與領域驅動設計中的“聚合”概念不謀而合词裤,甚至可以說是完全一致刺洒,每個shard的主表正是一個聚合中的聚合根!這樣切分下來你會發(fā)現(xiàn)數據庫分被切分地過于分散了(shard的數量會比較多吼砂,但是shard里的表卻不多)逆航,為了避免管理過多的數據源,充分利用每一個數據庫服務器的資源渔肩,可以考慮將業(yè)務上相近因俐,并且具有相近數據增長速率(主表數據量在同一數量級上)的兩個或多個shard放到同一個數據源里,每個shard依然是獨立的周偎,它們有各自的主表抹剩,并使用各自主表ID進行散列,不同的只是它們的散列取模(即節(jié)點數量)必需是一致的.
sharding中需要解決的問題
-
事務問題
蓉坎。當同一個業(yè)務中涉及到多庫時澳眷,就需要考慮分布式事務了。對于分布式事務的解決蛉艾,有多種方案钳踊,比如兩階段提交、一階段提交勿侯、Best Efforts 1PC模式和事務補償機制等拓瞪。 -
數據遷移,容量規(guī)劃助琐,擴容等問題
祭埂。建議利用對2的倍數取余具有向前兼容的特性(如對4取余得1的數對2取余也是1)來分配數據,避免了行級別的數據遷移弓柱,但是依然需要進行表級別的遷移沟堡,同時對擴容規(guī)模和分表數量都有限制。 -
跨節(jié)點sql
矢空。如跨節(jié)點join航罗、count、order by屁药、group by以及聚合函數等粥血。只要是進行切分,跨節(jié)點Join的問題是不可避免的酿箭。但是良好的設計和切分卻可以減少此類情況的發(fā)生复亏。解決這一問題的普遍做法是分兩次查詢實現(xiàn)。在第一次查詢的結果集中找出關聯(lián)數據的id,根據這些id發(fā)起第二次請求得到關聯(lián)數據缭嫡。因為它們都需要基于全部數據集合進行計算缔御。count、group by等問題妇蛀,一般的解決方案是分別在各個節(jié)點上得到結果后在應用程序端進行合并耕突。和join不同的是每個結點的查詢可以并行執(zhí)行笤成,因此很多時候它的速度要比單一大表快很多。但如果結果集很大眷茁,對應用程序內存的消耗是一個問題炕泳。 -
分布式唯一ID問題
。BIGINT型可以使用Twitter的分布式自增ID算法Snowflake上祈,VARCHAR類型可以考慮UUID培遵。但是Snowflake也會有自己的問題,比如某些場景登刺,生成的值大部分都是偶數籽腕。 -
sharding的時候還需要考慮自身的業(yè)務
。比如根據用戶ID分訂單表纸俭,有些用戶根本不下單节仿,但是可能有些用戶的訂單量占比超過總量的80%,如果這些用戶被sharding在了同一個庫和表中掉蔬,實際的sharding效果就會很差。這種情況就需要自定義分表規(guī)則矾瘾。
中間件
對于Java服務來講女轿,很大的一個優(yōu)勢就是生態(tài)完善,組件齊全壕翩。sharding也是如此蛉迹。sharding的中間件大概可以分成兩大類,一種是基于jdbc的lib組件放妈,一種是基于代理(Proxy)的中間件北救。基于jdbc的lib組件芜抒,好處在于易于和Java服務集成珍策、輕量;易于上手宅倒,無運維成本攘宙;業(yè)務直接到數據庫,少一層proxy理論上性能更好拐迁〔渑基于Proxy的中間件,需要在所有的數據源中間搭一個Proxy服務线召,Java的數據源只連接到Proxy上铺韧,由Proxy負責底層的分庫分表,以及請求路由缓淹,優(yōu)勢在于解耦性比較高哈打;可以找專門的DBA負責和運維Proxy塔逃,分庫分表操作對于Java程序員透明化;易于實現(xiàn)監(jiān)控前酿、數據遷移患雏、連接管理等功能;劣勢就是運維成本的增加罢维,小公司可能沒有預算請專門的DBA和運維人員來做這個解耦工作淹仑。
lib組件包括:當當網sharding-sphere、蘑菇街TSharding肺孵;
基于Proxy的中間件:TDDL匀借、DBProxy、Atlas平窘、oneproxy吓肋、vitess、mycat瑰艘、cobar等是鬼。此處就不做各個中間件的對比了。
代碼
https://github.com/chxfantasy/dynamic-datasource-with-sharding-starter
這里實現(xiàn)了一個支持多庫路由紫新、分庫分表的spring-boot-starter均蜜,基于baomidou/dynamic-datasource-spring-boot-starter 和 sharding-sphere
dynamic-datasource本身是為了在同一個系統(tǒng)中支持多庫,包括主備芒率、讀寫分離囤耳、多庫等,多個讀庫時偶芍,支持定義負載均衡算法充择;sharding-sphere是基于lib的分庫分表組件,可以根據配置的規(guī)則動態(tài)路由到相關庫和表匪蟀。在我的代碼中椎麦,sharding-sphere做一個dynamic-datasource的一個數據源。
一個問題:既然sharding-shpere本身已經支持多庫材彪、分庫和分表铃剔,為何還要再將它集成到dynamic-datasource中?有兩方面原因:首先查刻,sharding-sphere原理是對sql語句進行改造键兜,然后路由到相應的數據源和表,這就導致它對某些sql語句不支持穗泵,比如distinct普气,在分庫分表的場景下就無法使用distinct關鍵字,但是對于未sharding的表佃延,也無法使用distinct现诀,這時候就可以使用dynamic-datasource來為未sharding的表做一個新的數據源夷磕;其次,某些系統(tǒng)仔沿,可能需要掛載很多數據庫(比如后臺管理系統(tǒng)需要掛載5個庫)坐桩,而其中只有1個庫涉及到sharding,而其他4個庫不涉及封锉,這時候就只能用dynamic-datasource來管理另外的4個庫和數據源绵跷。
代碼中有example,僅供參考成福。