前提介绍
承接上一篇文章:(【ShardingSphere技术专题】「ShardingJDBC」(1)带你一同认识一下ShardingJDBC是什么?(高手勿入)),让我们了解了相关ShardingJDBC的作用和功能定位,以及技术本质,针对于它最核心的分片技术,接下来我们需要针对于ShardingJDBC的分片的核心概念进行介绍!
ShardingJDBC-表模型
ShardingJDBC的逻辑表概念,屏蔽下面的分片技术!
逻辑表
- 水平拆分的数据库(表)的相同逻辑和数据结构表的总称。例:用户数据根据主键尾数拆分为 10 张表,分别是userinfo_0 到 userinfo_9,他们的逻辑表名为 userinfo。
物理表
- 在分片的数据库中真实存在的物理表。即上个示例中的 userinfo_0 到 userinfo_9。
数据节点
- 数据分片的最小单元。由数据源名称和数据表组成,例:ds0.userinfo_0。
绑定表
指分片规则一致的主表和子表。
例如:userInfo 表和 userInfo_extend 表,均按照 user_id 分片,则此两张表互为绑定表关系。
绑定表之间的多表关联查询不会出现笛卡尔积关联,关联查询效率将大大提升。
举例说明,如果SQL 为:
SELECT i.* FROM userInfo o JOIN userInfo_extend i ON o.user_id=i.user_id WHERE o.user_id in (10, 11);
- 在不配置绑定表关系时,假设分片键user_id 将数值 10 路由至第 0 片,将数值 11 路由至第 1 片,那么路由后的 SQL 应该为 4 条,它们呈现为笛卡尔积:
SELECT i.* FROM userInfo_0 o JOIN userInfo_extend_0 i ON o.user_id=i.user_id WHERE o.user_id in (10, 11);
SELECT i.* FROM userInfo_0 o JOIN userInfo_extend_1 i ON o.user_id=i.user_id WHERE o.user_id in (10, 11);
SELECT i.* FROM userInfo_1 o JOIN userInfo_extend_0 i ON o.user_id=i.user_id WHERE o.user_id in (10, 11);
SELECT i.* FROM userInfo_1 o JOIN userInfo_extend_1 i ON o.user_id=i.user_id WHERE o.user_id in (10, 11);
- 在配置绑定表关系后,路由的 SQL 应该为 2 条:
SELECT i.* FROM userInfo_0 o JOIN userInfo_extend_0 i ON o.user_id=i.user_id WHERE o.user_id in (10, 11);
SELECT i.* FROM userInfo_1 o JOIN userInfo_extend_1 i ON o.user_id=i.user_id WHERE o.user_id in (10, 11);
其中 userInfo 在 FROM 的最左侧,ShardingSphere将会以它作为整个绑定表的主表。
所有路由计算将会只使用主表的策略,那么 userInfo_extend 表的分片计算将会使用 userInfo 的条件。故绑定表之间的分区键要完全相同。
广播表
指所有的分片数据源中都存在的表,表结构和表中的数据在每个数据库中均完全一致。适用于数据量不大且需要与海量数据的表进行关联查询的场景,例如:字典表、配置信息表。
单表
指所有的分片数据源中只存在唯一一张的表。适用于数据量不大且不需要做任何分片操作的场景。
ShardingJDBC-分片配置
分片策略配置
对于分片策略存有数据源分片策略和表分片策略两种维度。
分片键
主要用于分片的数据库字段,是将数据库(表)水平拆分的关键字段。例:将用户表中的user主键的尾数取模分片,则user主键为分片字段。
SQL中如果无分片字段,将执行全路由,性能较差。 除了对单分片字段的支持,Apache ShardingSphere 也支持根据多个字段进行分片。
分片算法
通过分片算法将数据分片,支持通过 =、>=、<=、>、<、BETWEEN 和 IN 分片。 分片算法需要应用方开发者自行实现,可实现的灵活度非常高。
- 目前提供3种分片算法。 由于分片算法和业务实现紧密相关,因此并未提供内置分片算法,而是通过分片策略将各种场景提炼出来,提供更高层级的抽象,并提供接口让应用开发者自行实现分片算法。
标准分片算法(StandardShardingAlgorithm)
用于处理使用单一键作为分片键的 =、IN、BETWEEN AND、>、<、>=、<= 进行分片的场景,需要配合StandardShardingStrategy使用。
复合分片算法(ComplexKeysShardingAlgorithm)
用于处理使用多键作为分片键进行分片的场景,包含多个分片键的逻辑较复杂,需要应用开发者自行处理其中的复杂度。需要配合 ComplexShardingStrategy 使用。
Hint分片算法(HintShardingAlgorithm)
用于处理使用 Hint 行分片的场景。需要配合 HintShardingStrategy 使用,具体未来会进行详细介绍!
分片策略
包含分片键和分片算法,由于分片算法的独立性,将其独立抽离。
真正可用于分片操作的是分片键 + 分片算法,也就是分片策略。目前提供 4 种分片策略。
标准分片策略
对应 StandardShardingStrategy:提供对 SQL 语句中的 =, >, <, >=, <=, IN 和 BETWEEN AND 的分片操作支持。
- StandardShardingStrategy 只支持单分片键,提供 PreciseShardingAlgorithm 和 RangeShardingAlgorithm 两个分片算法。
- PreciseShardingAlgorithm 是必选的,用于处理 = 和 IN 的分片。
- RangeShardingAlgorithm 是可选的,用于处理 BETWEEN AND, >, <, >=, <= 分片,如果不配置 RangeShardingAlgorithm,SQL 中的 BETWEEN AND 将按照全库路由处理。
复合分片策略
对应ComplexShardingStrategy,复合分片策略,提供对 SQL 语句中的 =, >, <, >=, <=, IN和BETWEEN AND 的分片操作支持。
ComplexShardingStrategy支持多分片键,由于多分片键之间的关系复杂,因此并未进行过多的封装,而是直接将分片键值组合以及分片操作符透传至分片算法,完全由应用开发者实现,提供最大的灵活度。
Hint分片策略
对应 HintShardingStrategy。通过 Hint 指定分片值而非从 SQL 中提取分片值的方式进行分片的策略。
不分片策略
对应 NoneShardingStrategy。不分片的策略。
SQL Hint
对于分片字段非SQL决定,而由其他外置条件决定的场景,可使用 SQL Hint 灵活的注入分片字段。 例:内部系统,按照员工登录主键分库,而数据库中并无此字段。SQL Hint 支持通过 Java API 和 SQL 注释(待实现)两种方式使用。
实现动机
通过解析 SQL 语句提取分片键列与值并进行分片是 Apache ShardingSphere 对 SQL 零侵入的实现方式。若 SQL 语句中没有分片条件,则无法进行分片,需要全路由。
在一些应用场景中,分片条件并不存在于 SQL,而存在于外部业务逻辑。因此需要提供一种通过外部指定分片结果的方式,在 Apache ShardingSphere 中叫做 Hint。
实现机制
Apache ShardingSphere 使用 ThreadLocal 管理分片键值。可以通过编程的方式向 HintManager 中添加分片条件,该分片条件仅在当前线程内生效。
除了通过编程的方式使用强制分片路由,Apache ShardingSphere 还计划通过 SQL 中的特殊注释的方式引用 Hint,使开发者可以采用更加透明的方式使用该功能。
指定了强制分片路由的 SQL 将会无视原有的分片逻辑,直接路由至指定的真实数据节点。
分片规则
分片规则配置的总入口。包含数据源配置、表配置、绑定表配置以及读写分离配置等。
数据源配置
系统配置的相关的数据源信息列表。
表配置
- 逻辑表名称、数据节点与分表规则的配置。
数据节点配置
- 用于配置逻辑表与真实表的映射关系。可分为均匀分布和自定义分布两种形式。
均匀分布
- 指数据表在每个数据源内呈现均匀分布的态势,例如:
db0
├── userInfo_0
└── userInfo_1
db1
├── userInfo_0
└── userInfo_1
- 那么数据节点的配置如下:
db0.userInfo_0, db0.userInfo_1, db1.userInfo_0, db1.userInfo_1
自定义分布
指数据表呈现有特定规则的分布,例如:
db0
├── userInfo_0
└── userInfo_1
db1
├── userInfo_3
└── userInfo_4
那么数据节点的配置如下:
db0.userInfo_0, db0.userInfo_1, db1.userInfo_3, db1.userInfo_4
数据源分片策略
对应于DatabaseShardingStrategy。用于配置数据被分配的目标数据源。
表分片策略
对应于TableShardingStrategy。用于配置数据被分配的目标表,该目标表存在于该数据的目标数据源内。故表分片策略是依赖于数据源分片策略的结果的。
两种策略的 API 完全相同。
自增主键生成策略
通过在客户端生成自增主键替换以数据库原生自增主键的方式,做到分布式主键无重复。
分片计算表达式
实现动机
配置的简化与一体化是行表达式所希望解决的两个主要问题。
在繁琐的数据分片规则配置中,随着数据节点的增多,大量的重复配置使得配置本身不易被维护。通过行表达式可以有效地简化数据节点配置工作量。
对于常见的分片算法,使用 Java 代码实现并不有助于配置的统一管理。通过行表达式书写分片算法,可以有效地将规则配置一同存放,更加易于浏览与存储。
语法说明
目前支持数据节点和分片算法这两个部分的配置。
行表达式的内容使用的是 Groovy 的语法,Groovy 能够支持的所有操作,行表达式均能够支持。只需要在配置中使用 ->{ expression } 标识行表达式即可。例如:
${begin..end} 表示范围区间
${[unit1, unit2, unit_x]} 表示枚举值
行表达式中如果出现连续多个->{ expression } 表达式,整个表达式最终的结果将会根据每个子表达式的结果进行笛卡尔组合。
行表达式案例
${['a', 'b']}_table${1..3}
最终会解析为:
a_table1, a_table2, a_table3, b_table1, b_table2, b_table3
配置数据节点
对于均匀分布的数据节点,如果数据结构如下:
db0
├── userInfo_0
└── userInfo_1
db1
├── userInfo_3
└── userInfo_4
db0.userinfo_${0..1},db1.userinfo_${0..1}
用行表达式可以简化为:
db${0..1}.userinfo_${0..1}
- 或者
db$->{0..1}.userinfo_$->{0..1}
对于自定义的数据节点,如果数据结构如下:
db0
├── userInfo_0
└── userInfo_1
db1
├── userInfo_0
└── userInfo_1
可以使用分开配置的方式,先配置包含前缀的数据节点,再配置不含前缀的数据节点,再利用行表达式笛卡尔积的特性,自动组合即可。 上面的示例,用行表达式可以简化为:
db${0..1}.t_order_0${0..9}, db${0..1}.t_order_${10..20}
或者
db$->{0..1}.t_order_0$->{0..9}, db$->{0..1}.t_order_$->{10..20}
配置分片算法
对于只有一个分片键的使用 = 和 IN 进行分片的 SQL,可以使用行表达式代替编码方式配置。
行表达式内部的表达式本质上是一段 Groovy 代码,可以根据分片键进行计算的方式,返回相应的真实数据源或真实表名称。
例如:分为 10 个库,尾数为 0 的路由到后缀为 0 的数据源, 尾数为 1 的路由到后缀为 1 的数据源,以此类推。用于表示分片算法的行表达式为:
ds${id % 10}
或者
ds$->{id % 10}