数据库分库分表中间件 Sharding-JDBC 源码分析 —— SQL 路由(一)之分库分表配置

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

  • 1. 概述
  • 2. TableRule
    • 2.2.1 DataNode
    • 2.2.2 DynamicDataNode
    • 2.1 logicTable
    • 2.2 数据单元
    • 2.3 分库/分表策略
    • 2.4 主键生成
  • 3. ShardingRule
    • 3.1 dataSourceRule
    • 3.2 tableRules
    • 3.3 bindingTableRules
  • 4. ShardingStrategy
  • 5. ShardingAlgorithm

1. 概述

?《SQL 解析》 已经告于段落,我们要开始新的旅程:《SQL 路由》。相比SQL解析,路由会容易理解很多,骗人是小?。整个系列预计会拆分成三小篇文章:

  1. 《分库分表配置》
  2. 《分表分库路由》
  3. 《Spring与YAML配置》

第一、二篇会在近期更新。第三篇会在《SQL 改写》、《SQL 执行》完成后进行更新。?改写和执行相对有趣。

?道友,您看,逗比博主“很有规划”,是关注公众号一波【芋道源码】还是分享朋友圈。


阅读本文之前,建议已经读过官方相关文章:

  • 《Sharding-JDBC 核心概念》
  • 《Sharding-JDBC 分表分库》

分表分库配置会涉及如下类:

  • TableRule 表规则配置对象
  • ShardingRule 分库分表规则配置对象
  • ShardingStrategy 分片策略
  • ShardingAlgorithm 分片算法

2. TableRule

TableRule,表规则配置对象,内嵌 TableRuleBuilder 对象进行创建。

2.1 logicTable

数据分片的逻辑表,对于水平拆分的数据库(表),同一类表的总称。 例:订单数据根据主键尾数拆分为10张表,分别是torder0到torder9,他们的逻辑表名为t_order。

2.2 数据单元

Sharding-JDBC 有两种类型数据单元

  • DataNode :静态分库分表数据单元

数据分片的最小单元,由数据源名称和数据表组成。 例:ds1.torder0。配置时默认各个分片数据库的表结构均相同,直接配置逻辑表和真实表对应关系即可。 如果各数据库的表结果不同,可使用ds.actualtable配置。

  • 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;
   // ... 省略部分代码
}

《分表分库路由》 会进一步说明。

原文发布于微信公众号 - 芋道源码(YunaiV)

原文发表时间:2017-09-02

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏orientlu

FreeRTOS 软定时器实现

考虑平台硬件定时器个数限制的, FreeRTOS 通过一个 Daemon 任务(启动调度器时自动创建)管理软定时器, 满足用户定时需求. Daemon 任务会在...

20320
来自专栏王亚昌的专栏

Linux进程同步机制-Futex

引子 在编译2.6内核的时候,你会在编译选项中看到[*] Enable futex support这一项,上网查,有的资料会告诉你"不选这个内核不一定能正确的运...

1.3K10
来自专栏人工智能LeadAI

今天不如来复习下Python基础

01 python是什么? Python是一种解释型语言。这就是说,与C语言和C的衍生语言不同,Python代码在运行之前不需要编译。其他解释型语言还包括PH...

42550
来自专栏软件开发

MyBatis学习总结(一)——ORM概要与MyBatis快速入门

程序员应该将核心关注点放在业务上,而不应该将时间过多的浪费在CRUD中,多数的ORM框架都把增加、修改与删除做得非常不错了,然后数据库中查询无疑是使用频次最高、...

20830
来自专栏程序员的SOD蜜

.NET ORM 的 “SOD蜜”--零基础入门篇

PDF.NET SOD框架不仅仅是一个ORM,但是它的ORM功能是独具特色的,我在博客中已经多次介绍,但都是原理性的,可能不少初学的朋友还是觉得复杂,其实,SO...

30370
来自专栏佳爷的后花媛

php基础(二)

输出b,if中的空值赋值给$num,因此if条件必定为false,还有其他一些大同小异的题目,用=和==判断for循环的,只要把握好基本的概念就行了

34320
来自专栏岑玉海

hbase源码系列(七)Snapshot的过程

  在看这一章之前,建议大家先去看一下snapshot的使用。可能有人会有疑问为什么要做Snapshot,hdfs不是自带了3个备份吗,这是个很大的误区,要知道...

35960
来自专栏用户2442861的专栏

mongodb操作(概述以及相关的命令)

http://blog.csdn.net/ljfbest/article/details/11979609

10020
来自专栏用户2442861的专栏

百度2014软件开发工程师笔试题详解

1.有一个数据A = [a_1,a_2,a_3.....a_n],n的大小不定,请设计算法将A中的所有数据组合进行输出

34320
来自专栏Albert陈凯

Spark详解07广播变量BroadcastBroadcast

Broadcast 顾名思义,broadcast 就是将数据从一个节点发送到其他各个节点上去。这样的场景很多,比如 driver 上有一张表,其他节点上运行的 ...

41060

扫码关注云+社区

领取腾讯云代金券