Sharding-JDBC簡介

一般,線上系統的業務量不是很大,比如說單庫的數據量在百萬級別以下,那麼MySQL的單庫即可完成任何增/刪/改/查的業務操作。隨着業務的發展,單個DB中保存的數據量(用戶、訂單、計費明細和權限規則等數據)呈現指數級增長,那麼各種業務處理操作都會面臨單DB的IO讀寫瓶頸帶來的性能問題。MySQL的分區表比較適合用於解決業務數據具有較強時間序列特點,且數據量偏大的場景。但是,如果SQL的查詢條件並非基於時間維度的,那麼執行起來的效率並不會有所改觀,同時對於處理單表千萬級別以上數據量時,MySQL的分區表方案還是會顯得有些“力不從心”。

使用垂直切分方案的主要優點如下:

a.拆分後業務清晰,符合微服務的總體設計理念;

b.子系統之間的整合與擴展相對容易;

c.按照不同的業務類型,將不同的庫表放在不同的數據庫服務器上,便於管理;

d.根據業務數據的“冷”、“熱”狀態,採用不同的緩存和數據庫模式設計方案;

垂直切分的缺點如下:

a.跨庫的Join查詢,需要使用不同子系統的API接口讀取後在內存中完成關聯查詢,提高系統複雜度;

b.如果某一種類型的業務呈現爆發式地增長,該業務對應的庫表還是會存在單DB的IO讀寫的瓶頸問題,從這一點來說垂直切分並未從根本解決單庫單表數據量過大的問題;

c.存在跨庫事務的一致性問題,解決起來比較麻煩,當然也可以採用分佈式事務來解決;

將其按照數據表中某個字段或某幾個字段的某種規則切分存儲至多個DB中,在每個庫每個表中所包含的只是其中的一部分數據,所有庫表加起來的纔是全量的業務數據。

這種對數據的切分方式,基本可以保證經過水平切分後的單庫單表存儲的容量不會太大,從而保證了對單表的增/刪/改/查的SQL執行效率和處理能力。其中,數據的分片規則也是需要重點考慮的,因爲它會使得數據分片在多個庫表中是否均勻分佈存儲

使用水平切分方案的主要優點如下:

a.單庫單表的數據容量可以維持在一個量級,有助於提高業務SQL的執行效率和系統性能;

b.不管是利用ShardingJdbc組件還是MyCat框架,業務系統的應用層涉及改造得都較少,只需要根據實際的業務情況來設計數據分片的路由規則即可;

c.可以提高業務系統的穩定性和負載能力;

使用水平切分方案的主要缺點如下:

a.數據水平切分後,分佈在多庫多表中,跨庫Join查詢比較複雜;

b.分片數據的一致性較爲難保證;

c.對於歷史數據的遷移和數據庫的擴容需要較大的維護工作量;

 

 

分庫分表用於應對當前互聯網常見的兩個場景——大數據量和高併發。通常分爲垂直拆分和水平拆分兩種。


垂直拆分是根據業務將一個庫(表)拆分爲多個庫(表)。如:將經常和不常訪問的字段拆分至不同的庫或表中。由於與業務關係密切,目前的分庫分表產品均使用水平拆分方式。
水平拆分則是根據分片算法將一個庫(表)拆分爲多個庫(表)。如:按照ID的最後一位以3取餘,尾數是1的放入第1個庫(表),尾數是2的放入第2個庫(表)等。


關係型數據庫在大於一定數據量的情況下檢索性能會急劇下降。在面對互聯網海量數據情況時,所有數據都存於一張表,顯然會輕易超過數據庫表可承受的數據量閥值。這個單表可承受的數據量閥值,需根據數據庫和併發量的差異,通過實際測試獲得。
單純的分表雖然可以解決數據量過大導致檢索變慢的問題,但無法解決過多併發請求訪問同一個庫,導致數據庫響應變慢的問題。所以通常水平拆分都至少要採用分庫的方式,用於一併解決大數據量和高併發的問題。這也是部分開源的分片數據庫中間件只支持分庫的原因。但分表也有不可替代的適用場景。最常見的分表需求是事務問題。同在一個庫則不需考慮分佈式事務,善於使用同庫不同表可有效避免分佈式事務帶來的麻煩。目前強一致性的分佈式事務由於性能問題,導致使用起來並不一定比不分庫分錶快。目前採用最終一致性的柔性事務居多。分表的另一個存在的理由是,過多的數據庫實例不利於運維管理。綜上所述,最佳實踐是合理地配合使用分庫+分表。

 

客戶端代理: 分片邏輯在應用端,封裝在jar包中,通過修改或者封裝JDBC層來實現。 噹噹網的 Sharding-JDBC 、阿里的TDDL是兩種比較常用的實現。
中間件代理: 在應用和數據中間加了一個代理層。分片邏輯統一維護在中間件服務中。 我們現在談的 Mycat、360的Atlas、網易的DDB等等都是這種架構的實現

 

