大家好,我是小富~

前言

本文是《ShardingSphere5.x分库分表原理与实战》系列的第六篇,书接上文实现三种自定义分片算法。通过自定义算法,可以根据特定业务需求定制分片策略,以满足不同场景下的性能、扩展性或数据处理需求。同时,可以优化分片算法以提升系统性能,规避数据倾斜等问题。

在这里,自定义分片算法的类型(Type)统一为CLASS_BASED,包含两个属性:strategy 表示分片策略类型,目前支持三种:STANDARDCOMPLEXHINTalgorithmClassName 表示自定义分片算法的实现类路径。此外,还可以向算法类内传入自定义属性。

自定义 STANDARD 算法

要实现自定义 STANDARD 标准算法,需要实现StandardShardingAlgorithm<T>接口( T 代表接收的分片健值类型),并重写接口中的四个方法。其中,有两个 doSharding() 方法为处理分片的核心逻辑;getProps() 方法用于获取分片算法的配置信息;init() 方法则用于初始化分片算法的配置信息,支持动态修改。

5.X 以后的版本,实现自定义标准算法的精准分片和范围分片,不在需要实现多个接口。只用实现 StandardShardingAlgorithm 标准算法接口,重写两个 doSharding() 方法。 doSharding(availableTargetNames,rangeShardingValue) 处理含有 >、<、between and 等操作符的 SQL,doSharding(availableTargetNames,preciseShardingValue) 处理含有 = 、in 等操作符的 SQL。

精准分片

精准分片用于SQL中包含 in、= 等操作符的场景,支持单一分片健。

重写方法 doSharding(Collection availableTargetNames, PreciseShardingValue preciseShardingValue),该方法返回单一的分片数据源或分片表数据。有两个参数:一个是可用目标分库、分表的集合,另一个是精准分片属性对象。

PreciseShardingValue 对象属性数据格式如下:

{
"columnName": "order_id", // 分片健
"dataNodeInfo": {
"paddingChar": "0",
"prefix": "db", // 数据节点信息前缀,例如:分库时为db,分表时为分片表t_order_
"suffixMinLength": 1
},
"logicTableName": "t_order", // 逻辑表
"value": 1 // 分片健值
}

范围分片

范围分片用于 SQL中包含 >、< 等范围操作符的场景,支持单一分片健。

重写方法 doSharding(Collection availableTargetNames, RangeShardingValue rangeShardingValue),该方法可以返回多个分片数据源或分片表数据。有两个参数:一个是可用目标分库、分表的集合,另一个是精准分片属性对象。

RangeShardingValue 对象属性数据格式如下:

{
"columnName": "order_id", // 分片健
"dataNodeInfo": {
"paddingChar": "0",
"prefix": "db", // 数据节点前缀,分库时为数据源,分表时为分片表t_order_
"suffixMinLength": 1
},
"logicTableName": "t_order", // 逻辑表
"valueRange": [0,∞] // 分片健值的范围数据
}

精准分片算法的 doSharding() 执行流程:从PreciseShardingValue.getValue()中获取分片键值,然后经过计算得出相应编号,最终在availableTargetNames可用目标分库、分片表集合中选择以一个符合的返回。

范围分片算法的 doSharding() 执行流程:从RangeShardingValue.getValueRange()方法获取分片键的数值范围,然后经过计算得出相应编号,最终在availableTargetNames可用目标分库、分片表集合中选择多个符合的返回。

下面是具体实现分片的逻辑:

