Sharding-JDBC中的分片策略有两个维度,分别是:

  • 数据源分片策略(DatabaseShardingStrategy)
  • 表分片策略(TableShardingStrategy)

其中,数据源分片策略表示:数据路由到的物理目标数据源,表分片策略表示数据被路由到的目标表。

特别的,表分片策略是依赖于数据源分片策略的,也就是说要先分库再分表,当然也可以只分表。

Sharding-JDBC的数据分片策略

Sharding-JDBC的分片策略包含了分片键和分片算法。由于分片算法与业务实现紧密相关,因此Sharding-JDBC没有提供内置的分片算法,而是通过分片策略将各种场景提炼出来,提供了高层级的抽象,通过提供接口让开发者自行实现分片算法。

以下内容引用自官方文档。官方文档

首先介绍四种分片算法。

通过分片算法将数据分片,支持通过=、BETWEEN和IN分片。
分片算法需要应用方开发者自行实现,可实现的灵活度非常高。

目前提供4种分片算法。由于分片算法和业务实现紧密相关,
因此并未提供内置分片算法,而是通过分片策略将各种场景提炼出来,
提供更高层级的抽象,并提供接口让应用开发者自行实现分片算法。

分片键

用于分片的数据库字段,是将数据库(表)水平拆分的关键字段。例:将订单表中的订单主键的尾数取模分片,则订单主键为分片字段。 SQL中如果无分片字段,将执行全路由,性能较差。 除了对单分片字段的支持,ShardingSphere也支持根据多个字段进行分片。

分片算法

通过分片算法将数据分片,支持通过=BETWEENIN分片。分片算法需要应用方开发者自行实现,可实现的灵活度非常高。

目前提供4种分片算法。由于分片算法和业务实现紧密相关,因此并未提供内置分片算法,而是通过分片策略将各种场景提炼出来,提供更高层级的抽象,并提供接口让应用开发者自行实现分片算法。

  • 精确分片算法

对应PreciseShardingAlgorithm,用于处理使用单一键作为分片键的=与IN进行分片的场景。需要配合StandardShardingStrategy使用。

  • 范围分片算法

对应RangeShardingAlgorithm,用于处理使用单一键作为分片键的BETWEEN AND进行分片的场景。需要配合StandardShardingStrategy使用。

  • 复合分片算法

对应ComplexKeysShardingAlgorithm,用于处理使用多键作为分片键进行分片的场景,包含多个分片键的逻辑较复杂,需要应用开发者自行处理其中的复杂度。需要配合ComplexShardingStrategy使用。

  • Hint分片算法

对应HintShardingAlgorithm,用于处理使用Hint行分片的场景。需要配合HintShardingStrategy使用。

分片策略

包含分片键和分片算法,由于分片算法的独立性,将其独立抽离。真正可用于分片操作的是分片键 + 分片算法,也就是分片策略。目前提供5种分片策略。

  • 标准分片策略

对应StandardShardingStrategy。提供对SQL语句中的=, IN和BETWEEN AND的分片操作支持。StandardShardingStrategy只支持单分片键,提供PreciseShardingAlgorithm和RangeShardingAlgorithm两个分片算法。PreciseShardingAlgorithm是必选的,用于处理=和IN的分片。RangeShardingAlgorithm是可选的,用于处理BETWEEN AND分片,如果不配置RangeShardingAlgorithm,SQL中的BETWEEN AND将按照全库路由处理。

  • 复合分片策略

对应ComplexShardingStrategy。复合分片策略。提供对SQL语句中的=, IN和BETWEEN AND的分片操作支持。ComplexShardingStrategy支持多分片键,由于多分片键之间的关系复杂,因此并未进行过多的封装,而是直接将分片键值组合以及分片操作符透传至分片算法,完全由应用开发者实现,提供最大的灵活度。

  • 行表达式分片策略

对应InlineShardingStrategy。使用Groovy的表达式,提供对SQL语句中的=和IN的分片操作支持,只支持单分片键。对于简单的分片算法,可以通过简单的配置使用,从而避免繁琐的Java代码开发,如: t_user_$->{u_id % 8} 表示t_user表根据u_id模8,而分成8张表,表名称为t_user_0t_user_7

  • Hint分片策略

