數據庫分庫分表中間件 Sharding-JDBC 源碼分析 —— SQL 路由(一)分庫分表配置


🙂🙂🙂關注微信公眾號:【芋道源碼】有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源碼分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注釋源碼 GitHub 地址
  3. 您對於源碼的疑問每條留言將得到認真回復。甚至不知道如何讀源碼也可以請教噢
  4. 新的源碼解析文章實時收到通知。每周更新一篇左右
  5. 認真的源碼交流微信群。

本文主要基於 Sharding-JDBC 1.5.0 正式版


1. 概述

😆《SQL 解析》 已經告於段落,我們要開始新的旅程:《SQL 路由》。相比SQL解析,路由會容易理解很多,騙人是小🐷。整個系列預計會拆分成三小篇文章:

  1. 《分庫分表配置》
  2. 《分表分庫路由》
  3. 《Spring與YAML配置》

第一、二篇會在近期更新。第三篇會在《SQL 改寫》《SQL 執行》完成后進行更新。😈改寫和執行相對有趣。

👼道友,您看,逗比博主“很有規划”,是關注公眾號一波【芋道源碼】還是分享朋友圈。


閱讀本文之前,建議已經讀過官方相關文章:

分表分庫配置會涉及如下類:

  • TableRule 表規則配置對象
  • ShardingRule 分庫分表規則配置對象
  • ShardingStrategy 分片策略
  • ShardingAlgorithm 分片算法

我們來一起逐個類往下看。

Sharding-JDBC 正在收集使用公司名單:傳送門
🙂 你的登記,會讓更多人參與和使用 Sharding-JDBC。傳送門
Sharding-JDBC 也會因此,能夠覆蓋更多的業務場景。傳送門
登記吧,騷年!傳送門

2. TableRule

TableRule,表規則配置對象,內嵌 TableRuleBuilder 對象進行創建。

2.1 logicTable

數據分片的邏輯表,對於水平拆分的數據庫(表),同一類表的總稱。
例:訂單數據根據主鍵尾數拆分為10張表,分別是t_order_0到t_order_9,他們的邏輯表名為t_order。

2.2 數據單元

Sharding-JDBC 有兩種類型數據單元

  • DataNode :靜態分庫分表數據單元

數據分片的最小單元,由數據源名稱和數據表組成。
例:ds_1.t_order_0。配置時默認各個分片數據庫的表結構均相同,直接配置邏輯表和真實表對應關系即可。
如果各數據庫的表結果不同,可使用ds.actual_table配置。

  • DynamicDataNode :動態表的分庫分表數據單元

邏輯表和真實表不一定需要在配置規則中靜態配置。
比如按照日期分片的場景,真實表的名稱隨着時間的推移會產生變化。
此類需求Sharding-JDBC是支持的,不過目前配置並不友好,會在新版本中提升。

TableRuleBuilder 調用 #build() 方法創建 TableRule。核心代碼如下:

// TableRuleBuilder.java
public static class TableRuleBuilder {
  public TableRule build() {
       KeyGenerator keyGenerator = null;
       if (null != generateKeyColumn && null != keyGeneratorClass) {
           keyGenerator = KeyGeneratorFactory.createKeyGenerator(keyGeneratorClass);
       }
       return new TableRule(logicTable, dynamic, actualTables, dataSourceRule, dataSourceNames, databaseShardingStrategy, tableShardingStrategy, generateKeyColumn, keyGenerator);
   }
}

