MyCat 学习笔记 第二十二篇 . mycat 源代码分析 下

9 篇文章 0 订阅

前两篇讲了mycat 启动与数据查询的操作过程,今天来看一下catlet执行逻辑。如果对hint有所了解的话,就好理解一些什么是catlet,在这里就不做科普了。

先来看下catlet的调用:

/*!mycat:catlet=demo.catlets.MyHellowJoin */ select * from t_user;

在 hint 位置,可以指明是catlet 还是sql,mycat 在sql语句路由处理的时候会进行判断处理。如果是catlet的话,则会调用指定的catlet实现类,在后一句sql数据处理时加入其他业务处理逻辑。今天是拿了 MyHellowJoin 进行尝试,可以根据部分代码看出这个catlet的目的是在ds1、ds2、ds3的数据源中从hotnews表获取title并合展示在后一句的结果集里。

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 runfriends@126.com
                	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;
	}

上面看到的catlet需要我们在SQL指明,并通过自定义的classloader从本地文件中读取class或是jar文件,classloader的加载、存储、实例化、链接的流程有兴趣可以去翻一下JVM的规范。

接下来会在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);

		}
	}

当然也可以选择并行执行,即所有的SQL任务不进任务队列进行进行后端数据查询操作。

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);
		}
	}

由于 SQLJob 类实现了 ResponseHandler 接口,因此会在数据返回时,触发数据响应事件

@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);
		}

	}


这里的 jobHandler 则需要同学根据业务逻辑进行自我实现,同时在 EngineCtx 中提供了最终数据写出方法

@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<byte[]> afields, List<byte[]> 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<bfields.size();i++) {
				  byte[] bfield = bfields.get(i);
				  bfield[3] = incPackageId();
				  buf = sc.writeToBuffer(bfield, buf);
				}
				// write field eof
				EOFPacket eofPckg = new EOFPacket();
				eofPckg.packetId = incPackageId();
				buf = eofPckg.write(buf, sc, true);
				sc.write(buf);
				//LOGGER.info("header outputed ,packgId:" + eofPckg.packetId);
			} finally {
				writeLock.unlock();
			}
		}

	}


最后还是要通过engineCtx向buffer写出数据

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~~






  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值