前两篇讲了mycat 启动与数据查询的操作过程,今天来看一下catlet执行逻辑。如果对hint有所了解的话,就好理解一些什么是catlet,在这里就不做科普了。
先来看下catlet的调用:
/*!mycat:catlet=demo.catlets.MyHellowJoin */ select * from t_user;
StringBuilder sb = new StringBuilder().append('(');
while ((theId = ids.poll()) != null) {
batchRows.put(theId, rows.remove(theId));
sb.append(theId).append(',');
if (count++ > batchSize) {
break;
}
}
if (count == 0) {
return;
}
sb.deleteCharAt(sb.length() - 1).append(')');
String querySQL = "select b.user_id, b.title from hotnews b where id in "
+ sb;
ctx.executeNativeSQLParallJob(new String[] { "dn1", "dn2", "dn3" },
querySQL, new MyRowOutPutDataHandler(fields, ctx, batchRows));
由于本地数据库环境问题,结果始终没有反馈title字段的值,但并不影响我们进行代码分析。
直接看下整个catlet的调用时序吧,其中最主要的逻辑处理类有以下几个:HintCatletHandler、EngineCtx、SQLJob、Catlet(接口)、SQLJobHandler(接口)
io.mycat.route.RouteService 是SQL执行路由的入口,会跟原始SQL进行判断需要返回哪个 RouteResultset,同时会对返回结果进行缓存。
public RouteResultset route(SystemConfig sysconf, SchemaConfig schema,
int sqlType, String stmt, String charset, ServerConnection sc)
throws SQLNonTransientException {
RouteResultset rrs = null;
String cacheKey = null;
/**
* SELECT 类型的SQL, 检测
*/
if (sqlType == ServerParse.SELECT) {
cacheKey = schema.getName() + stmt;
rrs = (RouteResultset) sqlRouteCache.get(cacheKey);
if (rrs != null) {
return rrs;
}
}
/*!mycat: sql = select name from aa */
/*!mycat: schema = test */
// boolean isMatchOldHint = stmt.startsWith(OLD_MYCAT_HINT);
// boolean isMatchNewHint = stmt.startsWith(NEW_MYCAT_HINT);
// if (isMatchOldHint || isMatchNewHint ) {
int hintLength = RouteService.isHintSql(stmt);
if(hintLength != -1){
int endPos = stmt.indexOf("*/");
if (endPos > 0) {
// 用!mycat:内部的语句来做路由分析
// int hintLength = isMatchOldHint ? OLD_MYCAT_HINT.length() : NEW_MYCAT_HINT.length();
String hint = stmt.substring(hintLength, endPos).trim();
int firstSplitPos = hint.indexOf(HINT_SPLIT);
if(firstSplitPos > 0 ){
Map hintMap= parseHint(hint);
String hintType = (String) hintMap.get(MYCAT_HINT_TYPE);
String hintSql = (String) hintMap.get(hintType);
if( hintSql.length() == 0 ) {
LOGGER.warn("comment int sql must meet :/*!mycat:type=value*/ or /*#mycat:type=value*/: "+stmt);
throw new SQLSyntaxErrorException("comment int sql must meet :/*!mycat:type=value*/ or /*#mycat:type=value*/: "+stmt);
}
String realSQL = stmt.substring(endPos + "*/".length()).trim();
HintHandler hintHandler = HintHandlerFactory.getHintHandler(hintType);
if( hintHandler != null ) {
if ( hintHandler instanceof HintSQLHandler) {
/**
* 修复 注解SQL的 sqlType 与 实际SQL的 sqlType 不一致问题, 如: hint=SELECT,real=INSERT
* fixed by zhuam
*/
int hintSqlType = ServerParse.parse( hintSql ) & 0xff;
rrs = hintHandler.route(sysconf, schema, sqlType, realSQL, charset, sc, tableId2DataNodeCache, hintSql,hintSqlType,hintMap);
} else {
rrs = hintHandler.route(sysconf, schema, sqlType, realSQL, charset, sc, tableId2DataNodeCache, hintSql,sqlType,hintMap);
}
}else{
LOGGER.warn("TODO , support hint sql type : " + hintType);
}
}else{//fixed by [email protected]
LOGGER.warn("comment in sql must meet :/*!mycat:type=value*/ or /*#mycat:type=value*/: "+stmt);
throw new SQLSyntaxErrorException("comment in sql must meet :/*!mcat:type=value*/ or /*#mycat:type=value*/: "+stmt);
}
}
} else {
stmt = stmt.trim();
rrs = RouteStrategyFactory.getRouteStrategy().route(sysconf, schema, sqlType, stmt,
charset, sc, tableId2DataNodeCache);
}
io.mycat.route.handler.HintCatletHandler指定了处理引擎,同步触发执行命令。
public RouteResultset route(SystemConfig sysConfig, SchemaConfig schema,
int sqlType, String realSQL, String charset, ServerConnection sc,
LayerCachePool cachePool, String hintSQLValue,int hintSqlType, Map hintMap)
throws SQLNonTransientException {
// sc.setEngineCtx ctx
String cateletClass = hintSQLValue;
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("load catelet class:" + hintSQLValue + " to run sql "
+ realSQL);
}
try {
Catlet catlet = (Catlet) MycatServer.getInstance()
.getCatletClassLoader().getInstanceofClass(cateletClass);
catlet.route(sysConfig, schema, sqlType, realSQL,charset, sc, cachePool);
catlet.processSQL(realSQL, new EngineCtx(sc.getSession2()));
} catch (Exception e) {
LOGGER.warn("catlet error "+e);
throw new SQLNonTransientException(e);
}
return null;
}
接下来会在EngineCtx中把这个SQL执行操作丢入 BatchSQLJob 的队列中进行异步处理。
public void executeNativeSQLSequnceJob(String[] dataNodes, String sql,
SQLJobHandler jobHandler) {
for (String dataNode : dataNodes) {
SQLJob job = new SQLJob(jobId.incrementAndGet(), sql, dataNode,
jobHandler, this);
bachJob.addJob(job, false);
}
}
public void addJob(SQLJob newJob, boolean parallExecute) {
if (parallExecute) {
runJob(newJob);
} else {
waitingJobs.offer(newJob);
if (runningJobs.isEmpty()) {
SQLJob job = waitingJobs.poll();
if (job != null) {
runJob(job);
}
}
}
}
private void runJob(SQLJob newJob) {
// EngineCtx.LOGGER.info("run job " + newJob);
runningJobs.put(newJob.getId(), newJob);
MycatServer.getInstance().getBusinessExecutor().execute(newJob);
}
上面的 runJob方法会调用MycatServer中的executor进行异步处理逻辑。异步处理是由 io.mycat.sqlengine.SQLJob来完成的。
注意一下,看上去这段代码很简单,就是获取了后端数据库的链接,并扫行doFinished方法。容易遗漏的是将这个类本身当做一个回调handler注入了本次请求,即在 getConnection 这里。
public void run() {
try {
if (ds == null) {
RouteResultsetNode node = new RouteResultsetNode(
dataNodeOrDatabase, ServerParse.SELECT, sql);
// create new connection
MycatConfig conf = MycatServer.getInstance().getConfig();
PhysicalDBNode dn = conf.getDataNodes().get(node.getName());
dn.getConnection(dn.getDatabase(), true, node, this, node);
} else {
ds.getConnection(dataNodeOrDatabase, true, this, null);
}
} catch (Exception e) {
LOGGER.info("can't get connection for sql ,error:" + e);
doFinished(true);
}
}
@Override
public void rowResponse(byte[] row, BackendConnection conn) {
boolean finsihed = jobHandler.onRowData(dataNodeOrDatabase, row);
if (finsihed) {
conn.close("not needed by user proc");
doFinished(false);
}
}
@Override
public boolean onRowData(String dataNode, byte[] rowData) {
RowDataPacket rowDataPkg = ResultSetUtil.parseRowData(rowData, bfields);
// 获取Id字段,
String id = ByteUtil.getString(rowDataPkg.fieldValues.get(0));
byte[] bname = rowDataPkg.fieldValues.get(1);
// 查找ID对应的A表的记录
byte[] arow = arows.remove(id);
rowDataPkg = ResultSetUtil.parseRowData(arow, afields);
// 设置b.name 字段
rowDataPkg.add(bname);
ctx.writeRow(rowDataPkg);
// EngineCtx.LOGGER.info("out put row ");
return false;
}
再翻到本篇文章的开头,我们讲了这个catlet的作用是将两个数据合并到一起进行返回的,即在hintCatlet的数据与原生sql数据进行合并返回,因此我们光写出数据结果是不够的,还需要写出数据集的列信息。
public void writeHeader(List afields, List bfields) {
if (headerWrited.compareAndSet(false, true)) {
try {
writeLock.lock();
// write new header
ResultSetHeaderPacket headerPkg = new ResultSetHeaderPacket();
headerPkg.fieldCount = afields.size() +bfields.size()-1;
headerPkg.packetId = incPackageId();
LOGGER.debug("packge id " + headerPkg.packetId);
ServerConnection sc = session.getSource();
ByteBuffer buf = headerPkg.write(sc.allocate(), sc, true);
// wirte a fields
for (byte[] field : afields) {
field[3] = incPackageId();
buf = sc.writeToBuffer(field, buf);
}
// write b field
for (int i=1;i
public void writeEof() {
ServerConnection sc = session.getSource();
EOFPacket eofPckg = new EOFPacket();
eofPckg.packetId = incPackageId();
ByteBuffer buf = eofPckg.write(sc.allocate(), sc, false);
sc.write(buf);
LOGGER.info("write eof ,packgId:" + eofPckg.packetId);
}
到这里为止,整体catlet内部处理的事务都已经差不多了,接下去就是mycat原来的那套东西,就不在重复。
另外祝各位苦逼IT从业人员 5.1 节快乐,劳动最光荣,周周996,yeah~~