Sharding-JDBC簡介

Sharding-JDBC是噹噹應用框架ddframe中,從關係型數據庫模塊dd-rdb中分離出來的數據庫水平分片框架,實現透明化數據庫分庫分表訪問。Sharding-JDBC是繼dubbox和elastic-job之後,ddframe系列開源的第3個項目。

Sharding-JDBC直接封裝JDBC API,可以理解爲增強版的JDBC驅動,舊代碼遷移成本幾乎爲零:

    可適用於任何基於Java的ORM框架,如JPA、Hibernate、Mybatis、Spring JDBC Template或直接使用JDBC。
    可基於任何第三方的數據庫連接池,如DBCP、C3P0、 BoneCP、Druid等。
    理論上可支持任意實現JDBC規範的數據庫。雖然目前僅支持MySQL,但已有支持Oracle、SQLServer等數據庫的計劃。

Sharding-JDBC定位爲輕量Java框架,使用客戶端直連數據庫,以jar包形式提供服務,無proxy代理層,無需額外部署,無其他依賴,DBA也無需改變原有的運維方式。

Sharding-JDBC分片策略靈活,可支持等號、between、in等多維度分片,也可支持多分片鍵。

SQL解析功能完善,支持聚合、分組、排序、limit、or等查詢,並支持Binding Table以及笛卡爾積表查詢。


無論使用哪種架構,核心邏輯均極爲相似,除了協議實現層不同(JDBC或數據庫協議),都會分爲分片規則配置、SQL解析、SQL改寫、SQL路由、SQL執行以及結果歸併等模塊。

 

sharding-jdbc的版本及其架構差異

目前最新版本的sharding-jdbc是v3.0.0.M1,應該來說還不穩定,包名又改成了io.shardingsphere,jar包名是sharding-jdbc。

1.5.4.1是目前最新版,也可能是終版,1.x的,座標是com.dangdang,jar包名是sharding-jdbc。

2.0.3是目前最新版,包名和座標統一改成了io.shardingjdbc,jar包名是sharding-jdbc-core。

說明規劃不是特別好,還是有些亂。

因爲2.0之後基本上純粹分庫分表的核心特性的增強就不多了,主要往治理和代理方向去了,所以如果沒有特別的需求比如需要類似mycat的獨立服務代理模式,使用1.x(注:1.x版本官網文檔好像下線了)就可以了,不過如果是大規模的部署,同時已經使用了微服務架構中的註冊中心或者基於spring boot,可以考慮使用2.0,因爲2.0增加了基於註冊中心的配置管理以及spring boot starter。所以2.0的架構是這樣的:

3.0之後,增加了類似mycat角色的sharding-proxy無狀態服務器(代理層可以有,但是不應該作爲應用直接訪問的入口,如下圖所示),以及類似於Service Mesh的Database Mesh。不過核心沒有變化,對SQL語法增加了部分新的支持。所以3.0的架構如下:

就分庫分表核心來說,我們就只要關心sharding-jdbc就可以了,不需要關心sharding-sphere的其他部分。

sharding jdbc 2.x不支持強一致性的分佈式事務,一般現在的系統設計也不追求強一致性,而是最終一致性。所以sharding jdbc 2.x支持2中事務:弱XA(同mycat)和最大努力投遞事務(官方簡稱BED)(算是BASE的一種),具體選擇哪一種需要根據業務和開發進行權衡,如果架構規範和設計做得好,是可以做到不跨庫分佈式事務的。BED的架構如下

但是沒有免費的午餐,BED對開發和維護有着一定的額外要求,而且這些要求都涉及面很廣,絕對算得上傷筋動骨。開發層面包括:

  1. INSERT語句的主鍵不能自增
  2. UPDATE必須可重複執行,比如不支持UPDATE xxx SET x=x+1,對於更新餘額類,這就相當於要求必須樂觀鎖了

運維層面包括:

  1. 需要存儲事務日誌的數據庫
  2. 用於異步作業使用的zookeeper
  3. 解壓sharding-jdbc-transaction-async-job-$VERSION.tar,通過start.sh腳本啓動異步作業

我們選擇了從設計層面避免強一致性的分佈式事務。

假設我們不使用弱性事務(如果使用柔性事務,則還需要引入sharding-jdbc-transaction以及sharding-jdbc-transaction-async-job),這樣就只要引入sharding-jdbc-core這個jar包就可以了,

<dependency>
            <groupId>io.shardingjdbc</groupId>
            <artifactId>sharding-jdbc-core</artifactId>
            <version>2.0.3</version>
        </dependency>
        <!-- for sharding-jdbc spring namespace -->
        <dependency>
            <groupId>io.shardingjdbc</groupId>
            <artifactId>sharding-jdbc-core-spring-namespace</artifactId>
            <version>2.0.3</version>
        </dependency>

Sharding-JDBC的整體架構圖參見圖1。

分片規則配置