对应HintShardingStrategy。通过Hint而非SQL解析的方式分片的策略。

  • 不分片策略

对应NoneShardingStrategy。不分片的策略。

SQL Hint

对于分片字段非SQL决定,而由其他外置条件决定的场景,可使用SQL Hint灵活的注入分片字段。例:内部系统,按照员工登录主键分库,而数据库中并无此字段。SQL Hint支持通过Java API和SQL注释(待实现)两种方式使用。

实战–自定义复合分片策略

由于目的为贴近实战,因此着重讲解如何实现复杂分片策略,即实现ComplexShardingStrategy接口定制生产可用的分片策略。

AdminIdShardingAlgorithm 复合分片算法代码如下:

import com.google.common.collect.Range;
import io.shardingjdbc.core.api.algorithm.sharding.ListShardingValue;
import io.shardingjdbc.core.api.algorithm.sharding.PreciseShardingValue;
import io.shardingjdbc.core.api.algorithm.sharding.RangeShardingValue;
import io.shardingjdbc.core.api.algorithm.sharding.ShardingValue;
import io.shardingjdbc.core.api.algorithm.sharding.complex.ComplexKeysShardingAlgorithm;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger; import java.util.*; /**
*/
public class AdminIdShardingAlgorithm implements ComplexKeysShardingAlgorithm { private Logger logger = Logger.getLogger(getClass()); @Override
public Collection<String> doSharding(Collection<String> availableTargetNames, Collection<ShardingValue> shardingValues) {
Collection<String> routTables = new HashSet<String>();
if (shardingValues != null) {
for (ShardingValue shardingValue : shardingValues) { // eq in 条件
if (shardingValue instanceof ListShardingValue) {
ListShardingValue listShardingValue = (ListShardingValue) shardingValue;
Collection<Comparable> values = listShardingValue.getValues();
if (values != null) {
Iterator<Comparable> it = values.iterator();
while (it.hasNext()) {
Comparable value = it.next();
String routTable = getRoutTable(shardingValue.getLogicTableName(), value);
if (StringUtils.isNotBlank(routTable)) {
routTables.add(routTable);
}
}
} // eq 条件
} else if (shardingValue instanceof PreciseShardingValue) {
PreciseShardingValue preciseShardingValue = (PreciseShardingValue) shardingValue; Comparable value = preciseShardingValue.getValue();
String routTable = getRoutTable(shardingValue.getLogicTableName(), value);
if (StringUtils.isNotBlank(routTable)) {
routTables.add(routTable);
}
// between 条件
} else if (shardingValue instanceof RangeShardingValue) {
RangeShardingValue rangeShardingValue = (RangeShardingValue) shardingValue;
Range<Comparable> valueRange = rangeShardingValue.getValueRange();
Comparable lowerEnd = valueRange.lowerEndpoint();
Comparable upperEnd = valueRange.upperEndpoint(); Collection<String> tables = getRoutTables(shardingValue.getLogicTableName(), lowerEnd, upperEnd);
if (tables != null && tables.size() > 0) {
routTables.addAll(tables);
}
} if (routTables != null && routTables.size() > 0) {
return routTables;
}
}
} throw new UnsupportedOperationException(); } private String getRoutTable(String logicTable, Comparable keyValue) {
Map<String, List<KeyShardingRange>> keyRangeMap = KeyShardingRangeConfig.getKeyRangeMap(); List<KeyShardingRange> keyShardingRanges = keyRangeMap.get(KeyShardingRangeConfig.SHARDING_ID_KEY); if (keyValue != null && keyShardingRanges != null) {
if (keyValue instanceof Integer) {
keyValue = Long.valueOf(((Integer) keyValue).intValue());
}
for (KeyShardingRange range : keyShardingRanges) {
if (keyValue.compareTo(range.getMin()) >= 0 && keyValue.compareTo(range.getMax()) <= 0) {
return logicTable + range.getTableKey();
}
}
}
return null;
}
private Collection<String> getRoutTables(String logicTable, Comparable lowerEnd, Comparable upperEnd) {
Map<String, List<KeyShardingRange>> keyRangeMap = KeyShardingRangeConfig.getKeyRangeMap(); List<KeyShardingRange> keyShardingRanges = keyRangeMap.get(KeyShardingRangeConfig.SHARDING_CONTENT_ID_KEY);
Set<String> routTables = new HashSet<String>();
if (lowerEnd != null && upperEnd != null && keyShardingRanges != null) {
if (lowerEnd instanceof Integer) {
lowerEnd = Long.valueOf(((Integer) lowerEnd).intValue());
} if (upperEnd instanceof Integer) {
upperEnd = Long.valueOf(((Integer) upperEnd).intValue());
}
boolean start = false;
for (KeyShardingRange range : keyShardingRanges) {
if (lowerEnd.compareTo(range.getMin()) >= 0 && lowerEnd.compareTo(range.getMax()) <= 0) {
start = true;
}
if (start) {
routTables.add(logicTable + range.getTableKey());
}
if (upperEnd.compareTo(range.getMin()) >= 0 && upperEnd.compareTo(range.getMax()) <= 0) {
break;
}
}
}
return routTables;
}
}

