解讀分庫分表中間件Sharding-JDBC

murphykwu 9年前發布 | 155K 次閱讀 JDBC SQL 開源

來自: http://www.uml.org.cn/sjjm/2016021710.asp

【編者按】數據庫分庫分表從互聯網時代開啟至今,一直是熱門話題。在NoSQL橫行的今天,關系型數據庫憑借其穩定、查詢靈活、兼容等特性,仍被大多數公司作為首選數據庫。因此,合理采用分庫分表技術應對海量數據和高并發對數據庫的沖擊,是各大互聯網公司不可避免的問題。

雖然很多公司都致力于開發自己的分庫分表中間件,但截止目前,仍無完美的開源解決方案覆蓋此領域。

分庫分表適用場景

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

垂直拆分是根據業務將一個庫(表)拆分為多個庫(表)。如:將經常和不常訪問的字段拆分至不同的庫或表中。由于與業務關系密切,目前的分庫分表產品均使用水平拆分方式。

水平拆分則是根據分片算法將一個庫(表)拆分為多個庫(表)。如:按照ID的最后一位以3取余,尾數是1的放入第1個庫(表),尾數是2的放入第2個庫(表)等。

關系型數據庫在大于一定數據量的情況下檢索性能會急劇下降。在面對互聯網海量數據情況時,所有數據都存于一張表,顯然會輕易超過數據庫表可承受的數據量閥值。這個單表可承受的數據量閥值,需根據數據庫和并發量的差異,通過實際測試獲得。

單純的分表雖然可以解決數據量過大導致檢索變慢的問題,但無法解決過多并發請求訪問同一個庫,導致數據庫響應變慢的問題。所以通常水平拆分都至少要采用分庫的方式,用于一并解決大數據量和高并發的問題。這也是部分開源的分片數據庫中間件只支持分庫的原因。

但分表也有不可替代的適用場景。最常見的分表需求是事務問題。同在一個庫則不需考慮分布式事務,善于使用同庫不同表可有效避免分布式事務帶來的麻煩。目前強一致性的分布式事務由于性能問題,導致使用起來并不一定比不分庫分表快。目前采用最終一致性的柔性事務居多。分表的另一個存在的理由是,過多的數據庫實例不利于運維管理。綜上所述,最佳實踐是合理地配合使用分庫+分表。

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以及笛卡爾積表查詢。

與常見開源產品對比

為了對其他開源項目表示尊重,我們無意評論目前仍在更新中的項目。這里僅列出目前停止更新,但仍然在數據庫分片領域非常有影響力的幾個項目,請參見表1。

表1 數據庫分片工具對比

通過以上表格可以看出,Cobar屬于中間層方案,在應用程序和MySQL之間搭建一層Proxy。中間層介于應用程序與數據庫間,需要做一次轉發,而基于JDBC協議并無額外轉發,直接由應用程序連接數據庫,性能上有些許優勢。這里并非說明中間層一定不如客戶端直連,除了性能,需要考慮的因素還有很多,中間層更便于實現監控、數據遷移、連接管理等功能。

Cobar-Client、TDDL和Sharding-JDBC均屬于客戶端直連方案。此方案的優勢在于輕便、兼容性、性能以及對DBA影響小。其中Cobar-Client的實現方式基于ORM(Mybatis)框架,其兼容性與擴展性不如基于JDBC協議的后兩者。

實現原理

前文已介紹了Sharding-JDBC是實現了JDBC協議的jar文件。基于JDBC協議的實現與基于MySQL等數據庫協議實現的中間層略有差別。

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

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

圖1 Sharding-JDBC的整體架構圖

分片規則配置

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合理的限制分組數據大小。

結果歸并部分目前并未采用管道解析的方式,之后會針對這里做更多改進。

性能

路由結果在單庫單表的性能測試報告:

查詢操作:Sharding-JDBC的TPS為JDBC的TPS的99.8%;

