mysql數據庫分庫分表shardingjdbc


分庫分表理解 

  分庫分表應用於互聯網的兩個場景;大量數據和高並發,通常策略有兩種:垂直分庫,水平拆分

  垂直拆分:是根據業務將一個庫拆分為多個庫,將一個表拆分為多個表,例如:將不常用的字段和經常訪問的字段分開存放,在實際開發由於跟業務關系緊密,所以一般采用水平拆分。

  水平拆分:則是根據分片算法講一個庫拆分為多個庫,來進行維護,與垂直拆分不同,水平拆分是按照一定的規則進行拆分,將不同的數據拆分至不同的物理庫。

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

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

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

 

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

 

由圖可以看出,我們只需要配置分片規則,然后關注業務就可以就可以實現數據庫的分庫分表,並不需要進行太多的配置

分片規則配置

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

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

 

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

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

運維層面包括:

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

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

分片靈活性

對於分庫分表來說,很重要的一個特性是分片的靈活性,比如單個字段、多個字段的=、IN、>=、<=。為什么多個字段很重要的,這里涉及到一個特殊的考慮

sharding-jdbc目前提供4種分片算法。

由於分片算法和業務實現緊密相關,因此並未提供內置分片算法,而是通過分片策略將各種場景提煉出來,提供更高層級的抽象,並提供接口讓應用開發者自行實現分片算法。

  • 精確分片算法

對應PreciseShardingAlgorithm,用於處理使用單一鍵作為分片鍵的=與IN進行分片的場景。需要配合StandardShardingStrategy使用。

  • 范圍分片算法

對應RangeShardingAlgorithm,用於處理使用單一鍵作為分片鍵的BETWEEN AND進行分片的場景。需要配合StandardShardingStrategy使用。

  • 復合分片算法

對應ComplexKeysShardingAlgorithm,用於處理使用多鍵作為分片鍵進行分片的場景,多分片鍵邏輯較復雜,需要應用開發者自行處理其中的復雜度。需要配合ComplexShardingStrategy使用。

  • Hint分片算法(Hint分片指的是對於分片字段非SQL決定,而由其他外置條件決定的場景,可使用SQL Hint靈活的注入分片字段。例:內部系統,按照員工登錄ID分庫,而數據庫中並無此字段。SQL Hint支持通過Java API和SQL注釋(待實現)兩種方式使用。)

對應HintShardingAlgorithm,用於處理使用Hint行分片的場景。需要配合HintShardingStrategy使用。

因為算法的靈活性,標准的方式是通過實現具體的java接口是實現具體的分片算法比如SingleKeyDatabaseShardingAlgorithm,有不少的情況下,分片是比較簡單的,比如說純粹是客戶編號,此時提供了行內表達式分片策略,使用Groovy的表達式,提供對SQL語句中的=和IN的分片操作支持,不過這只支持單分片鍵。比如,t_user_${u_id % 8} 表示t_user表按照u_id按8取模分成8個表,表名稱為t_user_0t_user_7

分片鍵+分片算法=真正可用的分片策略。

算法和分片鍵的選擇是分庫分表的關鍵,其直接決定了各個分庫的負載是否均衡,以及擴展是否容易。在設計上的考慮一節筆者會詳細闡述,訂單和委托業務、用戶在使用分庫分表時設計上的考慮以及原因。 

SQL語法限制

對於分庫分表來說,還需要知道有哪些SQL的限制,尤其是涉及到需要二次處理的,比如排序,去重,聚合等。

這里筆者就列下那些常用但沒有被支持的。比如:

  • case when
  • distinct
  • union

不過好在這些在java/js中處理都比較方便。

如果有很復雜的SQL,那最大的可能就是設計上有問題,應該采用讀寫分離解決。

sharding-jdbc對SQL的限制完整可以參考http://shardingsphere.io/document/current/cn/features/sharding/usage-standard/sql/

設計上的考慮

哪些表要分庫分表

首先從設計上要區分清楚哪些是廣播表/哪些是分庫表/哪些是只在一個庫的全局表,因為是公用數據源的,所以不管是不是分庫的表,都需要配置,不配置分片規則Sharding-JDB即無法精確的斷定應該路由至哪個數據源。但是一般分庫分表組件包括Sharding-JDBC都會提供簡化配置的方法。對於不分片的表:

方法1:sharding-jdbc可以在<sharding:sharding-rule />配置default-data-source-name,這樣未配置分片規則的表將通過默認數據源定位。

方法2:將不參與分庫分表的數據源獨立於Sharding-JDBC之外,在應用中使用多個數據源分別處理分片和不分片的情況。

分庫還是分表

一般來說應該選擇分庫(准確的說是分schema),不應該使用分表, 因為oracle可以包含n個schema,mysql可以包含多個database,而且就算真的需要,schema之間也是可以關聯查詢的,所以感覺就算是為了擴展性問題,也沒有必要使用分表,分表反而在擴展的時候更加麻煩。就算數據量多了一點,感覺稍微偏慢,可以先采用分區擋一擋。

分片鍵的選擇

 其中最重要的是分片鍵不能是自增字段,否則insert就不知道去哪里了。所以對於ID生成,需要一個ID生成中心。

分布式主鍵

分布式系統的主鍵生成可以通過設置增量或者也通過ID生成中心來生成,不過話說回來,既然使用ID生成中心了,就不要再使用數據庫機制的ID了,這不一定需要通過代碼全部重寫,可以在dao層通過aop判斷是否insert,是Insert的動態從ID中心獲取,這樣就避免了分庫分表和非分庫分表在開發商的差別。 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM