rule.xml和schema.xml的加载概况
rule.xml和schema.xml的加载入口虽然都在com.actiontech.dble.config.loader.xml.XMLSchemaLoader(null, null, lowerCaseNames)
,但是,实际上解析加载rule.xml的类是com.actiontech.dble.config.loader.xml.XMLRuleLoader
,所以XMLSchemaLoader
这个类依赖XMLRuleLoader
类:
XMLSchemaLoader
在构造函数中实例化了一个即用即弃的XMLRuleLoader
对象,用来加载rule.xml,在调用XMLRuleLoader.getTableRules()
继承到了它的成果后,就不再使用这个对象,转向XMLSchemaLoader.load()
来加载schema.xml。
rule.xml的加载过程
使用ConfigUtil.getDocument()
将rule.xml加载入内存后,先用loadFunctions()
对用户定义的算法(<function>
的具体内容),再用loadTableRules()
读取算法与表名的关联关系(<tableRule>
的具体内容)。
在描述分片算法是如何跟逻辑表挂钩这个事情上,DBLE继承了MyCat的做法。两者都使用了遵从OOP理念的逐层抽象方法(我认为在这里反而是过于复杂化的):
- 最具体的概念是具体的逻辑分片函数类,例如内置哈希分片函数
com.actiontech.dble.route.function.PartitionByLong
,它在<function class="class_name">
中指定; - 这个类的一个实例化对象,必然会有它自己特殊的对象属性值(在
<function>
里的<property>
中指定),这就构成了第一层的抽象概念function或algorithm(这层抽象的称呼不太稳定,我觉得应该是项目管理不善导致的) - 在function或algorithm的基础上,加上用作分片索引的逻辑表的列名,就进一步抽象成了tableRule,可以供逻辑表使用了。
第一次抽象,生成function(algorithm)–loadFunctions()
工作过程与读取server.xml的时候一样,大致是:
从rule.xml中逐个找出
<function>
标签。根据
<function>
标签的class
属性,实例化指定分片函数的对象根据
<function>
标签的name
属性,设置分片函数对象的名称执行分片函数对象自身的初始化方法,并登记到
XMLRuleLoader
中(加入到内部Map集合中)
private void loadFunctions(Element root) throws ClassNotFoundException,
InstantiationException, IllegalAccessException,
InvocationTargetException {
// 提取rule.xml中所有的<function>标签
NodeList list = root.getElementsByTagName("function");
// 逐个<function>标签进行处理
for (int i = 0, n = list.getLength(); i < n; ++i) {
Node node = list.item(i);
if (node instanceof Element) {
Element e = (Element) node;
// 根据<function>标签的name属性,确定该function的名称
String name = e.getAttribute("name");
// 基于name属性检查是否已经加载过该分片函数
if (functions.containsKey(name)) {
throw new ConfigException("rule function " + name + " duplicated!");
}
// 根据<function>标签的class属性,通过java反射创建分片函数类的对象
String clazz = e.getAttribute("class");
//reflection
AbstractPartitionAlgorithm function = createFunction(name, clazz);
function.setName(name);
// 通过java反射设置分片函数对象的属性
ParameterMapping.mapping(function, ConfigUtil.loadElements(e));
// 执行分片函数对象的初始化方法
function.init();
// 注册分片函数对象到XMLRuleLoader的内部集合中
functions.put(name, function);
}
}
}
其中,需要特别说明的是createFunction()
这个方法。在这个方法中,DBLE保留了MyCat的传统,让用户通过完全限定类名指定分片函数;此外,还对内置的6种分片算法提供了简化名来简化<function>
标签的配置。例如,内置哈希函数可以通过<function class="hash">
来指定,而不用大费周章地写<function class="com.actiontech.dble.route.function.PartitionByLong">
了。
简化名 | 完全限定类名 |
---|---|
hash | com.actiontech.dble.route.function.PartitionByLong |
stringhash | com.actiontech.dble.route.function.PartitionByString |
enum | com.actiontech.dble.route.function.PartitionByFileMap |
numberrange | com.actiontech.dble.route.function.AutoPartitionByLong |
patternrange | com.actiontech.dble.route.function.PartitionByPattern |
date | com.actiontech.dble.route.function.PartitionByDate |
private AbstractPartitionAlgorithm createFunction(String name, String clazz)
throws ClassNotFoundException, InstantiationException,
IllegalAccessException, InvocationTargetException {
String lowerClass = clazz.toLowerCase();
switch (lowerClass) {
// 等效于<function class="com.actiontech.dble.route.function.PartitionByLong">
case "hash":
return new PartitionByLong();
// 等效于<function class="com.actiontech.dble.route.function.PartitionByString">
case "stringhash":
return new PartitionByString();
// 等效于<function class="com.actiontech.dble.route.function.PartitionByFileMap">
case "enum":
return new PartitionByFileMap();
// 等效于<function class="com.actiontech.dble.route.function.AutoPartitionByLong">
case "numberrange":
return new AutoPartitionByLong();
// 等效于<function class="com.actiontech.dble.route.function.PartitionByPattern">
case "patternrange":
return new PartitionByPattern();
// 等效于<function class="com.actiontech.dble.route.function.PartitionByDate">
case "date":
return new PartitionByDate();
default:
Class<?> clz = Class.forName(clazz);
//all function must be extend from AbstractPartitionAlgorithm
if (!AbstractPartitionAlgorithm.class.isAssignableFrom(clz)) {
throw new IllegalArgumentException("rule function must implements " +
AbstractPartitionAlgorithm.class.getName() + ", name=" + name);
}
return (AbstractPartitionAlgorithm) clz.newInstance();
}
}
第二次抽象,生成tableRule–loadTableRules()
在第二次抽象后,生成的是com.actiontech.dble.config.model.rule.RuleConfig
对象的Map集合,并存储在XMLRuleLoader
中,供XMLSchemaLoader
在后面加载schema.xml时使用。
工作过程也非常单纯,大致如下:
从rule.xml中逐个找出
<tableRule>
标签。调用
loadRule()
,根据<tableRule>
标签的<rule>
的<columns>
和<algorithm>
,创建一个RuleConfig
对象根据
<tableRule>
标签的name
属性,以此名称为key登记刚才新创建的RuleConfig
对象到XMLRuleLoader
中(加入到内部Map集合中)
private void loadTableRules(Element root) throws SQLSyntaxErrorException {
// 提取rule.xml中所有的<tableRule>标签
NodeList list = root.getElementsByTagName("tableRule");
// 逐个<tableRule>标签进行处理
for (int i = 0, n = list.getLength(); i < n; ++i) {
Node node = list.item(i);
if (node instanceof Element) {
Element e = (Element) node;
// 根据<tableRule>标签的name属性,确定该tableRule的外部命名,
// RuleConfig本身是没有name或者id之类的称呼属性
String name = e.getAttribute("name");
// <tableRule>标签的name属性不能为空,也不能互相重复
if (StringUtil.isEmpty(name)) {
throw new ConfigException("name is null or empty");
}
if (tableRules.containsKey(name)) {
throw new ConfigException("table rule " + name + " duplicated!");
}
// 提取<tableRule>标签有且唯一的<rule>标签
// 目前一个<tableRule>只允许有一个<rule>,但从源代码和注释看来,
// MyCat希望日后能支持多个<rule>,而DBLE则继承了这部分设计
NodeList ruleNodes = e.getElementsByTagName("rule");
int length = ruleNodes.getLength();
if (length > 1) {
throw new ConfigException("only one rule can defined :" + name);
}
// 创建对应的RuleConfig对象,详解在本代码段内
RuleConfig rule = loadRule((Element) ruleNodes.item(0));
// 注册RuleConfig对象到XMLRuleLoader的内部集合中
tableRules.put(name, new TableRuleConfig(name, rule));
}
}
}
// ...
private RuleConfig loadRule(Element element) throws SQLSyntaxErrorException {
// 获取<tableRules>内的<rule>的<columns>标签
Element columnsEle = ConfigUtil.loadElement(element, "columns");
String column = columnsEle.getTextContent();
// <rule>必须有<columns>标签
if (StringUtil.isEmpty(column)) {
throw new ConfigException("no rule column is found");
}
// <columns>标签目前仅支持1个列作为分片索引
// 但源代码支持逗号分隔语法,MyCat应该是希望日后能支持
// 逻辑表的多个列构成一个分片索引(复合分片索引),
// 而DBLE则继承了这部分设计
String[] columns = SplitUtil.split(column, ',', true);
if (columns.length > 1) {
throw new ConfigException("table rule coulmns has multi values:" +
columnsEle.getTextContent());
}
// <rule>必须要有<algorithm>标签
Element algorithmEle = ConfigUtil.loadElement(element, "algorithm");
String algorithmName = algorithmEle.getTextContent();
if (StringUtil.isEmpty(algorithmName)) {
throw new ConfigException("algorithm is null or empty");
}
// 根据<algorithm>中的内容去找第一次抽象时生成的function
AbstractPartitionAlgorithm algorithm = functions.get(algorithmName);
// 如果能找到对应的function,则以此生成新的RuleConfig对象
if (algorithm == null) {
throw new ConfigException("can't find function of name :" + algorithmName);
}
return new RuleConfig(column.toUpperCase(), algorithmName, algorithm);
}