// TableRule.java
public TableRule(final String logicTable, final boolean dynamic, final List<String> actualTables, final DataSourceRule dataSourceRule, final Collection<String> dataSourceNames,
                final DatabaseShardingStrategy databaseShardingStrategy, final TableShardingStrategy tableShardingStrategy,
                final String generateKeyColumn, final KeyGenerator keyGenerator) {
   Preconditions.checkNotNull(logicTable);
   this.logicTable = logicTable;
   this.dynamic = dynamic;
   this.databaseShardingStrategy = databaseShardingStrategy;
   this.tableShardingStrategy = tableShardingStrategy;
   if (dynamic) { // 動態表的分庫分表數據單元
       Preconditions.checkNotNull(dataSourceRule);
       this.actualTables = generateDataNodes(dataSourceRule);
   } else if (null == actualTables || actualTables.isEmpty()) { // 靜態表的分庫分表數據單元
       Preconditions.checkNotNull(dataSourceRule);
       this.actualTables = generateDataNodes(Collections.singletonList(logicTable), dataSourceRule, dataSourceNames);
   } else { // 靜態表的分庫分表數據單元
       this.actualTables = generateDataNodes(actualTables, dataSourceRule, dataSourceNames);
   }
   this.generateKeyColumn = generateKeyColumn;
   this.keyGenerator = keyGenerator;
}

2.2.1 DataNode

大多數業務場景下,我們使用靜態分庫分表數據單元,即 DataNode。如上文注釋處 靜態表的分庫分表數據單元 處所見,分成種判斷,實質上第一種是將 logicTable 作為 actualTable,即在里不進行分表,是第二種的一種特例。

我們來看看 #generateDataNodes() 方法:

// TableRule.java
/**
* 生成靜態數據分片節點
*
* @param actualTables 真實表
* @param dataSourceRule 數據源配置對象
* @param actualDataSourceNames 數據源名集合
* @return 靜態數據分片節點
*/
private List<DataNode> generateDataNodes(final List<String> actualTables, final DataSourceRule dataSourceRule, final Collection<String> actualDataSourceNames) {
   Collection<String> dataSourceNames = getDataSourceNames(dataSourceRule, actualDataSourceNames);
   List<DataNode> result = new ArrayList<>(actualTables.size() * (dataSourceNames.isEmpty() ? 1 : dataSourceNames.size()));
   for (String actualTable : actualTables) {
       if (DataNode.isValidDataNode(actualTable)) { // 當 actualTable 為 ${dataSourceName}.${tableName} 時
           result.add(new DataNode(actualTable));
       } else {
           for (String dataSourceName : dataSourceNames) {
               result.add(new DataNode(dataSourceName, actualTable));
           }
       }
   }
   return result;
}
/**
* 根據 數據源配置對象 和 數據源名集合 獲得 最終的數據源名集合
*
* @param dataSourceRule 數據源配置對象
* @param actualDataSourceNames 數據源名集合
* @return 最終的數據源名集合
*/
private Collection<String> getDataSourceNames(final DataSourceRule dataSourceRule, final Collection<String> actualDataSourceNames) {
   if (null == dataSourceRule) {
       return Collections.emptyList();
   }
   if (null == actualDataSourceNames || actualDataSourceNames.isEmpty()) {
       return dataSourceRule.getDataSourceNames();
   }
   return actualDataSourceNames;
}
  • 第一種情況,自定義分布actualTable${dataSourceName}.${tableName} 時,即已經明確真實表所在數據源。
TableRule.builder("t_order").actualTables(Arrays.asList("db0.t_order_0", "db1.t_order_1", "db1.t_order_2"))
db0
  └── t_order_0 
db1
  ├── t_order_1
  └── t_order_2
  • 第二種情況,均勻分布
TableRule.builder("t_order").actualTables(Arrays.asList("t_order_0", "t_order_1"))
db0
  ├── t_order_0 
  └── t_order_1 
db1
  ├── t_order_0 
  └── t_order_1

#getDataSourceNames() 使用 dataSourceRuleactualDataSourceNames 獲取數據源的邏輯看起來有種“詭異”。實際 TableRuleBuilder 創建 TableRule 時,使用 dataSourceRule 而不要使用 actualDataSourceNames

2.2.2 DynamicDataNode

少數業務場景下,我們使用動態分庫分表數據單元,即 DynamicDataNode。
通過 dynamic=true 屬性配置。生成代碼如下:

