如果您是高手就不要看了,我也是初学者,如果您无法通过字段取余的方式实现分库分表,您就可以参考我这篇文章
希望能帮到初学者
公司要求用shardingsphere-proxy 进行分库分表操作,研究了一段时间,一路摸爬滚打总算入了门了。
本文介绍的是ShardingSphare4.1版本,最新的5.0版本不适应此方法,
5.0版本proxy自定义类请看下面下面这篇文章:
ShardingSphare5.0 Proxy通过自定义类实现分库分表_weixin_44211703的博客-优快云博客
shardingsphare官网咱也看了一下,但是只有一个根据ID取余分库分表的例子,其它例子都没有,只告诉了需要自定义实现。咱也是一根筋,总习惯跟着例子来学习配置,如何自定义实现?官网没有,度娘90%都是互相转载没啥用处。所以就自己扣吧。
主要心得如下:
1. 官方文档只介绍了取余这种分库分表方法,如果想按日期或者取余无法实现的逻辑分库分表需要通过自定义类实现,具体看下面介绍。
2.配置文件中可以指定拆分的表所在具体数据库节点和表名
比如:
配置文件中设置表所在数据节点和表名: actualDataNodes: ds_${0..3}.Content_${0..31} 表示拆分到0-3四个库,每个库的0-31表。也可以设置为指定的节点和表,
如果要求表分到0,2数据节点下的 2,4,6,710,12这几个表,而不是全部表。配置方式为:actualDataNodes: ds_${0,2}.Content_${ 2,4,6,710,12}
3.分库字段和分表字段可以设置为两个不同的字段,也可以设置为同一个字段,也可以跟主键ID设置相同
4.主键字段,可以通过在配置文件中通过配置snowflake算法,插入时系统自动生成。也可以在配置文件中配置好snowflake,插入前自己通过程序先生成一个ID,然后在insert中插入这个ID。
事先生成的方式,可以在项目中通过调用shardingspahre提供的snowflake算法类 DefaultKeyGenerator来实现。在这个类对象中可以设置snowflake的机器ID,时钟等参数。需要引入shardingsphare的jar包。
工程中生成ID方法:
import io.shardingsphere.core.keygen.DefaultKeyGenerator;
DefaultKeyGenerator key = new DefaultKeyGenerator();
key.setMaxTolerateTimeDifferenceMilliseconds(maxTolerateTimeDifferenceMillisecond);
key.setTimeService();
key.setWorkerId();
Number orderIdKey = key.generateKey();
Long orderId = orderIdKey.longValue();//获得ID,可以在insert中直接插入这个值
参考:shardingsphare-proxy DefaultKeyGenerator snowflake 雪花法提前生成ID,供程序调用_weixin_44211703的博客-优快云博客
5.主键生成策略通常是通过snowflake这种随机数方式,主键数字不是连续生成。如果需要联系可以采用美团的leaf-segment数据库服务方案。这个方案可以满足生成的ID是自增,这样就可以提前在程序中通过这个服务生成ID,然后插入shardingspahre分库分表中。这个服务最牛的地方是可以自动调整ID取值范围。 如果默认每次取1000个ID,服务会自动计算如果在15分钟内使用完了这1000个ID,那么下次会取2000个,防止高峰期频繁访问数据库,如果15分钟内用不完2000个,会自动变成1000
6.不是所有的表都需要分库分表,对于不需要分库分表的这些表,需要指定一个存储的节点,在配置文件最后通过设置 defaultDataSourceName: ds_2 实现。 这样就将不需要拆分的表存储到了ds_2这个节点。
7 对于需要时间分片的算法,需要通过自己写程序实现,程序中需要继承分库分表接口,本文虽然没有介绍如何实现按年,月,日,季度,星期时间分片,但是只需要将分片字段设置为分片的日期字段,自定义实现接口的方法中是相同的,可以参考下面内容接口实现方法
8.shardingsphare默认查询只支持=,between and 这种查询,像>,<,>=,<=这种查询目前不支持,除非通过继承自定义接口RangeShardingAlgorithm实现,否则无法使用>,<,>=,<=。同时也需要实现PreciseShardingAlgorithm<String>接口
9.自定义的类需要打成jar包,上传到shardingsphare-proxy中lib目录下,然后在配置文件中分库,分表位置设置调用的自定义类。
自定义类其实就是根据自定义类继承自定义接口,在接口函数中根据自己写的逻辑,返回数据对应的表名称和数据节点(数据库节点)名称供shardingsphare-proxy服务调用
10.分库需要自定义类实现,分表也需要自定义类实现,都需要继承 RangeShardingAlgorithm,RangeShardingAlgorithm,只不过自定义分库类中返回的是库名,自定义分表类中返回的是表名
11,继承RangeShardingAlgorithm自定义接口,因为如果查询条件是:>100 ,这样范围中只有下限值,没有上限值,需要程序专门处理。
12.在自定义类中通过System.out.print,会把内容输出在shardingsphare-proxy中log目录下stdout.log中
13. shardingsphare根据年、月、日、季度、星期,周分库分表时,只需要在配置文件中配置分库分表字段,设置对应的自定义类,在类中实现反季度,年,月,日的规则即可,在逻辑操作中,可以对传递的日期字段值取年,月,日,季度,星期进行计算,返回对对应的数据库节点 名称或表名称
14.自定义类中实现的逻辑,就是根据传递的字段值,用程序代码通过传递的字段值根据分库分表逻辑进行计算,计算该字段值对应的库名和表名,如果是查询大于,小于,between and 等表示范围时,就会返回多个值
例子:
例如:需要对Cotnent表进行分库分表操作, 分库分表字段是Content_ID,原则如下:
分库规则:4个库,库名是ds_0,ds_1,ds_2,ds_3,通过ContentID对32取余,然后对4取余确定 数据节点0,~4
分表规则:每个库分32个表 表名是Content_0~/31,通过ContetnID对32取余,确定表名称节点
分库分表的原则在自定义类中接口函数dosharding中实现。
自定义分类实现方法:
一、用于分库的类
1.在工程中引入sharding包,sharding-jdbc-core-4.1.1.jar、sharding-core-api-4.0.0-RC3.jar(也可以将shardingsphare中lib目录jar包全部引入,这样就懒得一个个找,省事。)
2.在类声明中继承并实现两个接口: PreciseShardingAlgorithm<String>, RangeShardingAlgorithm<Long> 。
PreciseShardingAlgorithm接口用于实现=查询, RangeShardingAlgorithm用于实现>,<,>=,<=查询
package com.standard;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.Set;
import com.google.common.collect.Range;
import org.apache.commons.lang3.StringUtils;
import org.apache.shardingsphere.api.sharding.standard.PreciseShardingAlgorithm;
import org.apache.shardingsphere.api.sharding.standard.PreciseShardingValue;
import org.apache.shardingsphere.api.sharding.standard.RangeShardingAlgorithm;
import org.apache.shardingsphere.api.sharding.standard.RangeShardingValue;
//自定义数据库分库类
public class ModuloShardingDatabaseAlgorithm implements PreciseShardingAlgorithm<String>, RangeShardingAlgorithm<Long> { //继承PreciseShardingAlgorithm接口,RangeShardingAlgorithm接口,处理范围查询
private int ModeID=1000;//默认取值范围。如果只设置了>20000没有设置 最大值,此时默认设置一个最大值:最小值+1000。反之设置了最大值,没有设置最小值,就是最大值-1000
#Collection<String> databaseNamescollection :配置文件中数据库的数节点范围
#RangeShardingValue<Long> rangeShardingValue :查询语句中查询范围
//返回符合查询条件的数据库节点名称
@Override
public Collection<String> doSharding(Collection<String> databaseNamescollection, RangeShardingValue<Long> rangeShardingValue) {//实现RangeShardingAlgorithm接口,在接口中实现逻辑,返回符合逻辑的数据库名称
Collection<String> collect = new ArrayList<>();//返回数据库节点名称list
Range<Long> valueRange = rangeShardingValue.getValueRange();//获取查询条件中范围值
String slowerEndpoint = String.valueOf(valueRange.hasLowerBound()?valueRange.lowerEndpoint():"");//查询下限值
String supperEndpoint = String.valueOf(valueRange.hasUpperBound()?valueRange.upperEndpoint():"");//查询上限值
//判断上限,下限值是否存在,如果不存在赋给默认值。用于处理查询条件中只有 >或<一个条件,不是一个范围查询的情况
long lowerEndpoint=0;
long lupperEndpoint=0;
if(!slowerEndpoint.isEmpty()&&!supperEndpoint.isEmpty()) {
lowerEndpoint= Math.abs(Long.parseLong(slowerEndpoint));
lupperEndpoint= Math.abs(Long.parseLong(supperEndpoint));
} else if(slowerEndpoint.isEmpty()&&!supperEndpoint.isEmpty()) {
lupperEndpoint= Math.abs(Long.parseLong(supperEndpoint));
lowerEndpoint=lupperEndpoint-ModeID>0?lupperEndpoint-ModeID:0;
}
else if(!slowerEndpoint.isEmpty()&&supperEndpoint.isEmpty()) {
lowerEndpoint= Math.abs(Long.parseLong(slowerEndpoint));
lupperEndpoint=lowerEndpoint+ModeID;
}
//逐个取slowerEndpoint ~supperEndpoint 之间的值,根据分库规则对该值先对32取余,将结果再对4取余,获得的数字就是数据库节点ID,然后返回这个数据库节点ID
for (Long i = lowerEndpoint; i <= lupperEndpoint; i++) {//传递的查询范围值,逐个值进行计算
for (String each : databaseNamescollection) {//配置文件中表配置中设置的数据库节点范围去个读取,{0..3}逐个读取
if (each.endsWith(Long.valueOf((i %Long.valueOf(32)))% Long.valueOf(databaseNamescollection.size()) + "")) {//从当前范围取一个值,对32取模,结果再对4取模。databaseNamescollection是配置文件中节点数量,节点配置为(0...3),所以这里会是4
if(!collect.contains(each))
{//将当前取值对应的数据库节点存入返回list中
collect.add(each);
}
}
}
}
return collect;//返回当前范围对应的节点列表
}
//Collection<String> collection :配置文件中数据库的数节点范围
//PreciseShardingValue<String> preciseShardingValue :查询语句中查询值
//返回符合查询条件的数据库名称
@Override
public String doSharding(Collection<String> collection, PreciseShardingValue<String> preciseShardingValue) {//实现PreciseShardingAlgorithm接口,处理=查询,在函数中处理逻辑,
String databaseName = "";
for (String each : collection) {//逐个读取数据库节点,然后根据传递的节点值根据分库分表逻辑(对32取余后再对4取余),获得当前查询需要访问的数据库节点名称并返回
Long l32 = new Long((long)32);
Long ldb= new Long((long)collection.size());
String hashCode = String.valueOf(preciseShardingValue.getValue());
long segment = Math.abs(Long.parseLong(hashCode)) % 32%4;
if (each.endsWith(segment + "")) {
databaseName = each;
break;
}
}
if (StringUtils.isNotEmpty(databaseName)) {
return databaseName;
}
throw new UnsupportedOperationException();
}
}
二、用于分表的类
public class ModuloShardingTableAlgorithm implements PreciseShardingAlgorithm<String>, RangeShardingAlgorithm<Long> {
private int ModeID=1000;
//Collection<String> collection 配置文件中设置的表取值范围
//RangeShardingValue<Long> rangeShardingValue 查询条件中输入的查询范围
//返回符合查询条件的表名称
@Override
public Collection<String> doSharding( Collection<String> collection , RangeShardingValue<Long> rangeShardingValue) {//实现RangeShardingAlgorithm接口,在函数中定义分表逻辑,返回符合逻辑的表名称
Range<Long> valueRange = rangeShardingValue.getValueRange();//获得输入的查询条件范围
String slowerEndpoint = String.valueOf(valueRange.hasLowerBound()?valueRange.lowerEndpoint():"");//查询条件下限
String supperEndpoint = String.valueOf(valueRange.hasUpperBound()?valueRange.upperEndpoint():"");//查询条件上限
//处理只有下限或上限的范围
long lowerEndpoint=0;
long lupperEndpoint=0;
if(!slowerEndpoint.isEmpty()&&!supperEndpoint.isEmpty()) {
lowerEndpoint= Math.abs(Long.parseLong(slowerEndpoint));
lupperEndpoint= Math.abs(Long.parseLong(supperEndpoint));
} else if(slowerEndpoint.isEmpty()&&!supperEndpoint.isEmpty()) {
lupperEndpoint= Math.abs(Long.parseLong(supperEndpoint));
lowerEndpoint=lupperEndpoint-ModeID>0?lupperEndpoint-ModeID:0;
}
else if(!slowerEndpoint.isEmpty()&&supperEndpoint.isEmpty()) {
lowerEndpoint= Math.abs(Long.parseLong(slowerEndpoint));
lupperEndpoint=lowerEndpoint+ModeID;
}
Collection<String> collect = new ArrayList<>();
逐个读取查询范围slowerEndpoint~lupperEndpoint的值,进行32取余计算,获得对应的表名称
for (long i = lowerEndpoint; i <= lupperEndpoint; i++) {
for (String each : collection) {
if (each.endsWith("_"+i % Integer.valueOf( collection.size()) + "")) {
if(!collect.contains(each))
{
collect.add(each);
}
}
}
}
return collect;
}
/Collection<String> collection 配置文件中设置的表取值范围
// PreciseShardingValue<String> preciseShardingValue)
//返回符合查询条件的表名称
@Override
public String doSharding(Collection<String> collection, PreciseShardingValue<String> preciseShardingValue) {//实现PreciseShardingAlgorithm<String>,在函数中定义分表逻辑,返回符合逻辑的表名称
// TODO Auto-generated method stub
for (String each : collection) {
{
String hashCode = String.valueOf(preciseShardingValue.getValue());//配置文件中,分表字段对应的值,也是查询条件中输入的查询条件
long segment = Math.abs(Long.parseLong(hashCode)) % 32;
if (each.endsWith("_"+segment + "")) {//
return each;
}
}
}
throw new RuntimeException(preciseShardingValue+"没有匹配到表");
}
}
三,将自定义类导出jar包,在shardingsphare中引用
打包成jar包,上传到shardingsphare-proxy安装目录lib目录下
配置文件config-sharding.yaml配置如下:
Content: #表名称
actualDataNodes: ds_${0..3}.Content_${0..31}#设置数据分布的数据节点
#开始分库
databaseStrategy:#分库
standard:
shardingColumn: CONTENT_ID #分库字段
preciseAlgorithmClassName: com.standard.ModuloShardingDatabaseAlgorithm #实现接口的自定义分库类,处理精确查询
rangeAlgorithmClassName: com.standard.ModuloShardingDatabaseAlgorithm#实现接口的自定义分库类,处理范围查询
tableStrategy:#分表
standard:
shardingColumn: CONTENT_ID#分表字段
preciseAlgorithmClassName: com.standard.ModuloShardingTableAlgorithm #实现接口的自定义分表类,处理精确查询
rangeAlgorithmClassName: com.standard.ModuloShardingTableAlgorithm #实现接口的自定义分表类,处理范围查询
keyGenerator:#主键生成方式
type: SNOWFLAKE#雪花算法
column: ID # 主键名称
然后重启shardingsphare-proxy就可以。