数据库路由中间件MyCat - 源代码篇(14)
此文已由作者张镐薪授权网易云社区发布。
欢迎访问网易云社区,了解更多网易技术产品运营经验。
对于表的dataNode对应关系,有个特殊配置即类似dataNode="distributed(dn$1-10)",这个含义是:
/**
* distribute datanodes in multi hosts,means ,dn1 (host1),dn100
* (host2),dn300(host3),dn2(host1),dn101(host2),dn301(host3)...etc
* 将每个host上的datanode按照host重新排列。比如上面的例子host1拥有dn1,dn2,host2拥有dn100,dn101,host3拥有dn300,dn301,
* 按照host重新排列: 0->dn1 (host1),1->dn100(host2),2->dn300(host3),3->dn2(host1),4->dn101(host2),5->dn301(host3)
*
* @param theDataNodes
*/
private void distributeDataNodes(ArrayList<String> theDataNodes) {
Map<String, ArrayList<String>> newDataNodeMap = new HashMap<String, ArrayList<String>>(dataHosts.size()); for (String dn : theDataNodes) {
DataNodeConfig dnConf = dataNodes.get(dn);
String host = dnConf.getDataHost();
ArrayList<String> hostDns = newDataNodeMap.get(host);
hostDns = (hostDns == null) ? new ArrayList<String>() : hostDns;
hostDns.add(dn);
newDataNodeMap.put(host, hostDns);
} ArrayList<String> result = new ArrayList<String>(theDataNodes.size()); boolean hasData = true; while (hasData) {
hasData = false; for (ArrayList<String> dns : newDataNodeMap.values()) { if (!dns.isEmpty()) {
result.add(dns.remove(0));
hasData = true;
}
}
}
theDataNodes.clear();
theDataNodes.addAll(result);
}
读取完所有表之后,记录好DB类型,这对之后的sql语句路由解析有帮助。将所有schema的配置保存在:
private final Map<String, SchemaConfig> schemas;
4.3 server.xml
之后会读取载入server配置。XMLConfigLoader.java:
public XMLConfigLoader(SchemaLoader schemaLoader) {
XMLServerLoader serverLoader = new XMLServerLoader(); this.system = serverLoader.getSystem(); this.users = serverLoader.getUsers(); this.quarantine = serverLoader.getQuarantine(); this.cluster = serverLoader.getCluster(); this.dataHosts = schemaLoader.getDataHosts(); this.dataNodes = schemaLoader.getDataNodes(); this.schemas = schemaLoader.getSchemas();
schemaLoader = null;
}
XMLServerLoader.java
public XMLServerLoader() { this.system = new SystemConfig(); this.users = new HashMap<String, UserConfig>(); this.quarantine = new QuarantineConfig(); this.load();
}private void load() { //读取server.xml配置
InputStream dtd = null;
InputStream xml = null; try {
dtd = XMLServerLoader.class.getResourceAsStream("/server.dtd");
xml = XMLServerLoader.class.getResourceAsStream("/server.xml");
Element root = ConfigUtil.getDocument(dtd, xml).getDocumentElement(); //加载System标签
loadSystem(root); //加载User标签
loadUsers(root); //加载集群配置
this.cluster = new ClusterConfig(root, system.getServerPort()); //加载权限和黑白名单
loadQuarantine(root);
} catch (ConfigException e) { throw e;
} catch (Exception e) { throw new ConfigException(e);
} finally { if (dtd != null) { try {
dtd.close();
} catch (IOException e) {
}
} if (xml != null) { try {
xml.close();
} catch (IOException e) {
}
}
}
}
首先加载System标签
5. 路由模块
5.1 路由模块组成:
路由模块,我们可以先把他当做个黑盒,看下输入和输出都是神马。输入,很明显,就是个SQL语句,原生的,不加任何修饰的,纯洁的,从客户端发过来刚刚被解码的SQL语句。
输出呢?就是个优化,改写后的SQL语句,以及要发送到的后台分片。
这个RouteResultSet就是输出,长什么样子呢?
下图是主要涉及到的类:
RouteResultSet:
sqlType:SQL类型(select?insert?…)
nodes: 语句和Datanode对应关系。一条语句可以根据不同节点拆成多条不同语句
subTables:分表,1.6后功能,单node多表
sqlStatement:经过DruidParser解析后的语句
limitStart,limitSize:含有limit的SQL的起始点和长度
cacheAble:是否可以缓存(MyCat缓存中会保存SQL(key)->RouteResultSet(value))
primaryKey:为了实现以后完整的主键缓存而预留
sqlMerge:带有合并函数的sql语句处理类
callStatement:是否为调用存储过程的语句(call)
globalTableFlag:操作表是否包含全局表
isFinishedRoute:是否路由完成
autocommit:是否为自动提交
isLoadData:是否是LoadData命令
canRunInReadDb:是否能在读节点上运行
runOnSlave:是否在从节点上运行
procedure:调用存储过程处理类
RouteResultSetNode:
serialVersionUID:全局序列化类版本标识
name:数据节点名称
statement:实际执行的语句
srcStatement:源语句
sqlType:sql类型
canRunInReadDB:是否可以在读节点运行
hasBlanceFlag:是否包含balance属性
hintMap:注解类型和注解sql语句的map
其他类似
对于路由模块,他需要完成的操作就是MyCat的核心功能之一,将前端发送过来的SQL语句路由到后面合适的分片上。那么,我们至少需要从SQL中解析出来这个SQL对应的是那张表,对应的分片规则是什么?有没有筛选条件,根据筛选条件我们是不是能路由到某几个分片上。是不是插入语句,需不需要生成全局唯一ID?等等等等
MyCat路由模块,大致上包括SQL语句分类,SQL语义解析,SQL语句改写,全局ID生成。
5.2 SQL语句分类
首先,我们先回顾下,SQL语句通过客户端发送给了MyCat,MyCat在前端连接模块完成包解码,在这之后,对SQL语句进行分类处理(其实就是构建自己一套简单的语法分支)。
如何分类?其实就是通过语句第一个词先进行第一步分类:
ServerQueryHandler.java:
public void query(String sql) {
ServerConnection c = this.source; if (LOGGER.isDebugEnabled()) {
LOGGER.debug(new StringBuilder().append(c).append(sql).toString());
} //
int rs = ServerParse.parse(sql); int sqlType = rs & 0xff; switch (sqlType) { //explain sql
case ServerParse.EXPLAIN:
ExplainHandler.handle(sql, c, rs >>> 8); break; //explain2 datanode=? sql=?
case ServerParse.EXPLAIN2:
Explain2Handler.handle(sql, c, rs >>> 8); break; case ServerParse.SET:
SetHandler.handle(sql, c, rs >>> 8); break; case ServerParse.SHOW:
ShowHandler.handle(sql, c, rs >>> 8); break; case ServerParse.SELECT: if(QuarantineHandler.handle(sql, c)){
SelectHandler.handle(sql, c, rs >>> 8);
} break; case ServerParse.START:
StartHandler.handle(sql, c, rs >>> 8); break; case ServerParse.BEGIN:
BeginHandler.handle(sql, c); break; //不支持oracle的savepoint事务回退点
case ServerParse.SAVEPOINT:
SavepointHandler.handle(sql, c); break; case ServerParse.KILL:
KillHandler.handle(sql, rs >>> 8, c); break; //不支持KILL_Query
case ServerParse.KILL_QUERY:
LOGGER.warn(new StringBuilder().append("Unsupported command:").append(sql).toString());
c.writeErrMessage(ErrorCode.ER_UNKNOWN_COM_ERROR,"Unsupported command"); break; case ServerParse.USE:
UseHandler.handle(sql, c, rs >>> 8); break; case ServerParse.COMMIT:
c.commit(); break; case ServerParse.ROLLBACK:
c.rollback(); break; case ServerParse.HELP:
LOGGER.warn(new StringBuilder().append("Unsupported command:").append(sql).toString());
c.writeErrMessage(ErrorCode.ER_SYNTAX_ERROR, "Unsupported command"); break; case ServerParse.MYSQL_CMD_COMMENT:
c.write(c.writeToBuffer(OkPacket.OK, c.allocate())); break; case ServerParse.MYSQL_COMMENT:
c.write(c.writeToBuffer(OkPacket.OK, c.allocate())); break; case ServerParse.LOAD_DATA_INFILE_SQL:
c.loadDataInfileStart(sql); break; default: if(readOnly){
LOGGER.warn(new StringBuilder().append("User readonly:").append(sql).toString());
c.writeErrMessage(ErrorCode.ER_USER_READ_ONLY, "User readonly"); break;
} if(QuarantineHandler.handle(sql, c)){
c.execute(sql, rs & 0xff);
}
}
}
每种语句都有自己对应的Handler,我们这里将用Select语句举例。第一个词决定语句是什么类型(CURD)的,第二个词将更细粒度的区分语句,这里是不同的Select。首先我们思考下,不是所有的select语句都需要路由到后面数据库的。比如 select version这样的语句,可以直接回复MyCat的version。还有select LAST_INSERT_ID这样的(MyCat),上次插入的全局ID是在MyCat会缓存的。
所以,MyCat对于select的第二个词也做解析,可以将select语句分为可以直接回复的和必须路由到后面分片得到结果的。SelectHandler.java:
更多网易技术、产品、运营经验分享请点击。
相关文章:
【推荐】 为Docker容器设置静态IP
【推荐】 Spring Boot 学习系列(05)—自定义视图解析规则
数据库路由中间件MyCat - 源代码篇(14)的更多相关文章
- 数据库路由中间件MyCat - 源代码篇(1)
此文已由作者张镐薪授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 进入了源代码篇,我们先从整体入手,之后拿一个简单流程前端连接建立与认证作为例子,理清代码思路和设计模式.然后 ...
- 数据库路由中间件MyCat - 源代码篇(13)
此文已由作者张镐薪授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 4.配置模块 4.2 schema.xml 接上一篇,接下来载入每个schema的配置(也就是每个MyCat ...
- 数据库路由中间件MyCat - 源代码篇(7)
此文已由作者张镐薪授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 3. 连接模块 3.4 FrontendConnection前端连接 构造方法: public Fronte ...
- 数据库路由中间件MyCat - 源代码篇(15)
此文已由作者张镐薪授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. public static void handle(String stmt, ServerConnectio ...
- 数据库路由中间件MyCat - 源代码篇(17)
此文已由作者张镐薪授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 调用processInsert(sc,schema,sqlType,origSQL,tableName,pr ...
- 数据库路由中间件MyCat - 源代码篇(4)
此文已由作者易国强授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 2. 前端连接建立与认证 Title:MySql连接建立以及认证过程client->MySql:1.T ...
- 数据库路由中间件MyCat - 源代码篇(2)
此文已由作者张镐薪授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 2. 前端连接建立与认证 Title:MySql连接建立以及认证过程client->MySql:1.T ...
- 数据库路由中间件MyCat - 源代码篇(16)
此文已由作者张镐薪授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 5. 路由模块 真正取得RouteResultset的步骤:AbstractRouteStrategy的ro ...
- 数据库路由中间件MyCat - 源代码篇(10)
此文已由作者张镐薪授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 3. 连接模块 3.5 后端连接 3.5.2 后端连接获取与维护管理 还是那之前的流程, st=>st ...
随机推荐
- 【题解】quake
[题解]\(quake\) 题目大意 我们共有报酬\(f\)元,一条边有它的价值\(w_i\),有它的建造时间\(t_i\).要求建一些边,生成一颗树.求最大的利润率. 数据范围 \(n\le 400 ...
- 【题解】Painting Fence
[题解]Painting Fence 分治模板.贪心加分治.直接\(O(n^2logn)\)分治过去.考虑一块联通的柱形是子问题的,是递归的,贪心分治就可.记得对\(r-l+1\)取\(min\). ...
- Linux下/usr/bin/python被删除的后果
可能部分的人使用linux都有直接root登陆的习惯,这有很大的便利性,因为很多的命令不需要使用sudo请求root权限.但是使用root权限,所有的命令都会立即被执行,即使这个命令是对系统有害处的. ...
- springboot 默认tomcat配置
1. Spring Boot 能支持的最大并发量主要看其对Tomcat的设置,可以在配置文件中对其进行更改.当在配置文件中敲出max后提示值就是它的默认值. 我们可以看到默认设置中,Tomcat的最大 ...
- 调用jersey发布的接口webservice,通过HttpPost传递文件
项目媒体文件之前都是上传到七牛云处理,现在客户为了安全和私密性,准备将移动端拍摄的图片和视频传递到文件服务器,所以就想办法能不能在服务器端发布一个WebService,供移动端调用.刚好之前做的接口都 ...
- Java多线程编程核心 - 对象及变量的并发访问
1.什么是“线程安全”与“非线程安全”? “非线程安全”会在多个线程对同一对象总的实例变量进行并发访问时发生,产生的后果是“脏读”,也就是取到的数据其实是被更改过的. “线程安全”是以获得的实例变量的 ...
- BZOJ 1192 [HNOI2006]鬼谷子的钱袋:二进制 砝码称重问题
题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=1192 题意: 鬼谷子带了a元钱,他要把a元钱分装在小袋子中,使得任意不大于a的数目的钱,都 ...
- ubuntu安装 LNMP+redis
一.更新软件源 1.修改软件源为163的源 sudo vim /etc/apt/sources.list 替换源为163的源: deb http://mirrors.163.com/ubuntu/ i ...
- C语言实现wc基本功能
GitHub地址:https://github.com/hhx007/wc 项目要求 wc.exe 是一个常见的工具,它能统计文本文件的字符数.单词数和行数. 这个项目要求写一个命令行程序,模仿已有w ...
- PS 滤镜— —图像偏移
clc; clear all; close all; addpath('E:\PhotoShop Algortihm\Image Processing\PS Algorithm'); I=imread ...