范围 map 如下:

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map; /**
* 分片键分布配置
*/
public class KeyShardingRangeConfig { private static Map<String, List<KeyShardingRange>> keyRangeMap = new LinkedHashMap<String, List<KeyShardingRange>>(); public static final String SHARDING_ORDER_ID_KEY = "id"; public static final String SHARDING_USER_ID_KEY = "adminId"; public static final String SHARDING_DATE_KEY = "createTime"; static {
List<KeyShardingRange> idRanges = new ArrayList<KeyShardingRange>();
idRanges.add(new KeyShardingRange(0, "_0", 0L, 4000000L));
idRanges.add(new KeyShardingRange(1, "_1", 4000001L, 8000000L));
idRanges.add(new KeyShardingRange(2, "_2", 8000001L, 12000000L));
idRanges.add(new KeyShardingRange(3, "_3", 12000001L, 16000000L));
idRanges.add(new KeyShardingRange(4, "_4", 16000001L, 2000000L));
keyRangeMap.put(SHARDING_ID_KEY, idRanges); List<KeyShardingRange> contentIdRanges = new ArrayList<KeyShardingRange>();
contentIdRanges.add(new KeyShardingRange(0, "_0", 0L, 4000000L));
contentIdRanges.add(new KeyShardingRange(1, "_1", 4000001L, 8000000L));
contentIdRanges.add(new KeyShardingRange(2, "_2", 8000001L, 12000000L));
contentIdRanges.add(new KeyShardingRange(3, "_3", 12000001L, 16000000L));
contentIdRanges.add(new KeyShardingRange(4, "_4", 16000001L, 2000000L));
keyRangeMap.put(SHARDING_CONTENT_ID_KEY, contentIdRanges); List<KeyShardingRange> timeRanges = new ArrayList<KeyShardingRange>();
timeRanges.add(new KeyShardingRange("_0", 20170701L, 20171231L));
timeRanges.add(new KeyShardingRange("_1", 20180101L, 20180630L));
timeRanges.add(new KeyShardingRange("_2", 20180701L, 20181231L));
timeRanges.add(new KeyShardingRange("_3", 20190101L, 20190630L));
timeRanges.add(new KeyShardingRange("_4", 20190701L, 20191231L));
keyRangeMap.put(SHARDING_DATE_KEY, timeRanges);
} public static Map<String, List<KeyShardingRange>> getKeyRangeMap() {
return keyRangeMap;
}
}

核心逻辑解析

梳理一下逻辑,首先介绍一下该方法的入参

参数名                                                  解释


availableTargetNames                有效的物理数据源,即配置文件中的 t_order_0,t_order_1,t_order_2,t_order_3

shardingValues                           分片属性,如:{“columnName”:”order_id”,”logicTableName”:”t_order”,”values”:[“UD020003011903261545436593200002”]} ,包含:分片列名,逻辑表名,当前列的具体分片值


该方法返回值为

参数名                                                       解释