// TableRule.java
private List<DataNode> generateDataNodes(final DataSourceRule dataSourceRule) {
   Collection<String> dataSourceNames = dataSourceRule.getDataSourceNames();
   List<DataNode> result = new ArrayList<>(dataSourceNames.size());
   for (String each : dataSourceNames) {
       result.add(new DynamicDataNode(each));
   }
   return result;
}

😂 從代碼上看,貌似和動態分庫分表數據單元沒一毛錢關系?!別捉雞,答案在《分表分庫路由》 上。

2.3 分庫/分表策略

  • databaseShardingStrategy :分庫策略
  • tableShardingStrategy :分表策略

當分庫/分表策略不配置時,使用 ShardingRule 配置的分庫/分表策略。

2.4 主鍵生成

  • generateKeyColumn :主鍵字段
  • keyGenerator :主鍵生成器

當主鍵生成器不配置時,使用 ShardingRule 配置的主鍵生成器。

3. ShardingRule

ShardingRule,分庫分表規則配置對象,內嵌 ShardingRuleBuilder 對象進行創建。

其中 databaseShardingStrategy、tableShardingStrategy、keyGenerator、defaultGenerator 和 TableRule 屬性重復,用於當 TableRule 未配置對應屬性,使用 ShardingRule 提供的該屬性。

3.1 dataSourceRule

dataSourceRule,數據源配置對象。ShardingRule 需要數據源配置正確。這點和 TableRule 是不同的。TableRule 對 dataSourceRule 只使用數據源名字,最終執行SQL 使用數據源名字從 ShardingRule 獲取數據源連接。大家可以回到本文【2.2.1 DataNode】細看下 DataNode 的生成過程。

3.2 tableRules

tableRules,表規則配置對象集合

3.3 bindingTableRules

指在任何場景下分片規則均一致的主表和子表。
例:訂單表和訂單項表,均按照訂單ID分片,則此兩張表互為BindingTable關系。
BindingTable關系的多表關聯查詢不會出現笛卡爾積關聯,關聯查詢效率將大大提升。

😈 這么說,可能不太容易理解。《分表分庫路由》,我們在源碼的基礎上,好好理解下。非常重要,特別是性能優化上面

4. ShardingStrategy

ShardingStrategy,分片策略。

  • 針對分庫、分表有兩個子類。

  • DatabaseShardingStrategy,使用分庫算法進行分片
  • TableShardingStrategy,使用分表算法進行分片

《分表分庫路由》 會進一步說明。

5. ShardingAlgorithm

ShardingAlgorithm,分片算法。

  • 針對分庫、分表有兩個子接口
  • 針對分片鍵數量分成:無分片鍵算法、單片鍵算法、多片鍵算法。

其中 NoneKeyDatabaseShardingAlgorithm、NoneTableShardingAlgorithm 為 ShardingRule 在未設置分庫、分表算法的默認值。代碼如下:

// ShardingRule.java
public ShardingRule(
       final DataSourceRule dataSourceRule, final Collection<TableRule> tableRules, final Collection<BindingTableRule> bindingTableRules,
       final DatabaseShardingStrategy databaseShardingStrategy, final TableShardingStrategy tableShardingStrategy, final KeyGenerator keyGenerator) {
   // ... 省略部分代碼
   this.databaseShardingStrategy = null == databaseShardingStrategy ? new DatabaseShardingStrategy(
           Collections.<String>emptyList(), new NoneDatabaseShardingAlgorithm()) : databaseShardingStrategy;
   this.tableShardingStrategy = null == tableShardingStrategy ? new TableShardingStrategy(
           Collections.<String>emptyList(), new NoneTableShardingAlgorithm()) : tableShardingStrategy;
   // ... 省略部分代碼
}

《分表分庫路由》 會進一步說明。

666. 彩蛋

本文看似在水更,實是為《分表分庫路由》做鋪墊(一陣臉紅😳)。

But,無論怎么說,道友,我做了新的關注二維碼(感謝貓🐱先生),是不是可以推薦一波公眾號給基佬。

恩,繼續更新。


免責聲明!

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



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