DIY 三种分库分表分片算法,自己写的轮子才吊!
前言
本文是《ShardingSphere5.x分库分表原理与实战》系列的第六篇,书接上文实现三种自定义分片算法。通过自定义算法,可以根据特定业务需求定制分片策略,以满足不同场景下的性能、扩展性或数据处理需求。同时,可以优化分片算法以提升系统性能,规避数据倾斜等问题。
在这里,自定义分片算法的类型(Type)统一为CLASS_BASED,包含两个属性:strategy 表示分片策略类型,目前支持三种:STANDARD、COMPLEX、HINT;algorithmClassName 表示自定义分片算法的实现类路径。此外,还可以向算法类内传入自定义属性。
图片
自定义 STANDARD 算法
要实现自定义 STANDARD 标准算法,需要实现StandardShardingAlgorithm接口( T 代表接收的分片健值类型),并重写接口中的四个方法。其中,有两个 doSharding() 方法为处理分片的核心逻辑;getProps() 方法用于获取分片算法的配置信息;init() 方法则用于初始化分片算法的配置信息,支持动态修改。
5.X 以后的版本,实现自定义标准算法的精准分片和范围分片,不在需要实现多个接口。只用实现 StandardShardingAlgorithm 标准算法接口,重写两个 doSharding() 方法。doSharding(availableTargetNames,rangeShardingValue) 处理含有 >、、< 等范围操作符的场景,支持单一分片健。
重写方法 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 {
/**
* 精准分片进入 sql中有 = 和 in 等操作符会执行
*
* @param availableTargetNames 所有分片表的集合
* @param shardingValue 分片健的值,SQL中解析出来的分片值
*/
@Override
public String doSharding(Collection availableTargetNames,
PreciseShardingValue 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 doSharding(Collection availableTargetNames,
RangeShardingValue 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 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 算法
复合分片算法支持包含 >,>=,