Collection<String>                    分片结果,可以是目标数据源,也可以是目标数据表,此处为数据源


接着回来看业务逻辑,伪代码如下

首先打印了一下数据源集合 availableTargetNames 以及 分片属性 shardingValues的值,执行测试用例后,日志输出为:

availableTargetNames:["t_order_0","t_order_1","t_order_2","t_order_3"],
shardingValues:[{"columnName":"user_id","logicTableName":"t_order","values":["UD020003011903261545436593200002"]},
{"columnName":"order_id","logicTableName":"t_order","values":["OD000000011903261545475143200001"]}]

从日志可以看出,我们可以在该路由方法中取到配置时的物理数据源列表,以及在运行时获取本次执行时的路由属性及其值

完整的逻辑流程如下:

  1. 定义一个集合用于放置最终匹配好的路由数据源,接着对shardingValues进行遍历,目的为至少命中一个路由键
  2. 遍历shardingValues循环体中,打印了当前循环的shardingValue,即实际的分片键的数值,如:订单号、用户id等。通过getIndex方法,获取该分片键值中包含的物理数据源索引
  3. 接着遍历数据源列表availableTargetNames,截取当前循环对应availableTargetName的索引值,(eg: ds0则取0,ds1则取1…以此类推)将该配置的物理数据源索引与 第2步 中解析到的数据源路由索引进行比较,两者相等则表名我们期望将该数据路由到该匹配到的数据源。
  4. 执行这个过程,直到匹配到一个路由键则停止循环,之所以这么做是因为我们是复合分片,至少要匹配到一个路由规则,才能停止循环,最终将路由到的物理数据源(ds0/ds1/ds2/ds3)通过add方法添加到事先定义好的集合中并返回给框架。
  5. 逻辑结束。

小结

本文中,基本完成了Sharding-JDBC中复合分片路由算法的自定义实现,并经过测试验证符合预期,该实现方案在生产上已经经历过考验。定义分片路由策略的核心还是要熟悉ComplexKeysShardingAlgorithm,对如何解析 doSharding(Collection availableTargetNames, CollectionshardingValues)的参数有明确的认识,最简单的方法就是实际打印一下参数,相信会让你更加直观的感受到作者优良的接口设计能力,站在巨人的肩膀上我们能看到更远。

Sharding-Jdbc 自定义分库分表-复合分片算法自定义实现的更多相关文章

  1. Sharding Sphere的分库分表

    什么是 ShardingSphere? 1.一套开源的分布式数据库中间件解决方案 2.有三个产品:Sharding-JDBC 和 Sharding-Proxy 3.定位为关系型数据库中间件,合理在分布 ...

  2. EasySharding.EFCore 如何设计使用一套代码完成的EFCore Migration 构建Saas系统多租户不同业务需求且满足租户自定义分库分表、数据迁移能力?

    下面用一篇文章来完成这些事情 多租户系统的设计单纯的来说业务,一套Saas多租户的系统,面临很多业务复杂性,不同的租户存在不同的业务需求,大部分相同的表结构,那么如何使用EFCore来完成这样的设计呢 ...

  3. mycat是什么?你是怎么理解的?你们公司分库分表的分片规则是什么?搭建mycat环境常用的配置文件有哪些?

    1.mycat是什么? 国内最活跃的.性能最好的开源数据库分库分表中间件 一个彻底开源的,面向企业应用开发的大数据库集群 支持事务.ACID.可以替代MySQL的加强版数据库 一个可以视为MySQL集 ...

  4. 分库分表后跨分片查询与Elastic Search

    携程酒店订单Elastic Search实战:http://www.lvesu.com/blog/main/cms-610.html 为什么分库分表后不建议跨分片查询:https://www.jian ...

  5. 分布式数据库中间件 MyCat | 分库分表实践

    MyCat 简介 MyCat 是一个功能强大的分布式数据库中间件,是一个实现了 MySQL 协议的 Server,前端人员可以把它看做是一个数据库代理中间件,用 MySQL 客户端工具和命令行访问:而 ...

  6. 使用TiDB把自己写分库分表方案推翻了

    背景 在日益数据量增长的情况下,影响数据库的读写性能,我们一般会有分库分表的方案和使用newSql方案,newSql如TIDB.那么为什么需要使用TiDB呢?有什么情况下才用TiDB呢?解决传统分库分 ...

  7. Sharding-jdbc(一)分库分表理解

    1.什么是分库分表 所谓的分库分表就是数据的分片(Sharding). 2.为什么需要分库分表 因为随着公司的业务越来越大,对于现成单机单个应用瓶颈问题,对数据持久化硬盘如何进行扩容. 可以从4个方面 ...

  8. 《MyCat分库分表策略详解》

    在我们的项目发展到一定阶段之后,随着数据量的增大,分库分表就变成了一件非常自然的事情.常见的分库分表方式有两种:客户端模式和服务器模式,这两种的典型代表有sharding-jdbc和MyCat.所谓的 ...

  9. 分库分表技术演进&最佳实践

    每个优秀的程序员和架构师都应该掌握分库分表,这是我的观点. 移动互联网时代,海量的用户每天产生海量的数量,比如: 用户表 订单表 交易流水表 以支付宝用户为例,8亿:微信用户更是10亿.订单表更夸张, ...

随机推荐

  1. day 03 作业 预科

    目录 作业 1.简述变量的组成 2.简述变量名的命名规范 3.简述注释的作用 4.使用turtle库构造一幅图,贴在markdown文档中 作业 1.简述变量的组成 变量由变量名.赋值符号.变量值所组 ...

  2. Python入门篇-递归函数Recursion

    Python入门篇-递归函数(recursion) 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.递归概述 (1)函数直接或者间接调用自身就是递归: (2)递归需要有边界,递归 ...

  3. video基础介绍&封装react-video基础组件,ES6

    好几个月没有写博客了,人都赖了,今天抽了一点时间把最近项目react中video整理了一下(感觉这个以后用的活比较多) 1.前三部部分详细归纳了video的基础知识,属性和功能: 2.第四部分是封装了 ...

  4. HDU1395 2^x mod n = 1——积与余数的性质

    对于数论的学习比较的碎片化,所以开了一篇随笔来记录一下学习中遇到的一些坑,主要通过题目来讲解 本题围绕:积与余数 HDU1395 2^x mod n = 1 题目描述 输入一个数n,如果存在2的x次方 ...

  5. RippleNet: Propagating User Preferences on the Knowledge Graph for Recommender Systems

    一.摘要 为了解决协同过滤的稀疏性和冷启动问题,社交网络或项目属性等辅助信息被用来提高推荐性能. 考虑到知识图谱是边信息的来源,为了解决现有的基于嵌入和基于路径的知识图谱感知重构方法的局限性,本文提出 ...

  6. 关于vue的v-for遍历不显示问题

    实属不才,因为好久没看vue导致忘光了,然后发生了这么小的一个问题,惭愧. 注:vue的注册的el一定要放嘴最外层,不要和v-for放在一起,否则不会显示,因为可以这样讲,el包含的是一个容器,而v- ...

  7. 10. vue-router命名路由

    命名路由的配置规则 为了更加方便的表示路由的路径,可以给路由规则起一个别名, 即为"命名路由". const router = new VueRouter ({ routes: [ ...

  8. Linux shell脚本基础学习详细介绍(完整版)一

    Linux shell脚本基础学习这里我们先来第一讲,介绍shell的语法基础,开头.注释.变量和 环境变量,向大家做一个基础的介绍,虽然不涉及具体东西,但是打好基础是以后学习轻松地前提.1. Lin ...

  9. 更新GitHub上自己 Fork 的代码与原作者的项目进度一致

    在GitHub上我们会去fork别人的一个项目,这就在自己的Github上生成了一个与原作者项目互不影响的副本,自己可以将自己Github上的这个项目再clone到本地进行修改,修改后再push,只有 ...

  10. Git的个人总结

    Git Git简史: 同生活中的许多伟大事物一样,Git 诞生于一个极富纷争大举创新的年代. Linux 内核开源项目有着为数众多的参与者. 绝大多数的 Linux 内核维护工作都花在了提交补丁和保存 ...