一、Mycat分片路由原理
我们先来看下面的一个SQL在Mycat里面是如何执行的:
select * from travelrecord where id in(5000001, 10000001);
有3个分片dn1,dn2,dn3, id=5000001这条数据在dn2上,id=10000001这条数据在dn3上。
查询时可能有出现的问题:
1)全部扫描一遍dn1 dn2 dn3,结果导致性能浪费。
2)只扫描某个片。漏掉数据的情况。
总结:
不能多扫——>性能不足
也不能少——>漏掉数据
那么Mycat是如何解决上面的问题的呢?
Mycat使用Druid的DruidParser作为分析器/解析器,解析的过程主要有Visitor和Statement两个阶段
说明:
1)Visitor过程,解析出如下属性:
哪一张表
字段列表
条件信息
什么样的SQL
解析出以上信息以后就可以根据schema.xml和rule.xml文件确认要去哪个分片上进行DML操作了
2)Statement过程转化:转化后知道执行的是什么样的SQL(增删改查)
3)改写SQL
通过查询条件可以知道要查询的数据都在哪些分片上
Dn2, id= 5000001
Dn3, id= 100000001
所以SQL被改写成以下的形式:
select * from travelrecord where id = 5000001;(dn2执行)select * from travelrecord where id = 10000001;(dn3执行)
4)分别在分片dn2,dn3上执行第 3)步改写的SQL,然后把从dn2,dn3上得到的结果进行拼装就是最终的结果了
备注:
多表关联查询的时候,visitor会遍历出所有的表,然后去各个分片上去获取结果,同时把结果缓存起来,最后根据关联查询计算出结果。
确定分片的过程:首先看where条件里面是否含有分片字段,有就根据分片字段的值结合schema.xml、rule.xml的值确定是哪个分片。当不能确定在哪一个分片上的时候,mycat会到所有的分片上去找
二、Mycat常用分片规则
1. 时间类:按天分片、自然月分片、单月小时分片
2. 哈希类:Hash固定分片、日期范围Hash分片、截取数字Hash求模范围分片、截取数字Hash分片、一致性Hash分片
3. 取模类:取模分片、取模范围分片、范围求模分片
4. 其他类:枚举分片、范围约定分片、应用指定分片、冷热数据分片
下面基于源码来介绍Mycat的常用分片规则,源码地址
三、Mycat常用分片规则介绍
说明:分片规则都定义在rule.xml文件里面
id
func1
1,1,2,3,1
128,128,128,128,128
1. 自动范围分片
在rule.xml里面的配置:
id
rang-long
autopartition-long.txt
说明:
有3个分片,第1个分片存储的是1-500000的数据,第2个分片存储的是500001-1000000的数据,第3个分片存储的是1000001-1500000的数据
insert into employee(id, name) value(1,Tom);在第1个分片
insert into employee(id, name) value(500002,Jack);在第2个分片
insert into employee(id, name) value(1000002,Lucy);在第3个分片
对应代码:
packageio.mycat.route.function;importjava.io.BufferedReader;importjava.io.InputStream;importjava.io.InputStreamReader;importjava.util.HashSet;importjava.util.LinkedList;importjava.util.Set;importio.mycat.config.model.rule.RuleAlgorithm;/*** auto partition by Long ,can be used in auto increment primary key partition
*
*@authorwuzhi*/
public class AutoPartitionByLong extends AbstractPartitionAlgorithm implementsRuleAlgorithm{privateString mapFile;privateLongRange[] longRongs;private int defaultNode = -1;
@Overridepublic voidinit() {
initialize();
}public voidsetMapFile(String mapFile) {this.mapFile =mapFile;
}
@OverridepublicInteger calculate(String columnValue) {//columnValue = NumberParseUtil.eliminateQoute(columnValue);
try{long value =Long.parseLong(columnValue);
Integer rst= null;for (LongRange longRang : this.longRongs) {if (value <= longRang.valueEnd && value >=longRang.valueStart) {returnlongRang.nodeIndx;
}
}//数据超过范围,暂时使用配置的默认节点
if (rst == null && defaultNode >= 0) {returndefaultNode;
}returnrst;
}catch(NumberFormatException e){throw new IllegalArgumentException(new StringBuilder().append("columnValue:").append(columnValue).append(" Please eliminate any quote and non number within it.").toString(),e);
}
}
@OverridepublicInteger[] calculateRange(String beginValue, String endValue) {return AbstractPartitionAlgorithm.calculateSequenceRange(this, beginValue, endValue);
}
@Overridepublic intgetPartitionNum() {//int nPartition = longRongs.length;
/** fix #1284 这里的统计应该统计Range的nodeIndex的distinct总数*/Set distNodeIdxSet = new HashSet();for(LongRange range : longRongs) {
distNodeIdxSet.add(range.nodeIndx);
}int nPartition =distNodeIdxSet.size();returnnPartition;
}private voidinitialize() {
BufferedReader in= null;try{//FileInputStream fin = new FileInputStream(new File(fileMapPath));
InputStream fin = this.getClass().getClassLoader()
.getResourceAsStream(mapFile);if (fin == null) {throw new RuntimeException("can't find class resource file "
+mapFile);
}
in= new BufferedReader(newInputStreamReader(fin));
LinkedList longRangeList = new LinkedList();for (String line = null; (line = in.readLine()) != null;) {
line=line.trim();if (line.startsWith("#") || line.startsWith("//")) {continue;
}int ind = line.indexOf('=');if (ind < 0) {
System.out.println(" warn: bad line int " + mapFile + " :"
+line);continue;
}
String pairs[]= line.substring(0, ind).trim().split("-");long longStart = NumberParseUtil.parseLong(pairs[0].trim());long longEnd = NumberParseUtil.parseLong(pairs[1].trim());int nodeId = Integer.parseInt(line.substring(ind + 1)
.trim());
longRangeList
.add(newLongRange(nodeId, longStart, longEnd));
}
longRongs= longRangeList.toArray(newLongRange[longRangeList
.size()]);
}catch(Exception e) {if (e instanceofRuntimeException) {throw(RuntimeException) e;
}else{throw newRuntimeException(e);
}
}finally{try{
in.close();
}catch(Exception e2) {
}
}
}public intgetDefaultNode() {returndefaultNode;
}public void setDefaultNode(intdefaultNode) {this.defaultNode =defaultNode;
}static classLongRange {public final intnodeIndx;public final longvalueStart;public final longvalueEnd;public LongRange(int nodeIndx, long valueStart, longvalueEnd) {super();this.nodeIndx =nodeIndx;this.valueStart =valueStart;this.valueEnd =valueEnd;
}
}
}
View Code
2. 枚举分片
把数据分类存储
在rule.xml里面的配置:
sharding_id
hash-int
partition-hash-int.txt
0
说明:找不到分片时设置容错规则,把数据插入到默认分片0里面
对应代码:
packageio.mycat.route.function;importjava.io.BufferedReader;importjava.io.InputStream;importjava.io.InputStreamReader;importjava.util.HashMap;importjava.util.HashSet;importjava.util.Map;importjava.util.Set;importio.mycat.config.model.rule.RuleAlgorithm;/***
*@authormycat*/
public class PartitionByFileMap extends AbstractPartitionAlgorithm implementsRuleAlgorithm {privateString mapFile;private Mapapp2Partition;/*** Map app2Partition中key值的类型:默认值为0,0表示Integer,非零表示String*/
private inttype;/*** 默认节点在map中的key*/
private static final String DEFAULT_NODE = "DEFAULT_NODE";/*** 默认节点:小于0表示不设置默认节点,大于等于0表示设置默认节点
*
* 默认节点的作用:枚举分片时,如果碰到不识别的枚举值,就让它路由到默认节点
* 如果不配置默认节点(defaultNode值小于0表示不配置默认节点),碰到
* 不识别的枚举值就会报错,
* like this:can't find datanode for sharding column:column_name val:ffffffff*/
private int defaultNode = -1;
@Overridepublic voidinit() {
initi