schema.xml的加载过程概况
com.actiontech.dble.config.loader.xml.XMLSchemaLoader
的load()
方法是schema.xml的加载入口。这个加载入口仅仅把schema.xml,以带DTD校验的方式加载成到内存中,形成w3c的DOM。然后依次调用loadDataHosts()
、loadDataNodes()
和loadSchemas()
这三个方法,处理schema.xml中的三种一级标签,加载成com.actiontech.dble.config.model
命名空间中对应的类的对象。
方法名 | 处理的标签 | 目标类 |
---|---|---|
loadDataHosts() | DataHostConfig | |
loadDataNodes() | DataNodeConfig | |
loadSchemas() | SchemaConfig |
处理标签——loadDataHosts()
loadDataHosts()
的使命是将
标签加载成DataHostConfig
对象。在逻辑结构上,
标签和DataHostConfig
对象完全相同。
DataHostConfig属性 | |
---|---|
name | String name |
maxCon | int maxCon |
minCon | int minCon |
balance | int balance |
switchType | int switchType |
slaveThreshold | int slaveThreshold |
tempReadHostAvailable | boolean tempReadHostAvailable |
String heartbeatSQL | |
DBHostConfig[] writeHosts | |
Map |
可以看到,相对于name这些属性,
和
这两个property(XML概念)的处理是重点,其中需要着重解决下面两个问题:
-
和
应该加载成什么?
除了
可以包含一个或多个
外,在属性层面,两者基本没有差异。因此统一用com.actiontech.dble.config.model.DBHostConfig
这个类来标识它们。一个DBHostConfig
对象表示一个
或
标签。
-
对
的一对多包含关系应该如何保存下来?
每个
标签在DataHostConfig.writeHosts
这个数组中存储,也因此会有它在数组中的序号,所以DataHostConfig.readHosts
这个Map集合利用了这一点,设计成了“<
在writeHosts中的序号, 这个
拥有的
数组>”这么一种形式来存储(虽然我觉得用存储密度更高的二维数组可能会更好)。
了解了以上概况之后,它的工作流程就很明晰了,大致是:
从schema.xml中逐个找出
标签加载
的简单属性到临时变量中:name, maxCon, minCon, balance, switchType, slaveThreshold, tempReadHostAvailable和加载
的复杂属性
和
到临时变量中根据临时变量生成
DataHostConfig
对象,并登记到XMLSchemaLoader
中(加入到内部Map集合中)
private void loadDataHosts(Element root) {
// 提取schema.xml中所有的标签
NodeList list = root.getElementsByTagName("dataHost");
// 逐个标签进行处理
for (int i = 0, n = list.getLength(); i < n; ++i) {
Element element = (Element) list.item(i);
// 加载简单属性到临时变量中:name, maxCon, minCon, balance, switchType, slaveThreshold, tempReadHostAvailable和
String name = element.getAttribute("name");
if (dataHosts.containsKey(name)) {
throw new ConfigException("dataHost name " + name + "duplicated!");
}
int maxCon = Integer.parseInt(element.getAttribute("maxCon"));
int minCon = Integer.parseInt(element.getAttribute("minCon"));
final int balance = Integer.parseInt(element.getAttribute("balance"));
String switchTypeStr = element.getAttribute("switchType");
int switchType = switchTypeStr.equals("") ? -1 : Integer.parseInt(switchTypeStr);
String slaveThresholdStr = element.getAttribute("slaveThreshold");
int slaveThreshold = slaveThresholdStr.equals("") ? -1 : Integer.parseInt(slaveThresholdStr);
String tempReadHostAvailableStr = element.getAttribute("tempReadHostAvailable");
boolean tempReadHostAvailable = !tempReadHostAvailableStr.equals("") && Integer.parseInt(tempReadHostAvailableStr) > 0;
final String heartbeatSQL = element.getElementsByTagName("heartbeat").item(0).getTextContent();
// 获取当前下所有的
NodeList writeNodes = element.getElementsByTagName("writeHost");
// 初始化当前中,和的临时变量(数组及集合)
DBHostConfig[] writeDbConfs = new DBHostConfig[writeNodes.getLength()];
Map readHostsMap = new HashMap<>(2);
// 逐个进行处理
for (int w = 0; w < writeDbConfs.length; w++) {
Element writeNode = (Element) writeNodes.item(w);
// 创建对应的DBHostConfig对象,并加入到临时变量的数组中
writeDbConfs[w] = createDBHostConf(name, writeNode, maxCon, minCon);
// 获取当前下所有的
NodeList readNodes = writeNode.getElementsByTagName("readHost");
if (readNodes.getLength() != 0) {
// 创建存放当前下所有的临时数组
DBHostConfig[] readDbConfs = new DBHostConfig[readNodes.getLength()];
// 逐个进行处理
for (int r = 0; r < readDbConfs.length; r++) {
Element readNode = (Element) readNodes.item(r);
// 创建对应的DBHostConfig对象,并加入到临时变量的数组中
readDbConfs[r] = createDBHostConf(name, readNode, maxCon, minCon);
}
// 将准备好的临时数组注册到临时变量(Map集合)中
readHostsMap.put(w, readDbConfs);
}
}
// 根据各属性的临时变量创建DataHostConfig
DataHostConfig hostConf = new DataHostConfig(name,
writeDbConfs, readHostsMap, switchType, slaveThreshold, tempReadHostAvailable);
hostConf.setMaxCon(maxCon);
hostConf.setMinCon(minCon);
hostConf.setBalance(balance);
hostConf.setHearbeatSQL(heartbeatSQL);
// 将当前对应的DataHostConfig注册到XMLSchemaLoader的内部清单中
dataHosts.put(hostConf.getName(), hostConf);
}
}
当中,createDBHostConf()
这个函数在DBLE和MyCat中,基本功能是一致的:
- 获取
或
的属性:host、url、user、password、usingDecrypt和weight - 检查host、url和user都不为空
- 从host属性中分离出ip和port
- 对password属性的内容进行RSA的解密(usingDecrypt属性为1时)
但是,由于设计思路的分歧,DBLE裁剪了MyCat中的以下功能:
- 非MySQL数据库的支持(通过
的dbType属性提供) - 其他JDBC Driver的支持(通过
的dbDriver属性提供) - 无用属性filters和logTime
private DBHostConfig createDBHostConf(String dataHost, Element node, int maxCon, int minCon) {
// 加载必须属性host、url和user
String nodeHost = node.getAttribute("host");
String nodeUrl = node.getAttribute("url");
String user = node.getAttribute("user");
String ip = null;
int port = 0;
// 检查必须属性是否都不为空
if (empty(nodeHost) || empty(nodeUrl) || empty(user)) {
throw new ConfigException(
"dataHost " + dataHost +
" define error,some attributes of this element is empty: " +
nodeHost);
}
// 从host属性中分离出ip和port
int colonIndex = nodeUrl.indexOf(':');
ip = nodeUrl.substring(0, colonIndex).trim();
port = Integer.parseInt(nodeUrl.substring(colonIndex + 1).trim());
// 加载password和usingDecrypt属性
String password = node.getAttribute("password");
String usingDecrypt = node.getAttribute("usingDecrypt");
// 对password属性的内容进行RSA的解密
String passwordEncryty = DecryptUtil.dbHostDecrypt(usingDecrypt, nodeHost, user, password);
// 创建目标DBHostConfig对象并根据各个属性赋值
DBHostConfig conf = new DBHostConfig(nodeHost, ip, port, nodeUrl, user, passwordEncryty);
conf.setMaxCon(maxCon);
conf.setMinCon(minCon);
// 读取weight属性并对DBHostConfig.weight赋值
String weightStr = node.getAttribute("weight");
int weight = "".equals(weightStr) ? PhysicalDBPool.WEIGHT : Integer.parseInt(weightStr);
conf.setWeight(weight);
// 返回准备好的DBHostConfig对象
return conf;
}
处理标签——loadDataNodes()
与loadDataHosts()
相似,loadDataNodes()
的使命是将
标签加载成与之逻辑结构相同的Java对象。在这里,加载目标就是DataNodeConfig
对象。
DataNodeConfig属性 | |
---|---|
name | String name |
database | String database |
dataHost | String dataHost |
可以看出,相对于
和DataHostConfig
,
和DataNodeConfig
要简单得多。但是,MyCat和DBLE为了让
支持一种自制语法,loadDataNodes()
的代码变得复杂了许多。
从现有代码来看,这个语法是为了减少schema.xml的编写量(并不会减少DataNodeConfig
对象的个数),将多个拥有同样dataHost属性或database属性的
缩写成一个
。
private void loadDataNodes(Element root) {
// 提取schema.xml中所有的标签
NodeList list = root.getElementsByTagName("dataNode");
// 逐个标签进行处理
for (int i = 0, n = list.getLength(); i < n; i++) {
Element element = (Element) list.item(i);
// 加载所有属性到临时变量中:name, dataHost和database
String dnNamePre = element.getAttribute("name");
String databaseStr = element.getAttribute("database");
if (lowerCaseNames) {
databaseStr = databaseStr.toLowerCase();
}
String host = element.getAttribute("dataHost");
if (empty(dnNamePre) || empty(databaseStr) || empty(host)) {
throw new ConfigException("dataNode " + dnNamePre + " define error ,attribute can't be empty");
}
// 根据用户属性中的输入,判断用户是否使用了缩写语法
String[] dnNames = SplitUtil.split(dnNamePre, ',', '$', '-');
String[] databases = SplitUtil.split(databaseStr, ',', '$', '-');
String[] hostStrings = SplitUtil.split(host, ',', '$', '-');
if (dnNames.length > 1 && dnNames.length != databases.length * hostStrings.length) {
throw new ConfigException("dataNode " + dnNamePre +
" define error ,dnNames.length must be=databases.length*hostStrings.length");
}
if (dnNames.length > 1) {
// 如果用户使用了缩写语法,
// 就使用“外层dataHost,内层database”的两层循环去生生成多个DataNodeConfig,
// 并注册到XMLSchemaLoader的内部清单中
List mhdList = mergerHostDatabase(hostStrings, databases);
for (int k = 0; k < dnNames.length; k++) {
String[] hd = mhdList.get(k);
String dnName = dnNames[k];
String databaseName = hd[1];
String hostName = hd[0];
createDataNode(dnName, databaseName, hostName);
}
} else {
// 如果用户没有使用缩写语法,
// 就直接生成一个DataNodeConfig,并注册到XMLSchemaLoader的内部清单中
createDataNode(dnNamePre, databaseStr, host);
}
}
}
loadDataNodes()
里用到了两个辅助方法,在这里简单说明一下:
-
mergerHostDatabase()
是在确定用户使用了缩写语法之后,求两个字符串属性dataHost × database(dataHost与database的叉乘)
private List mergerHostDatabase(String[] hostStrings, String[] databases) {
List mhdList = new ArrayList<>();
for (String hostString : hostStrings) {
for (String database : databases) {
String[] hd = new String[2];
hd[0] = hostString;
hd[1] = database;
mhdList.add(hd);
}
}
return mhdList;
}
-
createDataNode()
除了创建一个DataNodeConfig
对象外,还必须说明它会自动把这个新对象注册到XMLSchemaLoader
的内部清单里
private void createDataNode(String dnName, String database, String host) {
// 创建新的DataNodeConfig对象
DataNodeConfig conf = new DataNodeConfig(dnName, database, host);
// 注册到XMLSchemaLoader之前的检查1:名称不能与已有的重复
if (dataNodes.containsKey(conf.getName())) {
throw new ConfigException("dataNode " + conf.getName() + " duplicated!");
}
// 注册到XMLSchemaLoader之前的检查2:dataHost属性指定的DataHost必须已注册
if (!dataHosts.containsKey(host)) {
throw new ConfigException("dataNode " + dnName + " reference dataHost:" + host + " not exists!");
}
// 将新的DataNodeConfig对象注册到XMLSchemaLoader的内部清单中
dataNodes.put(conf.getName(), conf);
}
处理标签——loadSchemas()
loadSchemas()
的使命是将
标签加载成SchemaConfig
对象。在逻辑结构上,
标签和SchemaConfig
对象有很多共同的地方。
SchemaConfig属性 | |
---|---|
name | String name |
dataNode | String dataNode |
sqlMaxLimit | int defaultMaxLimit |
此外, 直接加载一般属性,例如name、primaryKey之类。 读取用户指定的分片算法(rule属性)和逻辑分片(dataNode属性)的字面值,进行是否存在之类的检查后,与之前的rule.xml和 如果含有 tips: 创建 读取简单属性name、primaryKey、autoIncrement、needAddLimit、type、rule和ruleRequired 读取属性dataNode,这个属性可以一次指定多个dataNode,使用“,”、“$”或“-”来分隔 调用checkDataNodeExists()来检查用户给这个 如果当前处理的 如果用户在配置 将初始化完成的 |