数据库路由中间件MyCat - 源代码篇(16)
此文已由作者张镐薪授权网易云社区发布。
欢迎访问网易云社区,了解更多网易技术产品运营经验。
5. 路由模块
真正取得RouteResultset的步骤:AbstractRouteStrategy的route方法:对应源代码:
public RouteResultset route(SystemConfig sysConfig, SchemaConfig schema, int sqlType, String origSQL,
String charset, ServerConnection sc, LayerCachePool cachePool) throws SQLNonTransientException { /**
* 处理一些路由之前的逻辑
* 全局序列号,父子表插入
*/
if ( beforeRouteProcess(schema, sqlType, origSQL, sc) ) return null; /**
* SQL 语句拦截
*/
String stmt = MycatServer.getInstance().getSqlInterceptor().interceptSQL(origSQL, sqlType); if (origSQL != stmt && LOGGER.isDebugEnabled()) {
LOGGER.debug("sql intercepted to " + stmt + " from " + origSQL);
} //对应schema标签checkSQLschema属性,把表示schema的字符去掉
if (schema.isCheckSQLSchema()) {
stmt = RouterUtil.removeSchema(stmt, schema.getName());
} RouteResultset rrs = new RouteResultset(stmt, sqlType); /**
* 优化debug loaddata输出cache的日志会极大降低性能
*/
if (LOGGER.isDebugEnabled() && origSQL.startsWith(LoadData.loadDataHint)) {
rrs.setCacheAble(false);
} /**
* rrs携带ServerConnection的autocommit状态用于在sql解析的时候遇到
* select ... for update的时候动态设定RouteResultsetNode的canRunInReadDB属性
*/
if (sc != null ) {
rrs.setAutocommit(sc.isAutocommit());
} /**
* DDL 语句的路由
*/
if (ServerParse.DDL == sqlType) { return RouterUtil.routeToDDLNode(rrs, sqlType, stmt, schema);
} /**
* 检查是否有分片
*/
if (schema.isNoSharding() && ServerParse.SHOW != sqlType) {
rrs = RouterUtil.routeToSingleNode(rrs, schema.getDataNode(), stmt);
} else {
RouteResultset returnedSet = routeSystemInfo(schema, sqlType, stmt, rrs); if (returnedSet == null) {
rrs = routeNormalSqlWithAST(schema, stmt, rrs, charset, cachePool);
}
} return rrs;
}
5.3 路由之前的逻辑 - 判断子表插入以及全局序列号的生成:
AbstractRouteStrategy.java
/**
* 路由之前必要的处理
* 主要是全局序列号插入,还有子表插入
*/private boolean beforeRouteProcess(SchemaConfig schema, int sqlType, String origSQL, ServerConnection sc)
throws SQLNonTransientException { return RouterUtil.processWithMycatSeq(schema, sqlType, origSQL, sc)
|| (sqlType == ServerParse.INSERT && RouterUtil.processERChildTable(schema, origSQL, sc))
|| (sqlType == ServerParse.INSERT && RouterUtil.processInsert(schema, sqlType, origSQL, sc));
}
这里利用了Java的一个特性,||表达式,前半部分如果为真,则后半部分不会被执行。首先执行RouterUtil.processWithMycatSeq(schema, sqlType, origSQL, sc),这个方法是判断是否是显示使用全局序列号的sql语句,比如像:insert into table1(id,name) values(next value for MYCATSEQ_GLOBAL,‘test’);对于这样的语句处理是先将改写next value for MYCATSEQ_GLOBAL 为调用全局ID生成的ID,之后进入AST语句解析路由
如果不是,则执行(sqlType == ServerParse.INSERT && RouterUtil.processERChildTable(schema, origSQL, sc)),这个方法判断是否是子表插入:部分代码:
String tableName = StringUtil.getTableName(origSQL).toUpperCase();final TableConfig tc = schema.getTables().get(tableName);//判断是否为子表,如果不是,只会返回falseif (null != tc && tc.isChildTable()) {final RouteResultset rrs = new RouteResultset(origSQL, ServerParse.INSERT);
String joinKey = tc.getJoinKey();//因为是Insert语句,用MySqlInsertStatement进行parseMySqlInsertStatement insertStmt = (MySqlInsertStatement) (new MySqlStatementParser(origSQL)).parseInsert();
......
这里注意,所有类型的SQL语句都有druid对应的SQLparser,比如说这里的插入语句就用MySqlInsertStatement解析。druidparser在这节先不讲,会在 AST语义解析路由中详细讲述。
接上面代码:
//判断条件完整性,取得解析后语句列中的joinkey列的index
int joinKeyIndex = getJoinKeyIndex(insertStmt.getColumns(), joinKey); if (joinKeyIndex == -1) {
String inf = "joinKey not provided :" + tc.getJoinKey() + "," + insertStmt;
LOGGER.warn(inf); throw new SQLNonTransientException(inf);
} //子表不支持批量插入
if (isMultiInsert(insertStmt)) {
String msg = "ChildTable multi insert not provided";
LOGGER.warn(msg); throw new SQLNonTransientException(msg);
} //取得joinkey的值
String joinKeyVal = insertStmt.getValues().getValues().get(joinKeyIndex).toString(); String sql = insertStmt.toString(); // try to route by ER parent partion key
//如果是二级子表(父表不再有父表),并且分片字段正好是joinkey字段,调用routeByERParentKey
RouteResultset theRrs = RouterUtil.routeByERParentKey(sc, schema, ServerParse.INSERT, sql, rrs, tc, joinKeyVal); if (theRrs != null) { boolean processedInsert=false; //判断是否需要全局序列号
if ( sc!=null && tc.isAutoIncrement()) {
String primaryKey = tc.getPrimaryKey();
processedInsert=processInsert(sc,schema,ServerParse.INSERT,sql,tc.getName(),primaryKey);
} if(processedInsert==false){
rrs.setFinishedRoute(true);
sc.getSession2().execute(rrs, ServerParse.INSERT);
} return true;
} // route by sql query root parent's datanode
//如果不是二级子表或者分片字段不是joinKey字段结果为空,则启动异步线程去后台分片查询出datanode
//只要查询出上一级表的parentkey字段的对应值在哪个分片即可
final String findRootTBSql = tc.getLocateRTableKeySql().toLowerCase() + joinKeyVal; if (LOGGER.isDebugEnabled()) {
LOGGER.debug("find root parent's node sql " + findRootTBSql);
} ListenableFuture<String> listenableFuture = MycatServer.getInstance().
getListeningExecutorService().submit(new Callable<String>() { @Override
public String call() throws Exception {
FetchStoreNodeOfChildTableHandler fetchHandler = new FetchStoreNodeOfChildTableHandler(); return fetchHandler.execute(schema.getName(), findRootTBSql, tc.getRootParent().getDataNodes());
}
}); Futures.addCallback(listenableFuture, new FutureCallback<String>() { @Override
public void onSuccess(String result) { //结果为空,证明上一级表中不存在那条记录,失败
if (Strings.isNullOrEmpty(result)) {
StringBuilder s = new StringBuilder();
LOGGER.warn(s.append(sc.getSession2()).append(origSQL).toString() + " err:" + "can't find (root) parent sharding node for sql:" + origSQL);
sc.writeErrMessage(ErrorCode.ER_PARSE_ERROR, "can't find (root) parent sharding node for sql:" + origSQL); return;
} if (LOGGER.isDebugEnabled()) {
LOGGER.debug("found partion node for child table to insert " + result + " sql :" + origSQL);
} //找到分片,进行插入(和其他的一样,需要判断是否需要全局自增ID)
boolean processedInsert=false; if ( sc!=null && tc.isAutoIncrement()) { try {
String primaryKey = tc.getPrimaryKey();
processedInsert=processInsert(sc,schema,ServerParse.INSERT,origSQL,tc.getName(),primaryKey);
} catch (SQLNonTransientException e) {
LOGGER.warn("sequence processInsert error,",e);
sc.writeErrMessage(ErrorCode.ER_PARSE_ERROR , "sequence processInsert error," + e.getMessage());
}
} if(processedInsert==false){
RouteResultset executeRrs = RouterUtil.routeToSingleNode(rrs, result, origSQL);
sc.getSession2().execute(executeRrs, ServerParse.INSERT);
} } @Override
public void onFailure(Throwable t) {
StringBuilder s = new StringBuilder();
LOGGER.warn(s.append(sc.getSession2()).append(origSQL).toString() + " err:" + t.getMessage());
sc.writeErrMessage(ErrorCode.ER_PARSE_ERROR, t.getMessage() + " " + s.toString());
}
}, MycatServer.getInstance().
getListeningExecutorService()); return true;
}return false;
如果返回false,则继续执行(sqlType == ServerParse.INSERT && RouterUtil.processInsert(schema, sqlType, origSQL, sc))
这个是处理一般的SQL插入语句,将其中的自增主键字段的值改写成内置的全局ID生成器生成的id。RouterUtil.java:
public static boolean processInsert(SchemaConfig schema, int sqlType,
String origSQL, ServerConnection sc) throws SQLNonTransientException {
String tableName = StringUtil.getTableName(origSQL).toUpperCase();
TableConfig tableConfig = schema.getTables().get(tableName); boolean processedInsert=false; //判断是有自增字段
if (null != tableConfig && tableConfig.isAutoIncrement()) {
String primaryKey = tableConfig.getPrimaryKey();
processedInsert=processInsert(sc,schema,sqlType,origSQL,tableName,primaryKey);
} return processedInsert;
}
更多网易技术、产品、运营经验分享请点击。
相关文章:
【推荐】 为何要在网站上设置的验证码
【推荐】 如何玩转基于风险的测试
数据库路由中间件MyCat - 源代码篇(16)的更多相关文章
- 数据库路由中间件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 - 源代码篇(14)
此文已由作者张镐薪授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 对于表的dataNode对应关系,有个特殊配置即类似dataNode="distributed(d ...
- 数据库路由中间件MyCat - 源代码篇(4)
此文已由作者易国强授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 2. 前端连接建立与认证 Title:MySql连接建立以及认证过程client->MySql:1.T ...
- 数据库路由中间件MyCat - 源代码篇(2)
此文已由作者张镐薪授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 2. 前端连接建立与认证 Title:MySql连接建立以及认证过程client->MySql:1.T ...
- 数据库路由中间件MyCat - 源代码篇(10)
此文已由作者张镐薪授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 3. 连接模块 3.5 后端连接 3.5.2 后端连接获取与维护管理 还是那之前的流程, st=>st ...
随机推荐
- 翻译Lanlet2
Here is more information on the basic primitives that make up a Lanelet2 map. Read here for a primer ...
- DIV CSS 笔记
/*针对谷歌浏览器内核支持的CSS样式*/ <style type="text/css"> @media screen and (-webkit-min-device- ...
- Java_io_02_从一个目录拷贝文件到另一个目录下
java从一个目录拷贝文件到另一个目录下 http://www.cnblogs.com/langtianya/p/4857524.html ** * 复制单个文件 * @param oldPath ...
- Git之Eclipse提交项目到Github并实现多人协作
一.Eclipece提交项目到Github 见 eclipse提交项目到github 二.利用github组织实现多人协作 1.新建组织: New organization
- linux命令学习笔记(10):cat 命令
cat命令的用途是连接文件或标准输入并打印.这个命令常用来显示文件内容,或者将几个文件连接起来显示, 或者从标准输入读取内容并显示,它常与重定向符号配合使用. .命令格式: cat [选项] [文件] ...
- 树套树Day2
滚回来更新,,, 在Day1我们学了最基本的线段树套平衡树 Day2开始我们要学习一些黑科技 (所以很大概率会出现Day3 w 1.线段树上的黑科技 这一段我们分几项来讲 1.权值线段树 权值线段树以 ...
- 使用 py2exe 打包 Python 程序
上回在<使用 PyInstaller 打包 Python 程序>中,我们介绍了使用 PyInstaller 对 Python 程序进行打包,今天带大家认识一个新的工具:py2exe. 接下 ...
- nodejs调试:node-inspector
基于Chrome浏览器的调试器 既然我们可以通过V8的调试插件来调试,那是否也可以借用Chrome浏览器的JavaScript调试器来调试呢?node-inspector模块提供了这样一种可能.我们需 ...
- OIer应该知道的二进制知识
计算机使用\(2\)进制,这是众所周知的.在学习\(OI\)的过程中,\(2\)进制也显得尤为重要.有时候,细节决定成败,所以我想总结一下容易被遗忘和误解的关于\(2\)进制的知识. 1.运算符 &a ...
- 在CentOS 7上安装Node.js的4种方法(包含npm)
Node.js和Javascript有着千丝万缕的联系,可以说Node.js让Javascript显得从未如此强大.好吧…微魔其实是个门外汉…但是这并不能阻碍微魔学习探索未知的信心~今天在国外闲逛,看 ...