數據庫分庫分表從互聯網時代開啓至今,一直是熱門話題。在NoSQL橫行的今天,關係型數據庫憑藉其穩定、查詢靈活、兼容等特性,仍被大多數公司做爲首選數據庫。所以,合理採用分庫分表技術應對海量數據和高併發對數據庫的衝擊,是各大互聯網公司不可避免的問題。算法
雖然不少公司都致力於開發本身的分庫分表中間件,但截止目前,仍無完美的開源解決方案覆蓋此領域。spring
分庫分表用於應對當前互聯網常見的兩個場景——大數據量和高併發。一般分爲垂直拆分和水平拆分兩種。sql
垂直拆分是根據業務將一個庫(表)拆分爲多個庫(表)。如:將常常和不常訪問的字段拆分至不一樣的庫或表中。因爲與業務關係密切,目前的分庫分表產品均使用水平拆分方式。數據庫
水平拆分則是根據分片算法將一個庫(表)拆分爲多個庫(表)。如:按照ID的最後一位以3取餘,尾數是1的放入第1個庫(表),尾數是2的放入第2個庫(表)等。多線程
關係型數據庫在大於必定數據量的狀況下檢索性能會急劇降低。在面對互聯網海量數據狀況時,全部數據都存於一張表,顯然會輕易超過數據庫表可承受的數據量閥值。這個單表可承受的數據量閥值,需根據數據庫和併發量的差別,經過實際測試得到。架構
單純的分表雖然能夠解決數據量過大致使檢索變慢的問題,但沒法解決過多併發請求訪問同一個庫,致使數據庫響應變慢的問題。因此一般水平拆分都至少要採用分庫的方式,用於一併解決大數據量和高併發的問題。這也是部分開源的分片數據庫中間件只支持分庫的緣由。併發
但分表也有不可替代的適用場景。最多見的分表需求是事務問題。同在一個庫則不需考慮分佈式事務,善於使用同庫不一樣表可有效避免分佈式事務帶來的麻煩。目前強一致性的分佈式事務因爲性能問題,致使使用起來並不必定比不分庫分錶快。目前採用最終一致性的柔性事務居多。分表的另外一個存在的理由是,過多的數據庫實例不利於運維管理。綜上所述,最佳實踐是合理地配合使用分庫+分表。框架
Sharding-JDBC是噹噹應用框架ddframe中,從關係型數據庫模塊dd-rdb中分離出來的數據庫水平分片框架,實現透明化數據庫分庫分表訪問。Sharding-JDBC是繼dubbox和elastic-job以後,ddframe系列開源的第3個項目。運維
Sharding-JDBC直接封裝JDBC API,能夠理解爲加強版的JDBC驅動,舊代碼遷移成本幾乎爲零:分佈式
Sharding-JDBC定位爲輕量Java框架,使用客戶端直連數據庫,以jar包形式提供服務,無proxy代理層,無需額外部署,無其餘依賴,DBA也無需改變原有的運維方式。
Sharding-JDBC分片策略靈活,可支持等號、between、in等多維度分片,也可支持多分片鍵。
SQL解析功能完善,支持聚合、分組、排序、limit、or等查詢,並支持Binding Table以及笛卡爾積表查詢。
爲了對其餘開源項目表示尊重,咱們無心評論目前仍在更新中的項目。這裏僅列出目前中止更新,但仍然在數據庫分片領域很是有影響力的幾個項目,請參見表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。
Sharding-JDBC的分片邏輯很是靈活,支持分片策略自定義、複數分片鍵、多運算符分片等功能。
如:根據用戶ID分庫,根據訂單ID分表這種分庫分表結合的分片策略;或根據年分庫,月份+用戶區域ID分表這樣的多片鍵分片。
Sharding-JDBC除了支持等號運算符進行分片,還支持in/between運算符分片,提供了更增強大的分片功能。
Sharding-JDBC提供了spring命名空間用於簡化配置,以及規則引擎用於簡化策略編寫。因爲目前剛開源分片核心邏輯,這兩個模塊暫未開源,待覈心穩定後將會開源其餘模塊。
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解析器主要有fdb/jsqlparser和Druid。Sharding-JDBC使用Druid做爲SQL解析器,經實際測試,Druid解析速度是另外兩個解析器的幾十倍。
目前Sharding-JDBC支持join、aggregation(包括avg)、order by、 group by、limit、甚至or查詢等複雜SQL的解析。目前不支持union、部分子查詢、函數內分片等不太應在分片場景中出現的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定位至真正的數據源。主要分爲單表路由、Binding表路由和笛卡爾積路由。
單表路由最爲簡單,但路由結果不必定落入惟一庫(表),由於支持根據between和in這樣的操做符進行分片,因此最終結果仍然可能落入多個庫(表)。
Binding表可理解爲分庫分表規則徹底一致的主從表。舉例說明:訂單表和訂單詳情表都根據訂單ID做爲分片鍵,任意時刻分片邏輯均相同。這樣的關聯查詢和單表查詢難度和性能至關。
笛卡爾積查詢最爲複雜,由於沒法根據Binding關係定位分片規則的一致性,因此非Binding表的關聯查詢須要拆解爲笛卡爾積組合執行。查詢性能較低,並且數據庫鏈接數較高,需謹慎使用。
路由至真實數據源後,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的性能測試報告。
目前Sharding-JDBC集中於分庫分表核心邏輯開發,在功能穩定以後將會按照以下線路持續更新:
目前國內不少開源產品都在公司內部經受過期間的考驗,而後剝離業務邏輯和敏感代碼,再開源貢獻給社區。這樣作的優勢是開源的產品相對成熟。但缺點也不可避免,主要有:
咱們考慮全新的開源策略,在Sharding-JDBC剛完成第一版的時候,即向社區和噹噹內部同時推廣。這樣作的好處有:
最後須要澄清,未經時間考證的Sharding-JDBC並不是Bug成堆,徹底不可用的項目。目前測試覆蓋率超過90%,詳細功能以及不支持項都明確地羅列在GitHub的文檔中,但願讓使用者心中有數。