Sharding-JDBC的分片邏輯非常靈活,支持分片策略自定義、複數分片鍵、多運算符分片等功能。

如:根據用戶ID分庫,根據訂單ID分表這種分庫分表結合的分片策略;或根據年分庫,月份+用戶區域ID分表這樣的多片鍵分片。

Sharding-JDBC除了支持等號運算符進行分片,還支持in/between運算符分片,提供了更加強大的分片功能。

Sharding-JDBC提供了spring命名空間用於簡化配置,以及規則引擎用於簡化策略編寫。

JDBC規範重寫

Sharding-JDBC對JDBC規範的重寫思路是針對DataSource、Connection、Statement、PreparedStatement和ResultSet五個核心接口封裝,將多個真實JDBC實現類集合(如:MySQL JDBC實現/DBCP JDBC實現等)納入Sharding-JDBC實現類管理。

Sharding-JDBC儘量最大化實現JDBC協議,包括addBatch這種在JPA中會使用的批量更新功能。但分片JDBC畢竟與原生JDBC不同,所以目前仍有未實現的接口,包括Connection遊標,存儲過程和savePoint相關、ResultSet向前遍歷和修改等不太常用的功能。此外,爲了保證兼容性,並未實現JDBC 4.1及其後發佈的接口(如:DBCP 1.x版本不支持JDBC 4.1)。
SQL解析

SQL解析作爲分庫分表類產品的核心,性能和兼容性是最重要的衡量指標。目前常見的SQL解析器主要有fdb/jsqlparser和Druid。Sharding-JDBC使用Druid作爲SQL解析器,經實際測試,Druid解析速度是另外兩個解析器的幾十倍。

目前Sharding-JDBC支持join、aggregation(包括avg)、order by、 group by、limit、甚至or查詢等複雜SQL的解析。目前不支持union、部分子查詢、函數內分片等不太應在分片場景中出現的SQL解析。

SQL改寫

SQL改寫分爲兩部分,一部分是將分表的邏輯表名稱替換爲真實表名稱。另一部分是根據SQL解析結果替換一些在分片環境中不正確的功能。這裏具兩個例子:

第1個例子是avg計算。在分片的環境中,以avg1 +avg2+avg3/3計算平均值並不正確,需要改寫爲(sum1+sum2+sum3)/(count1+count2+ count3)。這就需要將包含avg的SQL改寫爲sum和count,然後再結果歸併時重新計算平均值。

第2個例子是分頁。假設每10條數據爲一頁,取第2頁數據。在分片環境下獲取limit 10, 10,歸併之後再根據排序條件取出前10條數據是不正確的結果。正確的做法是將分條件改寫爲limit 0, 20,取出所有前2頁數據,再結合排序條件算出正確的數據。可以看到越是靠後的Limit分頁效率就會越低,也越浪費內存。有很多方法可避免使用limit進行分頁,比如構建記錄行記錄數和行偏移量的二級索引,或使用上次分頁數據結尾ID作爲下次查詢條件的分頁方式。
SQL路由

SQL路由是根據分片規則配置,將SQL定位至真正的數據源。主要分爲單表路由、Binding表路由和笛卡爾積路由。

單表路由最爲簡單,但路由結果不一定落入唯一庫(表),因爲支持根據between和in這樣的操作符進行分片,所以最終結果仍然可能落入多個庫(表)。

Binding表可理解爲分庫分表規則完全一致的主從表。舉例說明:訂單表和訂單詳情表都根據訂單ID作爲分片鍵,任意時刻分片邏輯均相同。這樣的關聯查詢和單表查詢難度和性能相當。

笛卡爾積查詢最爲複雜,因爲無法根據Binding關係定位分片規則的一致性,所以非Binding表的關聯查詢需要拆解爲笛卡爾積組合執行。查詢性能較低,而且數據庫連接數較高,需謹慎使用。
SQL執行

路由至真實數據源後,Sharding-JDBC將採用多線程併發執行SQL,並完成對addBatch等批量方法的處理。

結果歸併

結果歸併包括4類:普通遍歷類、排序類、聚合類和分組類。每種類型都會先根據分頁結果跳過不需要的數據。

普通遍歷類最爲簡單,只需按順序遍歷ResultSet的集合即可。

排序類結果將結果先排序再輸出,因爲各分片結果均按照各自條件完成排序,所以採用歸併排序算法整合最終結果。

聚合類分爲3種類型,比較型、累加型和平均值型。比較型包括max和min,只返回最大(小)結果。累加型包括sum和count,需要將結果累加後返回。平均值則是通過SQL改寫的sum和count計算,相關內容已在SQL改寫涵蓋,不再贅述。

分組類最爲複雜,需要將所有的ResultSet結果放入內存,使用map-reduce算法分組,最後根據排序和聚合條件做相關處理。最消耗內存,最損失性能的部分即是此,可以考慮使用limit合理的限制分組數據大小。

結果歸併部分目前並未採用管道解析的方式,之後會針對這裏做更多改進。
 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

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