/**
* 自定义标准分片算法
*
* @author 公众号:程序员小富
* @date 2024/03/22 11:02
*/
@Slf4j
public class OrderStandardCustomAlgorithm implements StandardShardingAlgorithm<Long> { /**
* 精准分片进入 sql中有 = 和 in 等操作符会执行
*
* @param availableTargetNames 所有分片表的集合
* @param shardingValue 分片健的值,SQL中解析出来的分片值
*/
@Override
public String doSharding(Collection<String> availableTargetNames,
PreciseShardingValue<Long> shardingValue) {
/**
* 分库策略使用时:availableTargetNames 参数数据为分片库的集合 ["db0","db1"]
* 分表策略使用时:availableTargetNames 参数数据为分片库的集合 ["t_order_0","t_order_1","t_order_2"]
*/
log.info("进入精准分片 precise availableTargetNames:{}", JSON.toJSONString(availableTargetNames)); /**
* 分库策略使用时: shardingValue 参数数据:{"columnName":"order_id","dataNodeInfo":{"paddingChar":"0","prefix":"db","suffixMinLength":1},"logicTableName":"t_order","value":1}
* 分表策略使用时: shardingValue 参数数据:{"columnName":"order_id","dataNodeInfo":{"paddingChar":"0","prefix":"t_order_","suffixMinLength":1},"logicTableName":"t_order","value":1}
*/
log.info("进入精准分片 preciseShardingValue:{}", JSON.toJSONString(shardingValue));
int tableSize = availableTargetNames.size();
// 真实表的前缀
String tablePrefix = shardingValue.getDataNodeInfo().getPrefix();
// 分片健的值
long orderId = shardingValue.getValue();
// 对分片健取模后确定位置
long mod = orderId % tableSize;
return tablePrefix + mod;
} /**
* 范围分片进入 sql中有 between 和 < > 等操作符会执行
*
* @param availableTargetNames 所有分片表的集合
* @param shardingValue 分片健的值,SQL中解析出来的分片值
* @return
*/
@Override
public Collection<String> doSharding(Collection<String> availableTargetNames,
RangeShardingValue<Long> shardingValue) {
/**
* 分库策略使用时:availableTargetNames 参数数据为分片库的集合 ["db0","db1"]
* 分表策略使用时:availableTargetNames 参数数据为分片库的集合 ["t_order_0","t_order_1","t_order_2"]
*/
log.info("进入范围分片:range availableTargetNames:{}", JSON.toJSONString(availableTargetNames)); /**
* 分库策略使用时 shardingValue 参数数据:{"columnName":"order_id","dataNodeInfo":{"paddingChar":"0","prefix":"db","suffixMinLength":1},"logicTableName":"t_order","valueRange":{"empty":false}}
* 分表策略使用时 shardingValue 参数数据:{"columnName":"order_id","dataNodeInfo":{"paddingChar":"0","prefix":"t_order_","suffixMinLength":1},"logicTableName":"t_order","valueRange":{"empty":false}}
*/
log.info("进入范围分片:rangeShardingValue:{}", JSON.toJSONString(shardingValue));
// 分片健值的下边界
Range<Long> valueRange = shardingValue.getValueRange();
Long lower = valueRange.lowerEndpoint();
// 分片健值的上边界
Long upper = valueRange.upperEndpoint();
// 真实表的前缀
String tablePrefix = shardingValue.getDataNodeInfo().getPrefix();
if (lower != null && upper != null) {
// 分片健的值
long orderId = upper - lower;
// 对分片健取模后确定位置
long mod = orderId % availableTargetNames.size();
return Arrays.asList(tablePrefix + mod);
}
//
return Collections.singletonList("t_order_0");
} @Override
public Properties getProps() {
return null;
} /**
* 初始化配置
*
* @param properties
*/
@Override
public void init(Properties properties) {
Object prop = properties.get("prop");
log.info("配置信息:{}", JSON.toJSONString(prop));
}
}

配置算法

在实现了自定义分片算法的两个 doSharding() 核心逻辑之后,接着配置并使用定义的算法。配置属性包括strategy分片策略类型设置成standardalgorithmClassName自定义标准算法的实现类全路径。需要注意的是:策略和算法类型必须保持一致,否则会导致错误

spring:
shardingsphere:
rules:
sharding:
# 分片算法定义
sharding-algorithms:
t_order_database_mod:
type: MOD
props:
sharding-count: 2 # 指定分片数量
# 12、自定义 STANDARD 标准算法
t_order_standard_custom_algorithm:
type: CLASS_BASED
props:
# 分片策略
strategy: standard
# 分片算法类
algorithmClassName: com.shardingsphere_101.algorithm.OrderStandardCustomAlgorithm
# 自定义属性
prop:
aaaaaa: 123456
bbbbbb: 654321
tables:
# 逻辑表名称
t_order:
# 数据节点:数据库.分片表
actual-data-nodes: db$->{0..1}.t_order_${0..2}
# 分库策略
database-strategy:
standard:
sharding-column: order_id
sharding-algorithm-name: t_order_database_mod
# 分表策略
table-strategy:
standard:
sharding-column: order_id
sharding-algorithm-name: t_order_standard_custom_algorithm

