ShardingSphere5.0跟之前的4.0在配置文件上有了区别,并且自定义规则类的定义引用跟4.0也发生了变化。这里介绍一下5.0的自定义规则的定义和调用。
一、为什么用自定义规则。
通过取余无法实现的规则,需要通过自定义类根据自己定义的分库分表逻辑实现分库分表规则,返回数据对应的分库名称和分表名称
官网和网上大部分介绍的都是通过行表达式 对ID字段取余或者取整的方式,但是实际分库分表可能会需要按日期范围,按年,月,周,季度分库分表这时候就无法通过行表达式实现,需要自己定义规则类,然后在配置文件中指定字段,引用自定义规则类,实现字段根据自定义规则分库分表。
二、自定义规则有什么作用?
官网介绍的分库分表都是通过在配置文件中行表达式,计算字段值对应的分库名称和分表名称,自定义规则也是根据字段值计算并返回对应的分库和分表名称
三、自定义类需要继承的接口
在自定义类中继承shardingsphere5.0接口StandardShardingAlgorithm和接口函数。同时需要 引入对应的shardingsphare定义的jar包,我偷懒直接将shardingsphere5.0工程中lib目录下的jar包都引用了,没有具体区分。
四、.自定规则分类
自定规则可以有自定义分库规则类,分表规则类。 不同的分库分表规则定义不同的类,分别在shardingsphere配置文件中引用。分库类和分表类都需要实现StandardShardingAlgorithm接口
比如表Test 自定义分库分表规则:
1.根据创建日期字段Create_Date中年分表,一年一个表 。
比如 2021-01-01,2021-05-01,2021-06-01,年份是2021的都存储在Test_2021中。
2015-02-03,2015-05-03,2015-06-03 年份是2015的都存储在 Test_2015中
2.1995年年之前的数据都存储在Test_1995标中
3.分库规则:分四个库,创建日期字段Create_Date中年分对4取余,余数就是分库名称。比如ds_0,ds_1,ds_2,ds_3
分库规则如下:
package com.standard;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import org.apache.shardingsphere.sharding.api.sharding.standard.PreciseShardingValue;
import org.apache.shardingsphere.sharding.api.sharding.standard.RangeShardingValue;
import org.apache.shardingsphere.sharding.api.sharding.standard.StandardShardingAlgorithm;
import com.google.common.collect.Range;
public class ModuloShardingDatabaseDateYearAlgorithm implements StandardShardingAlgorithm<String>{
private int ModedID=5;
private static final String DataBase_NAME = "ds_";
//获得日期字段中年份数字,如果年份<1995,返回1995
private int getYearFromString(String value) throws ParseException {
int nYear=-1;
try {
if(null==value||""==value||value.length()<4) {
Date date = new Date();
return date.getYear();
}
else {
try {
nYear=Integer.valueOf(value.substring(0, 4));
}
catch(Exception e) {
Date date = new Date();
return date.getYear();
}
}
if(nYear<1995)
nYear=1995;
}
catch(Exception e) {
Date date = new Date();
return date.getYear();
}
return nYear;
}
/*
*databaseNamescollection:配置文件中定义的数据库名称集合
*shardingValue:insert,select,update,delate时,Create_Date字段值
*函数返回值:返回函数值shardingValue经过取余计算后对于的数据库名称。比如 1995%4 =3,返回数据库名称是ds_3
*/
@Override
public String doSharding(Collection<String> availableTargetNames, PreciseShardingValue<String> shardingValue) {
if (shardingValue != null) {
String value = shardingValue.getValue();//获取传递的字段值
int nYear=-1;
try {
nYear = getYearFromString(value);//获取年份
} catch (ParseException e) {
e.printStackTrace();
}
//对年份取余,将余数跟数据库前缀组合成数据库名称,
//检查这个名称是否在配置文件数据库集合中,如果存在返回数据库名称
for (String each : availableTargetNames) {
if (each.equals(DataBase_NAME + String.valueOf(nYear%4))) {
return each;
}
}
throw new UnsupportedOperationException("content_channel没有匹配到可用数据库节点");
} else {
throw new UnsupportedOperationException("分片列为空");
}
}
/*
*databaseNamescollection:配置文件中定义的数据库名称集合
*rangeShardingValue:insert,select,update,delate时,Create_Date 范围查询字段值。比如 between 2015-01-01 and 2021-01-01,存储的就是 2015-01-01和2021-01-01
*函数返回值:返回函数值rangeShardingValue经过取余计算后对于的数据库名称。比如 between 2015-01-01 and 2021-01-01 ,根据这时间段年份,得到返回的集合是ds_0,ds_1,ds_2,ds_3
*/
@Override
public Collection<String> doSharding(Collection<String> databaseNamescollection, RangeShardingValue<String> rangeShardingValue) {
Collection<String> collect = new ArrayList<>();
if (rangeShardingValue != null) {
Range<String> valueRange = rangeShardingValue.getValueRange();//获得范围区间值
String slowerEndpointDate = String.valueOf(valueRange.hasLowerBound()?valueRange.lowerEndpoint():""); //获得返回区间值下限
String supperEndpointDate = String.valueOf(valueRange.hasUpperBound()?valueRange.upperEndpoint():"");//获得范围区间值上限
int nStartYear =-1;
int nEndYear=-1;
try {
nStartYear = getYearFromString(slowerEndpointDate) ;//获得下限年份
nEndYear = getYearFromString(supperEndpointDate) ;//获得上限年份
if(nStartYear==-1&&nEndYear!=-1) {//下限年份为空时,上限不为空时,下限=上限-5
nStartYear=nEndYear-ModedID;
} else if(nStartYear!=-1&&nEndYear==-1) {//下限不为空,上限为空,上限=下限+5
nEndYear=nStartYear+ModedID;
}
} catch (ParseException e) {
e.printStackTrace();
}
//根据上下限范围,循环取值判断对应的数据库名称,返回数据库名称集合
for (String each : databaseNamescollection) {
for(int i=nStartYear;i<=nEndYear;i++) {
if (each.equals(DataBase_NAME + String.valueOf(i%4))) {
if(!collect.contains(each)) {
collect.add(each);
}
}
}
}
return collect;
}else {
throw new UnsupportedOperationException("分片列为空");
}
}
}
分表规则代码:
package com.standard;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import org.apache.shardingsphere.sharding.api.sharding.standard.PreciseShardingValue;
import org.apache.shardingsphere.sharding.api.sharding.standard.RangeShardingValue;
import org.apache.shardingsphere.sharding.api.sharding.standard.StandardShardingAlgorithm;
import com.google.common.collect.Range;
public class ModuloShardingTableDateYearAlgorithm implements StandardShardingAlgorithm<String>{
private static final String TABLE_NAME = "content_channel_";
private int ModedID=5;
//获得日期字段中年份数字,如果年份<1995,返回1995
private int getYearFromString(String value) throws ParseException {
int nYear=-1;
try {
if(null==value||""==value||value.length()<4) {
Date date = new Date();
return date.getYear();
}
else {
try {
nYear=Integer.valueOf(value.substring(0, 4));
}
catch(Exception e) {
Date date = new Date();
return date.getYear();
}
}
if(nYear<1995)
nYear=1995;
}
catch(Exception e) {
Date date = new Date();
return date.getYear();
}
return nYear;
}
/*
*availableTargetNames:配置文件中定义的表名称集合
*shardingValue:insert,select,update,delate时,Create_Date字段值
*函数返回值:返回函数值shardingValue年份对应的表名。比如 1995 =3,返回表名Test_1995
*/
@Override
public String doSharding(Collection<String> availableTargetNames, PreciseShardingValue<String> shardingValue) {
if (shardingValue != null) {
String value = shardingValue.getValue();//获取传递的字段值
int nYear=-1;
try {
nYear = getYearFromString(value) ;//获取年份
} catch (ParseException e) {
e.printStackTrace();
}
//根据年份判断表名集合是否存在对应表表名集合,存在时返回表名
for (String each : availableTargetNames) {
new UnsupportedOperationException(each+"--"+TABLE_NAME + String.valueOf(nYear));
if (each.endsWith(String.valueOf(nYear))) {
return each;
}
}
throw new UnsupportedOperationException("content_channel没有匹配到可用表");
} else {
throw new UnsupportedOperationException("分片列为空");
}
}
/*
*availableTargetNames:配置文件中定义的表名称集合
*rangeShardingValue:insert,select,update,delate时,Create_Date 范围查询字段值。比如 between 2015-01-01 and 2021-01-01,存储的就是 2015-01-01和2021-01-01
*函数返回值:返回函数值rangeShardingValue 年份对应的表名集合比如 between 2020-01-01 and 2021-01-01 ,根据这时间段年份,得到返回的集合是Test_2020,Test_2021
*/
@Override
public Collection<String> doSharding(Collection<String> availableTargetNames, RangeShardingValue<String> rangeShardingValue) {
// TODO Auto-generated method stub
Collection<String> collect = new ArrayList<>();
if (rangeShardingValue != null) {
Range<String> valueRange = rangeShardingValue.getValueRange();//获得范围区间值
String slowerEndpointDate = String.valueOf(valueRange.hasLowerBound()?valueRange.lowerEndpoint():""); //获得返回区间值下限
String supperEndpointDate = String.valueOf(valueRange.hasUpperBound()?valueRange.upperEndpoint():"");//获得范围区间值上限
int nStartYear =-1;
int nEndYear=-1;
try {
nStartYear = getYearFromString(slowerEndpointDate) ;//获得下限年份
nEndYear = getYearFromString(supperEndpointDate) ;//获得上限年份
if(nStartYear==-1&&nEndYear!=-1) {
nStartYear=nEndYear-ModedID;//下限年份为空时,上限不为空时,下限=上限-5
} else if(nStartYear!=-1&&nEndYear==-1) {
nEndYear=nStartYear+ModedID;//下限不为空,上限为空,上限=下限+5
}
} catch (ParseException e) {
e.printStackTrace();
}
//根据上下限范围,循环取值判断对应的表名称,返回符合条件的表名称集合
for (int i = nStartYear; i <= nEndYear; i++) {
for (String each : availableTargetNames) {
if (each.endsWith(String.valueOf(i))) {
if(!collect.contains(each))
{
collect.add(each);
}
}
}
}
return collect;
}
return null;
}
}
五、配置文件引用
将第四部定义的类生成jar包,拷贝到ShardingSphere目录中lib子目录下
rules:
- !SHARDING
tables:
Test:
actualDataNodes: ds_${0..3}.Test_${1995..2042} #分4个库,分表从1995-2042
tableStrategy:#分表设置
standard:
shardingColumn: Create_date #分表字段
shardingAlgorithmName: test_shard #分表规则名称
databaseStrategy:#分库设置(如果多个表分库字段和规则相同,可以不用设置每个表分库规则,设置一个默认分库规则defaultDatabaseStrategy就可以)
standard:
shardingColumn: Create_date #分库字段
shardingAlgorithmName: test_DB_shard #分库规则名称
keyGenerateStrategy:
column: id
keyGeneratorName: snowflake
bindingTables:
- Test
defaultDatabaseStrategy:
standard: #分表
shardingColumn: Create_Date #默认分库字段
shardingAlgorithmName: test_DB_shard #默认分库规则名称
shardingAlgorithms:
test_DB_shard:
type: CLASS_BASED # 声明类引用
props:
strategy: STANDARD #设置属性
algorithmClassName: com.standard.ModuloShardingDatabaseDateYearAlgorithm #引用分库类
test_shard:
type: CLASS_BASED # 声明类引用
props:
strategy: STANDARD #设置属性
algorithmClassName: com.standard.ModuloShardingTableDateYearAlgorithm #引用分表类
keyGenerators:
snowflake:
type: SNOWFLAKE
props:
worker-id: 123
根据上面的代码和配置文件设置,就完成了自定义规则类在shardingspehre5.0中的引用,实现了自定义规则定义和引用。
六、感觉ShardingSphere相关文档写的不是很详细,并且4.0和5.0在自定义分库分表上没有形成一个完整的继承体系,
七、上面内容本人经过实际验证可以正确执行,达到分库分表目的,数据库可以按规则插入对应的库和表。由于水平有限,有错误地方请多谅解。