Mycat 的分片规则设计
分片规则设计架构
分布式数据库系统中,分片规则用于定义数据与分片的路由关系,也就是insert,delete,update,select的基本sql操作中,如何将sql路由到对应的分片执行。
Mycat的总体路由图为:
如图所示分片规则是最终解析sql到那个分片执行的规则,Mycat分片的确定是根据分片字段来确定数据的分布,即根据预先配置好的分片字段(只有一个)到分片规则中解析该字段对应的值应该路由到哪个分片,然后确认sql到哪个分片执行,分片规则的类图设计为:
RouterUtil,RouteResultset,RouteResultsetNode 几张表是解析sql,解析出sql路由的节点,内部调用AbstractPartitionAlgorithm实现类解析分片字段,查找对应的分片。
AbstractPartitionAlgorithm :为路由规则的抽象类。
RuleAlgorithm :路由规则接口抽象,规定了分片规则的初始化(init),路由分片计算(calculate),及路由多值分片计算(calculateRange)。
分片规则中calculate方法是基本的分片路由计算方法,根据分片字段值,计算出分片。
分片规则中calculateRange方法是范围查询时分片计算,即如果查询类似:
select * from t_user t where t.id<100;
需要解析出指定范围的所有值对应分片。
自定义的分片规则只需要继承AbstractPartitionAlgorithm,按照自己的规则初始化配置文件,并且实现calculate或者calculateRange方法即可,路由的配置文件为:rule.xml。
route 包下面是对应的路由处理,其下面的function包,是分片规则的具体抽象与实现的代码位置。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mycat:rule SYSTEM "rule.dtd"><mycat:rule xmlns:mycat="http://org.opencloudb/"><tableRule name="rule1"><rule><columns>user_id</columns><algorithm>func1</algorithm></rule></tableRule><function name="func1" class="org.opencloudb.route.function.PartitionByLong"><property name="partitionCount">2</property><property name="partitionLength">512</property></function></mycat:rule>
其中rule下的columns规定了分片字段,algorithm为自定义分片类配置。
function 标签为分片规则配置:
name : 为自定义名字
class: 自定义分片规则方法。
property: 其中的参数为自定义参数配置。
分片规则自定义实现
本章节通过日期分片讲解分片规则内部实现细节:
package org.opencloudb.route.function;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import org.apache.log4j.Logger;
import org.opencloudb.config.model.rule.RuleAlgorithm;
/** * 例子按日期列分区格式 between操作解析的范例
* * @author lxy
* */
public class PartitionByDate extends AbstractPartitionAlgorithm implements RuleAlgorithm {
private static final Logger LOGGER = Logger.getLogger(PartitionByDate.class);
private String sBeginDate;
private String sPartionDay;
private String dateFormat;
private long beginDate;
private long partionTime;
private static final long oneDay = 86400000;
@Override
public void init() {
try {
beginDate = new SimpleDateFormat(dateFormat).parse(sBeginDate) .getTime();
} catch (ParseException e) {
throw new java.lang.IllegalArgumentException(e);
}
partionTime = Integer.parseInt(sPartionDay) * oneDay;
}
@Override
public Integer calculate(String columnValue) {
try {
long targetTime = new SimpleDateFormat(dateFormat).parse(columnValue).getTime();
int targetPartition = (int) ((targetTime - beginDate) / partionTime);
return targetPartition;
} catch (ParseException e) {
throw new java.lang.IllegalArgumentException(e);
}
}
@Override
public Integer[] calculateRange(String beginValue, String endValue) {
return AbstractPartitionAlgorithm.calculateSequenceRange(this, beginValue, endValue);
}
public void setsBeginDate(String sBeginDate) {
this.sBeginDate = sBeginDate;
}
public void setsPartionDay(String sPartionDay) {
this.sPartionDay = sPartionDay;
}
public void setDateFormat(String dateFormat) {
this.dateFormat = dateFormat;
}
}
在日期分片字段配置中,分片规则类PartitionByDate的配置属性与类的成员变量对应一次为
dateFormat==>private String dateFormat;
sBeginDate==>private String sBeginDate;
sPartionDay==>private String sPartionDay;
在Mycat的配置文件装载机制中,会根据property 自动设置类的成员变量,因此只要设置了Set…方法就可以赋值。
init方法:
主要处理每种规则的自定义处理,例如本规则中,解析了变量beginDate、partionTime
try {
beginDate = new SimpleDateFormat(dateFormat).parse(sBeginDate) .getTime();
} catch (ParseException e) {
throw new java.lang.IllegalArgumentException(e);
}
partionTime = Integer.parseInt(sPartionDay) * oneDay;
calculate方法:
计算路由分片的核心方法,本规则中通过处理传入的(目标日期-设置的开始日期间隔)/分片时间,计算出偏移量即是分片节点,所有的分片节点编号都是从0开始编码。
例如:每个1天一分片,开始日期是2015-01-01那么分片日期字段值假若是2015-01-10,那么通过公式:
`分片=(2015-01-10-2015-01-01)/1 =9 ,即dn9。
try {
long targetTime = new SimpleDateFormat(dateFormat).parse(columnValue).getTime();
int targetPartition = (int) ((targetTime - beginDate) / partionTime);
return targetPartition;
} catch (ParseException e) {
throw new java.lang.IllegalArgumentException(e);
}
calculateRange方法:
calculateRange 方法默认根据继承的抽象类规则,可以不实现,默认实现是获取分片字段的值连续范围内的所有分片,主要用于类似:update test where id<5; 这种语句中,通过解析条件 id<15解析出所有的id值域分片的对应关系,依次路由执行,[1->dn0,2->dn1,3->dn2,4->dn3].
Integer begin = 0, end = 0;
begin = algorithm.calculate(beginValue);
end = algorithm.calculate(endValue);
if(begin == null || end == null){
return new Integer[0];
}
if (end >= begin) {
int len = end-begin+1;
Integer [] re = new Integer[len];
for(int i =0;i<len;i++){
re[i]=begin+i;
}
return re;
}else{
return null;
}