插入操作:Sharding-JDBC的TPS為JDBC的TPS的90.2%;

更新操作:Sharding-JDBC的TPS為JDBC的TPS的93.1%;?

可以看到,Sharding-JDBC性能損失非常低。

路由結果在多庫多表的性能測試報告:

查詢操作:TPS雙庫比單庫可以增加大約94%的性能;?

插入操作:TPS雙庫比單庫可以增加大約60%的性能;

更新操作:TPS雙庫比單庫可以增加大約89%的性能;?

結果表明,Sharding-JDBC可有效利用多線程與分布式資源大幅度提升性能;?

更多詳細情況可查看Sharding-JDBC的性能測試報告。

Roadmap

目前Sharding-JDBC集中于分庫分表核心邏輯開發,在功能穩定之后將會按照如下線路持續更新:

  • 讀寫分離;
  • 柔性分布式事務;
  • 分布式主鍵生成策略;
  • SQL重寫優化,進一步提升性能;
  • SQL Hint,可指定某SQL在某具體庫表執行,基于業務規則而非SQL解析路由;?
    小表廣播;
  • HA相關;
  • 流量控制;
  • 數據庫建表工具;
  • 數據遷移;
  • 復雜SQL解析支持,如子查詢、存儲過程等;
  • Oracle, SQLServer支持;
  • 配置中心;

開源理念

目前國內很多開源產品都在公司內部經受過時間的考驗,然后剝離業務邏輯和敏感代碼,再開源貢獻給社區。這樣做的優點是開源的產品相對成熟。但缺點也不可避免,主要有:

  1. 后續支持匱乏。產品已經滿足了該公司的業務場景需求,缺乏后續提升的動力。文檔、支持也會相對較少,甚至出現文檔和代碼不同步的狀況。
  2. 與該公司業務場景耦合較為嚴重。大部分框架產品都是為了解決特定的問題。比如:有的公司可能并不需要分表;有的公司只需支持幾種分片策略就好。
  3. 開源不完整。和公司業務耦合緊密的部分不會開源。
  4. 缺乏粘度。較為成型的項目由于功能繁多、代碼結構復雜,社區志愿者難于擴展或修改核心邏輯。如果測試覆蓋率不夠,難以保證修改后的代碼質量。以上一系列問題會導致項目對社區的粘度不高,難于找尋可合作開發的志愿者。
  5. 分支眾多難于維護。由于開源之后公司缺乏持續提升的動力,和本公司關系不大的需求功能得不到重視,導致各公司都開發自己的分支。開源項目雖然一開始給社區注入了新鮮思想,但最終并沒有吸取社區精華。如:Dubbo一出現即引起了相當多的關注,而各公司都有自己的版本,如當當的DubboX,但最終Dubbo并未能持續發展。

我們考慮全新的開源策略,在Sharding-JDBC剛完成初版的時候,即向社區和當當內部同時推廣。這樣做的好處有:

  • 后續支持完善。Sharding-JDBC與當當內部落地綁定,將會在當當內部和社區同時提供支持。雖然無法提供社區需求的優先級高于當當內部的承諾,但我們會綜合考慮社區與內部的需求,以更高的視角,盡量整合與優化升級路線。
  • 完整開源。代碼的snapshot版本都會首先出現在GitHub上。
  • 共同發展。Sharding-JDBC目前代碼較為簡單。使社區開源愛好者能更加輕松地理解代碼核心,為以后的持續發展奠定基礎。并且Sharding-JDBC也會吸納社區精華,讓更多地愛好者參與代碼貢獻。

最后需要澄清,未經時間考證的Sharding-JDBC并非Bug成堆,完全不可用的項目。目前測試覆蓋率超過90%,詳細功能以及不支持項都明確地羅列在GitHub的文檔中,希望讓使用者心中有數。

當當網架構師張亮
 本文由用戶 murphykwu 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!