做者简介 钟悦,就任于某大型国有银行,多年从事MySQL和分布式中间件的方案设计与实施工做;资深MySQL数据库专家,架构师;DBLE开源项目积极贡献者。mysql
文章概要 DBLE默认支持数十种数据拆分算法,基本能知足大部分的社区用户的使用需求;为了知足更广的业务场景,DBLE还支持更加灵活的自定义拆分算法;本文对面向有相似需求的DBLE开发者,提供了一个如何开发和部署自定义的拆分规则的一个指引。算法
目录sql
1. 工做原理数据库
- 1.1 函数的加载
- 1.2 路由计算
- 1.3 参数查询
2. 开发和部署数组
- 2.1 开发
- 2.2 部署
3. 接口规范架构
- 3.1 配置项setters
- 3.2 selfCheck()
- 3.3 init()
- 3.4 calculate()和calculateRange()
- 3.5 getAllProperties()
4. 内置路由函数的缩写与类名对照表分布式
路由函数的加载发生在DBLE启动或重载时。ide
DBLE读取rule.xml时,根据用户配置的标签的class属性函数
DBLE经过Java的反射机制,从$DBLE_HOME/lib的jar包中,找到对应的jar(里的class文件),加载同名的类并建立对象this
DBLE会逐个扫描中的标签,并根据name属性来调用路由函数的对应setter,以此完成赋值过程——例如,若是用户配置了2,那么DBLE就会尝试找路由函数中叫作setPartitionCount()的方法,并将字符串“2”传给它
DBLE调用路由函数的selfCheck()方法,执行函数编写者制定的检查动做,例如检查赋值获得的变量值是否有问题
DBLE调用路由函数的init()方法,执行函数编写者制定的准备动做,例如建立后面要用到的一些中间变量
路由函数接受用户SQL中的分片字段的值,计算出这个值对应的数据记录应该在哪一个编号的数据分片(逻辑分片)上,DBLE从而知道把这个SQL准确发到这些分片上。
用户经过管理端口(默认9066),经过SHOW @@ALGORITHM WHERE SCHEMA=? AND TABLE=?来查询表上的路由算法时,DBLE调用路由算法的getAllProperties()方法,直接从内存中获取路由信息的配置。
mysql> show @@algorithm where schema=testdb and table=seqtest; +-----------------+----------------------------------------------------+ | KEY | VALUE | +-----------------+----------------------------------------------------+ | TYPE | SHARDING TABLE | | COLUMN | ID | | CLASS | com.actiontech.dble.route.function.PartitionByLong | | partitionCount | 2 | | partitionLength | 1 | +-----------------+----------------------------------------------------+ 5 rows in set (0.05 sec)
开发时,理论上只须要引入AbstractPartitionAlgorithm抽象类和RuleAlgorithm接口及它们的依赖类就能够了。但实际上AbstractPartitionAlgorithm抽象类依赖了TableConfig类,由此开启了环游世界的依赖之旅。所以,现实的操做仍是引用整个DBLE项目的源代码会比较直接方便。
开发一个新的路由函数时,必须给这个路由函数的开发新建项目,而后再引用DBLE项目(项目引用项目的方式)。而不该该直接打开DBLE的项目,而后在DBLE的项目里面直接新建源代码来直接开发(内嵌开发方式)。经过遵循这个作法,会有如下好处:
路由函数能够独立打包,直接去看路由函数的jar包版本就可以确认函数版本;而把路由函数嵌到DBLE里的话,就很容易出现DBLE版本同样,但不清楚里面的函数是什么版本的窘况
路由函数的递进能够更加自由,若是DBLE的AbstractPartitionAlgorithm抽象类和RuleAlgorithm接口没有变更,同一版本的路由函数能够延续使用好几个版本的DBLE,而不须要每次DBLE释放新版就得去重编译
可让路由函数中的受保护代码免受DBLE自身的开源协议影响
完成开发以后,成品打包成jar包进行发布,而不要直接发布class和依赖的library(其余项目的jar包或class文件)。
让DBLE使用上新的路由函数的过程:
将成品jar包放入$DBLE_HOME/lib目录中
调整jar包的全部者权限(chown)和文件权限(chmod),使之与其余$DBLE_HOME/lib目录里的jar包同样
| 请注意:2.18.12.0及之后版本建议放在algorithm目录下,用于区分原生lib和自定义lib,便于管理
按照原来的思路配置rule.xml,但须要注意标签的class属性必需要填写新的路由函数类的彻底限定名(Fully Qualified Name),例如net.john.DBLE.route.functions.NewFunction
配置逻辑表之类的必要信息,重启DBLE后,自动生效。
每一个路由函数本质上就是一个继承了AbstractPartitionAlgorithm抽象类,而且实现了RuleAlgorithm接口的一个类。下面之内置的com.actiontech.DBLE.route.function.PartitionByLong为例,介绍实现一个路由函数类所须要作的最小工做(必要工做)。
在rule.xml中,咱们须要配置partitionCount和partitionLength两个配置项。
<function name="hashmod" class="com"> <property name="partitionCount">4</property> <property name="partitionLength">1</property> </function>
为了让DBLE在函数加载过程当中,可以认出这里的partitionCount(值为4)和partitionLength(值为1),所以PartitionByLong类中,就必须有属性设置方法(setter)setPartitionCount()和setPartitionLength()。而由于rule.xml是个文本型的XML文件,因此这些函数的传入参数就只能是一个String,数据类型转换和预处理的动做就由这些setter来处理了。
public void setPartitionCount(String partitionCount) { this.count = toIntArray(partitionCount); /* 参考本文的getAllProperties()的说明 */ propertiesMap.put("partitionCount", partitionCount); } public void setPartitionLength(String partitionLength) { this.length = toIntArray(partitionLength); /* 参考本文的getAllProperties()的说明 */ propertiesMap.put("partitionLength", partitionLength); }
在函数加载过程当中,完成了配置项赋值以后,DBLE会调用这个路由函数对象的selfCheck()方法,让这个对象自我检查刚才读进来的配置项的值,放在一块儿是否是有问题。若是有问题的话,路由函数编写者在这时候,能够经过抛出RuntimeException来进行报错,并终止DBLE使用这个函数,固然,因为RuntimeException的霸道,DBLE本身也会所以而报错退出。
因为selfCheck()是RuleAlgorithm接口的要求,并且AbstractPartitionAlgorithm抽象类没又实现它,对于想偷懒或者没有必要进行这个检查的人来讲,仍是须要自行定义一个空的同名方法来实现它。
@Override public void selfCheck() { }
在函数加载过程的最后,DBLE调用这个路由函数对象的init()方法,让这个对象完成一些内部的初始化工做。
在咱们的例子PartitionByLong里,经过init()方法准备了PartitionUtil对象,其中有一个哈希值的范围与逻辑分片号对应的数组,这样在后面的路由计算时就能经过查数组来加速获得结果。
DBLE执行用户SQL时,根据用户SQL的不一样,调用calculate()或calculateRange()来肯定用户的SQL应该发到哪一个数据分片上去。
从IPO(Input-Process-Output)来分析,calculate()和calculateRange()的工做原理是同样的:
calculate()和calculateRange()的使用场景不一样,致使它们存在着一些微小的差别。
函数名 | 调用场景 | Input | Output |
---|---|---|---|
calculate() | 用户SQL里分片字段的值是单值的状况,例如 ... WHERE sharding_key = 1 | 1个String | 1个Integer |
calculateRange() | 用户SQL里分片字段的值是连续范围,例如 ... WHERE sharding_key BETWEEN 1 AND 5 | 2个String | Integer数组 |
@Override public Integer calculate(String columnValue) { try { if (columnValue == null || columnValue.equalsIgnoreCase("NULL")) { return 0; } long key = Long.parseLong(columnValue); return calculate(key); } catch (NumberFormatException e) { throw new IllegalArgumentException("columnValue:" + columnValue + " Please eliminate any quote and non number within it.", e); } } @Override public Integer[] calculateRange(String beginValue, String endValue) { long begin = 0; long end = 0; try { begin = Long.parseLong(beginValue); end = Long.parseLong(endValue); } catch (NumberFormatException e) { return new Integer[0]; } int partitionLength = partitionUtil.getPartitionLength(); if (end - begin >= partitionLength || begin > end) { //TODO: optimize begin > end return new Integer[0]; } Integer beginNode = calculate(begin); Integer endNode = calculate(end); if (endNode > beginNode || (endNode.equals(beginNode) && partitionUtil.isSingleNode(begin, end))) { int len = endNode - beginNode + 1; Integer[] re = new Integer[len]; for (int i = 0; i < len; i++) { re[i] = beginNode + i; } return re; } else { int split = partitionUtil.getSegmentLength() - beginNode; int len = split + endNode + 1; if (endNode.equals(beginNode)) { //remove duplicate len--; } Integer[] re = new Integer[len]; for (int i = 0; i < split; i++) { re[i] = beginNode + i; } for (int i = split; i < len; i++) { re[i] = i - split; } return re; } }
当用户找DBLE要路由函数的配置信息时,DBLE经过访问路由函数的getAllProperties()来得到一个<配置项, 配置值>的哈希表,而后将里面的内容逐项返回给用户。
getAllProperties()是RuleAlgorithm接口所规定要实现的,但为了简化编写新的路由函数的工做,在AbstractPartitionAlgorithm抽象类里,定义了propertiesMap这个私有变量,而且把“将propertiesMap交出去”做为了实现了getAllProperties()方法的默认实现。
通常来讲,这个默认的实现能知足需求,而新路由函数编写者只须要在配置项setters处理用户配置时,将<配置项, 配置值>给put()进propertiesMap里就行了。
@Override public Map<String, String> getAllProperties() { return propertiesMap; }
DBLE内置的路由函数都位于com.actiontech.DBLE.route.function命名空间。但实际配置rule.xml的时候,却不用写那么长的彻底限定名,这其实都是XMLRuleLoader类作了转换,所以实现了简写。下面就是7个内置函数的类名和它们的简写。
简写名 | 完整类名 |
---|---|
date | PartitionByDate |
enum | PartitionByFileMap |
hash | PartitionByLong |
jumpstringhash | PartitionByJumpConsistentHash |
numberrange | AutoPartitionByLong |
patternrange | PartitionByPattern |
stringhash | PartitionByString |