DIY 3 种分库分表分片算法,自己写的轮子才吊!
大家好,我是小富~
前言
本文是《ShardingSphere5.x分库分表原理与实战》系列的第六篇,书接上文实现三种自定义分片算法。通过自定义算法,可以根据特定业务需求定制分片策略,以满足不同场景下的性能、扩展性或数据处理需求。同时,可以优化分片算法以提升系统性能,规避数据倾斜等问题。
在这里,自定义分片算法的类型(Type)统一为CLASS_BASED
,包含两个属性:strategy
表示分片策略类型,目前支持三种:STANDARD
、COMPLEX
、HINT
;algorithmClassName
表示自定义分片算法的实现类路径。此外,还可以向算法类内传入自定义属性。
自定义 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
分片策略类型设置成standard
,algorithmClassName
自定义标准算法的实现类全路径。需要注意的是:策略和算法类型必须保持一致,否则会导致错误。
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
分片策略类型设置成complex
,algorithmClassName
自定义算法的实现类全路径。
需要注意:配置分片键时,一定要使用 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
分片策略类型设置成hint
,algorithmClassName
自定义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 API
的 addDatabaseShardingValue
和 addTableShardingValue
方法来指定分库或分表的分片值,这样算法内通过 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 种分库分表分片算法,自己写的轮子才吊!的更多相关文章
- 一种可以避免数据迁移的分库分表scale-out扩容方式
原文地址:http://jm-blog.aliapp.com/?p=590 目前绝大多数应用采取的两种分库分表规则 mod方式 dayofweek系列日期方式(所有星期1的数据在一个库/表,或所有?月 ...
- [转]一种可以避免数据迁移的分库分表scale-out扩容方式
原文地址:http://jm-blog.aliapp.com/?p=590 目前绝大多数应用采取的两种分库分表规则 mod方式 dayofweek系列日期方式(所有星期1的数据在一个库/表,或所有?月 ...
- 一种可以避免数据迁移的分库分表scale-out扩容模式
转自: http://jm.taobao.org/ 一种可以避免数据迁移的分库分表scale-out扩容方式 目前绝大多数应用采取的两种分库分表规则 mod方式 dayofweek系列日期方式(所有星 ...
- mysql分库分表(二)
mysql分库分表 参考: https://www.cnblogs.com/dongruiha/p/6727783.html https://www.cnblogs.com/oldUncle/p/64 ...
- 分库分表利器之Sharding Sphere(深度好文,看过的人都说好)
Sharding-Sphere Sharding-JDBC 最早是当当网内部使用的一款分库分表框架,到2017年的时候才开始对外开源,这几年在大量社区贡献者的不断迭代下,功能也逐渐完善,现已更名为 S ...
- mysql分库分表(一)
mysql分库分表 参考: https://blog.csdn.net/xlgen157387/article/details/53976153 https://blog.csdn.net/cleve ...
- 2017-6-5/MySQL分库分表
分库分表,顾名思义,就是把原本存储于一个库一张表的数据分块存储到多个库多张表上.对于大型互联网应用来说,当一张表的数据量达到百万.千万时,数据库每执行一次查询所花的时间会变多,并且数据库面临着极高的并 ...
- sharding-jdbc 分库分表的 4种分片策略,还蛮简单的
上文<快速入门分库分表中间件 Sharding-JDBC (必修课)>中介绍了 sharding-jdbc 的基础概念,还搭建了一个简单的数据分片案例,但实际开发场景中要远比这复杂的多,我 ...
- 【分库分表】sharding-jdbc—分片策略
一.分片策略 Sharding-JDBC认为对于分片策略存有两种维度: 数据源分片策略(DatabaseShardingStrategy):数据被分配的目标数据源 表分片策略(TableShardin ...
- 分库分表的 9种分布式主键ID 生成方案,挺全乎的
<sharding-jdbc 分库分表的 4种分片策略> 中我们介绍了 sharding-jdbc 4种分片策略的使用场景,可以满足基础的分片功能开发,这篇我们来看看分库分表后,应该如何为 ...
随机推荐
- linux和unix中的IO模型总结
高性能网络IO编程模型 一.I/O模型简介 在一个 linux 操作系统中,一般分为用户空间和内核空间. 用户空间一般就是我们进行应用程序编程的地方. 内核空间就是 linux 操作系统自己运行的一些 ...
- flask操作mongodb
一个简单的注册登录 from pymongo import MongoClient MC = MongoClient('127.0.0.1', 27017) MongoDB = MC['s2'] #创 ...
- Android Compose开发
目录 好处 入门 Composable 布局 其他组件 列表 verticalScroll 延迟列表 内容内边距 性能 修饰符 偏移量 requiredSize 滚动 添加间距Spacer Butto ...
- 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 ...
- 【Azure API 管理】APIM服务资源删除后,为什么不能马上创建相同名称的APIM服务呢?
问题描述 使用Azure APIM服务,在删除旧资源准备新建相同名称的新APIM服务时,尝试多次都是出现"指定的服务名称已正在使用"错误.但实际上同名称的服务已经被删除.为什么多次 ...
- JVM内存模式
Java内存模型即Java Memory Model,简称JMM. JMM定义了Java 虚拟机(JVM)在计算机内存(RAM)中的工作方式.JVM 是整个计算机虚拟模型,所以 JMM 是隶属于 JV ...
- Java static关键字的小练习
1 package com.bytezreo.statictest; 2 3 /** 4 * 5 * @Description static 关键字的使用 小练习 6 * @author Byteze ...
- 学习ASP.NET Core Razor 编程系列文章目录
学习ASP.NET Core Razor 编程系列一 学习ASP.NET Core Razor 编程系列二--添加一个实体 学习ASP.NET Core Razor 编程系列三--创建数据表及创建项目 ...
- be动词 系动词 连缀动词 Linking Verb
be动词 系动词 连缀动词 Linking Verb be 原型 am 第一人称单数形式 is 第三人称单数形式 are 第二人称单数和复数形式 been 过去分词 being 现在分词 was 第一 ...
- vscode 自动格式化 好使的配置 setting.json 20210622
一直用idea,今天有个需求得用vscode,发现格式化不好使了 用 vetur 格式化 结果带分行什么的,eslint 过去不了,更新了个好使的配置,记录一下. { "update.mod ...