测试算法

在插入测试数据时,默认会自动进入精确分片的 doSharding() 方法内,看到该方法会获取分片键的数值,根据我们的计算规则确定返回一个目标分片表用于路由。

接着执行一个范围查询的 SQL,此时将进入范围分片的 doSharding() 方法。通过观察 shardingValue.getValueRange() 方法中分片键的数值范围,可以发现这些数值范围是从SQL查询中解析得到的。

select * from t_order where order_id > 1 and order_id < 10

自定义 COMPLEX 算法

复合分片算法支持包含 >,>=, <=,<,=,IN 和 BETWEEN AND 等操作符的SQL,支持多分片健。

自定义COMPLEX复合分片算法,需要我们实现 ComplexKeysShardingAlgorithm<T> 接口(其中 T 代表接收的分片键值类型),并重写该接口内部的 3 个方法。其中,主要关注用于处理核心分片逻辑的 doSharding()方法,可以返回多个分片数据源或分片表数据;其他两个配置方法与上述类似,这里不再赘述。

重写复合分片方法 doSharding(Collection availableTargetNames, ComplexKeysShardingValue shardingValues) 实现定制的多分片健逻辑,该方法有两个参数:一个是可用目标分库、分表的集合;另一个是多分片健属性对象。

logicTableName为逻辑表名,columnNameAndShardingValuesMap用于存储多个分片键和对应的键值,columnNameAndRangeValuesMap用于存储多个分片键和对应的键值范围。

ComplexKeysShardingValue数据结构如下:

public final class ComplexKeysShardingValue<T extends Comparable<?>> implements ShardingValue {
// 逻辑表
private final String logicTableName;
// 多分片健及其数值
private final Map<String, Collection<T>> columnNameAndShardingValuesMap;
// 多分片健及其范围数值
private final Map<String, Range<T>> columnNameAndRangeValuesMap;
}

核心流程:通过循环 Map 得到多个分片健值进行计算,从 availableTargetNames 可用目标分库、分片表集合中选择多个符合条件的返回。

/**
* 自定义复合分片算法
*
* @author 公众号:程序员小富
* @date 2024/03/22 11:02
*/
@Slf4j
public class OrderComplexCustomAlgorithm implements ComplexKeysShardingAlgorithm<Long> { /**
* 复合分片算法进入,支持>,>=, <=,<,=,IN 和 BETWEEN AND 等操作符
*
* @param availableTargetNames 所有分片表的集合
* @param complexKeysShardingValue 多个分片健的值,并SQL中解析出来的分片值
*/
@Override
public Collection<String> doSharding(Collection<String> availableTargetNames,
ComplexKeysShardingValue<Long> complexKeysShardingValue) { /**
* 分库策略使用时:availableTargetNames 参数数据为分片库的集合 ["db0","db1"]
* 分表策略使用时:availableTargetNames 参数数据为分片库的集合 ["t_order_0","t_order_1","t_order_2"]
*/
log.info("进入复合分片:complex availableTargetNames:{}", JSON.toJSONString(availableTargetNames)); // 多分片健和其对应的分片健范围值
Map<String, Range<Long>> columnNameAndRangeValuesMap = complexKeysShardingValue.getColumnNameAndRangeValuesMap();
log.info("进入复合分片:columnNameAndRangeValuesMap:{}", JSON.toJSONString(columnNameAndRangeValuesMap)); columnNameAndRangeValuesMap.forEach((columnName, range) -> {
// 分片健
log.info("进入复合分片:columnName:{}", columnName);
// 分片健范围值
log.info("进入复合分片:range:{}", JSON.toJSONString(range));
}); // 多分片健和其对应的分片健值
Map<String, Collection<Long>> columnNameAndShardingValuesMap = complexKeysShardingValue.getColumnNameAndShardingValuesMap();
log.info("进入复合分片:columnNameAndShardingValuesMap:{}", JSON.toJSONString(columnNameAndShardingValuesMap));
columnNameAndShardingValuesMap.forEach((columnName, shardingValues) -> {
// 分片健
log.info("进入复合分片:columnName:{}", columnName);
// 分片健值
log.info("进入复合分片:shardingValues:{}", JSON.toJSONString(shardingValues));
}); return null;
}
}

配置算法

处理完复合分片算法的doSharding()核心逻辑,接着配置使用定义的算法,配置属性包括strategy分片策略类型设置成complexalgorithmClassName自定义算法的实现类全路径。

需要注意:配置分片键时,一定要使用 sharding-columns 表示复数形式,很容易出错。

spring:
shardingsphere:
rules:
sharding:
sharding-algorithms:
t_order_database_mod:
type: MOD
props:
sharding-count: 2 # 指定分片数量
# 13、自定义 complex 标准算法
t_order_complex_custom_algorithm:
type: CLASS_BASED
props:
# 分片策略
strategy: complex
# 分片算法类
algorithmClassName: com.shardingsphere_101.algorithm.OrderComplexCustomAlgorithm
# 自定义属性
aaaaaa: aaaaaa
tables:
# 逻辑表名称
t_order:
# 数据节点:数据库.分片表
actual-data-nodes: db$->{0..1}.t_order_${0..2}
# 分库策略
database-strategy:
standard:
sharding-column: order_id
sharding-algorithm-name: t_order_database_mod
# 分表策略
table-strategy:
complex:
sharding-columns: order_id , user_id
sharding-algorithm-name: t_order_complex_custom_algorithm

测试算法

插入测试数据,debug 进入 doSharding() 方法,看到columnNameAndShardingValuesMap内获取到了 user_id

、order_id 两个分片键及健值。

当执行范围查询的SQL,columnNameAndRangeValuesMap属性内获取到了 user_id、order_id 两个分片键及健值范围,通过range.upperEndpoint()、lowerEndpoint()得到上下界值。

select * from t_order where order_id > 1 and user_id > 1;

自定义 HINT 算法

要实现自定义HINT强制路由分片算法,需要实现 HintShardingAlgorithm<T> 接口( T 代表接收的分片键值类型)。在实现过程中,需要重写接口中的3个方法。其中,核心的分片逻辑在 doSharding() 方法中处理,可以支持返回多个分片数据源或分片表数据。另外,其他两个prop配置方法的使用方式与上述相同,这里不赘述。

重写 HINT 核心分片方法 doSharding(Collection availableTargetNames, HintShardingValue shardingValue),以实现我们的定制逻辑。该方法接受两个参数:一个是可用目标分库、分表的集合,另一个是 Hint 分片属性对象。

方法内执行流程:我们首先获取 HintManager API 设置的分库或分表的分片值,经过计算后得到合适的分片数据源或分片表集合,然后直接路由到目标位置,无需再关注SQL本身的条件信息。

/**
* 自定义强制路由分片算法
*
* @author 公众号:程序员小富
* @date 2024/03/22 11:02
*/
@Slf4j
public class OrderHintCustomAlgorithm implements HintShardingAlgorithm<Long> { @Override
public Collection<String> doSharding(Collection<String> availableTargetNames, HintShardingValue<Long> hintShardingValue) { /**
* 获取到设置的分表或者分库的分片值
* 指定分表时的分片值 hintManager.addTableShardingValue("t_order",2L);
* 指定分库时的分片值 hintManager.addDatabaseShardingValue("t_order", 100L);
*/
Collection<Long> values = hintShardingValue.getValues();
Collection<String> result = new ArrayList<>();
// 从所有分片表中得到合适的分片表
for (String each : availableTargetNames) {
for (Long value : values) {
Long mod = value % availableTargetNames.size();
if (each.endsWith(String.valueOf(mod))) {
result.add(each);
}
}
}
return result;
}
}

配置算法

配置自定义Hint算法,配置属性包括strategy分片策略类型设置成hintalgorithmClassName自定义Hint算法的实现类全路径。使用该算法时无需指定分片健!

spring:
shardingsphere:
# 具体规则配置
rules:
sharding:
# 分片算法定义
sharding-algorithms:
t_order_database_mod:
type: MOD
props:
sharding-count: 2 # 指定分片数量
# 14、自定义 hint 标准算法
t_order_hint_custom_algorithm:
type: CLASS_BASED
props:
# 分片策略
strategy: hint
# 分片算法类
algorithmClassName: com.shardingsphere_101.algorithm.OrderHintCustomAlgorithm
# 自定义属性
bbbbbb: bbbbbb
tables:
# 逻辑表名称
t_order:
# 数据节点:数据库.分片表
actual-data-nodes: db$->{0..1}.t_order_${0..2}
# 分库策略
database-strategy:
hint:
sharding-algorithm-name: t_order_database_mod
# 分表策略
table-strategy:
hint:
sharding-algorithm-name: t_order_hint_custom_algorithm

测试算法

在执行SQL操作之前,使用 HintManager APIaddDatabaseShardingValueaddTableShardingValue方法来指定分库或分表的分片值,这样算法内通过 HintShardingValue 可以获取到分片值。注意:如果在执行 SQL 时没有使用 HintManager 指定分片值,那么执行SQL将会执行全库表路由

@DisplayName("Hint 自动义分片算法-范围查询")
@Test
public void queryHintTableTest() { HintManager hintManager = HintManager.getInstance();
// 指定分表时的分片值
hintManager.addTableShardingValue("t_order",2L);
// 指定分库时的分片值
hintManager.addDatabaseShardingValue("t_order", 100L); QueryWrapper<OrderPo> queryWrapper = new QueryWrapper<OrderPo>()
.eq("user_id", 20).eq("order_id", 10);
List<OrderPo> orderPos = orderMapper.selectList(queryWrapper);
log.info("查询结果:{}", JSON.toJSONString(orderPos));
}

到这关于 shardingsphere-jdbc 的 3种自定义分片算法实现就全部结束了。

总结

本文介绍了 STANDARD、COMPLEX 和 HINT 三种自定义分片算法的实现,和使用过程中一些要注意的事项。ShardingSphere 内置的十几种算法,其实已经可以满足我们绝大部分的业务场景,不过,如果考虑到后续的性能优化和扩展性,定制分片算法是个不错的选择。

全部demo案例 GitHub 地址:https://github.com/chengxy-nds/Springboot-Notebook/tree/master/shardingsphere101/shardingsphere-algorithms

我是小富~ 下期见

DIY 3 种分库分表分片算法,自己写的轮子才吊!的更多相关文章

  1. 一种可以避免数据迁移的分库分表scale-out扩容方式

    原文地址:http://jm-blog.aliapp.com/?p=590 目前绝大多数应用采取的两种分库分表规则 mod方式 dayofweek系列日期方式(所有星期1的数据在一个库/表,或所有?月 ...

  2. [转]一种可以避免数据迁移的分库分表scale-out扩容方式

    原文地址:http://jm-blog.aliapp.com/?p=590 目前绝大多数应用采取的两种分库分表规则 mod方式 dayofweek系列日期方式(所有星期1的数据在一个库/表,或所有?月 ...

  3. 一种可以避免数据迁移的分库分表scale-out扩容模式

    转自: http://jm.taobao.org/ 一种可以避免数据迁移的分库分表scale-out扩容方式 目前绝大多数应用采取的两种分库分表规则 mod方式 dayofweek系列日期方式(所有星 ...

  4. mysql分库分表(二)

    mysql分库分表 参考: https://www.cnblogs.com/dongruiha/p/6727783.html https://www.cnblogs.com/oldUncle/p/64 ...

  5. 分库分表利器之Sharding Sphere(深度好文,看过的人都说好)

    Sharding-Sphere Sharding-JDBC 最早是当当网内部使用的一款分库分表框架,到2017年的时候才开始对外开源,这几年在大量社区贡献者的不断迭代下,功能也逐渐完善,现已更名为 S ...

  6. mysql分库分表(一)

    mysql分库分表 参考: https://blog.csdn.net/xlgen157387/article/details/53976153 https://blog.csdn.net/cleve ...

  7. 2017-6-5/MySQL分库分表

    分库分表,顾名思义,就是把原本存储于一个库一张表的数据分块存储到多个库多张表上.对于大型互联网应用来说,当一张表的数据量达到百万.千万时,数据库每执行一次查询所花的时间会变多,并且数据库面临着极高的并 ...

  8. sharding-jdbc 分库分表的 4种分片策略,还蛮简单的

    上文<快速入门分库分表中间件 Sharding-JDBC (必修课)>中介绍了 sharding-jdbc 的基础概念,还搭建了一个简单的数据分片案例,但实际开发场景中要远比这复杂的多,我 ...

  9. 【分库分表】sharding-jdbc—分片策略

    一.分片策略 Sharding-JDBC认为对于分片策略存有两种维度: 数据源分片策略(DatabaseShardingStrategy):数据被分配的目标数据源 表分片策略(TableShardin ...

  10. 分库分表的 9种分布式主键ID 生成方案,挺全乎的

    <sharding-jdbc 分库分表的 4种分片策略> 中我们介绍了 sharding-jdbc 4种分片策略的使用场景,可以满足基础的分片功能开发,这篇我们来看看分库分表后,应该如何为 ...

随机推荐

  1. linux和unix中的IO模型总结

    高性能网络IO编程模型 一.I/O模型简介 在一个 linux 操作系统中,一般分为用户空间和内核空间. 用户空间一般就是我们进行应用程序编程的地方. 内核空间就是 linux 操作系统自己运行的一些 ...

  2. flask操作mongodb

    一个简单的注册登录 from pymongo import MongoClient MC = MongoClient('127.0.0.1', 27017) MongoDB = MC['s2'] #创 ...

  3. Android Compose开发

    目录 好处 入门 Composable 布局 其他组件 列表 verticalScroll 延迟列表 内容内边距 性能 修饰符 偏移量 requiredSize 滚动 添加间距Spacer Butto ...

  4. Spring + JAX-WS : ‘xxx’ is an interface, and JAXB can’t handle interfaces 错误解决方法

    错误栈 Caused by: com.sun.xml.bind.v2.runtime.IllegalAnnotationsException: 2 counts of IllegalAnnotatio ...

  5. 【Azure API 管理】APIM服务资源删除后,为什么不能马上创建相同名称的APIM服务呢?

    问题描述 使用Azure APIM服务,在删除旧资源准备新建相同名称的新APIM服务时,尝试多次都是出现"指定的服务名称已正在使用"错误.但实际上同名称的服务已经被删除.为什么多次 ...

  6. JVM内存模式

    Java内存模型即Java Memory Model,简称JMM. JMM定义了Java 虚拟机(JVM)在计算机内存(RAM)中的工作方式.JVM 是整个计算机虚拟模型,所以 JMM 是隶属于 JV ...

  7. Java static关键字的小练习

    1 package com.bytezreo.statictest; 2 3 /** 4 * 5 * @Description static 关键字的使用 小练习 6 * @author Byteze ...

  8. 学习ASP.NET Core Razor 编程系列文章目录

    学习ASP.NET Core Razor 编程系列一 学习ASP.NET Core Razor 编程系列二--添加一个实体 学习ASP.NET Core Razor 编程系列三--创建数据表及创建项目 ...

  9. be动词 系动词 连缀动词 Linking Verb

    be动词 系动词 连缀动词 Linking Verb be 原型 am 第一人称单数形式 is 第三人称单数形式 are 第二人称单数和复数形式 been 过去分词 being 现在分词 was 第一 ...

  10. vscode 自动格式化 好使的配置 setting.json 20210622

    一直用idea,今天有个需求得用vscode,发现格式化不好使了 用 vetur 格式化 结果带分行什么的,eslint 过去不了,更新了个好使的配置,记录一下. { "update.mod ...