Apache Camel组件之Control Bus

这是一个被直接集成在camel-core中的组件,其地位由此可见一斑。而且实现组成也是相当简单,加起来也才三个类ControlBusComponentControlBusEndpointControlBusProducer

1. 概述

Apache Camel在对EIP实现中,最终选择在其2.11版本完成了对于"Control Bus"模式的支持——内置了对于所集成系统的监控和管理。通俗点说就是你可以针对每一个Route进行诸如start,stop,suspend,resume,status,stats等等操作,这极大增加了程序运行时的灵活性。本文接下来的部分将对Apache Camel实现进行分析,做到知其所以然。

在Apache Camel中,你已经可以通过JMX,CamelContext提供的Java API,亦或是Event Notifier来实现管理和监控功能,但是ControlBus提供的是一种更加友好的开箱即食机制。

2. 源码解读

本次的用例如下:

// 注意这里的测试用例,笔者抛弃了官方文档的样例,旨在希望能提供了一种更具普适性的控制方式.
// 下面的方式结合camel-servlet可以无需额外的java代码实现诸如start,stop,suspend,resume,status,stats等等操作.
CamelTestUtil.defaultPrepareTest2(new RouteBuilder() {

	@Override
	public void configure() throws Exception {
		from("timer://myTimer?period=5000")//
				.id("timeRoute")//
				.setBody(simple("Current time is [ ${header.firedTime} ]"))//
				.to("stream:err");							
        
        // 以下是我们本次的研究重点
		from("stream:in?promptMessage=Enter something:")//
		  .setBody(simple("camelContext.getRouteStatus('${body}')"))
		  .to("controlbus:language:simple?async=true");
	}
});

以上测试用例启动后:

  1. 启动一个定时任务,每五秒中会以红色字体向控制台打印当前时间。
  2. 在控制台接收用户输入内容,作为routeId对相应的Route进行停止操作。

接下来我们依然参照之前的Apache Camel系列文章的惯用格式,分两部分进行讨论。

2.1 启动时

以上代码启动时候,我们重点关注.to("controlbus:language:simple?async=true")。我们将得到以下堆栈图:
ControlBusComponent

  1. 关于ControlBusComponent

    // ControlBusComponent.createEndpoint()
    protected Endpoint createEndpoint(String uri, String remaining, Map<String, Object> parameters) throws Exception {
        ControlBusEndpoint answer = new ControlBusEndpoint(uri, this);
    
        // does the control bus use a language 
        // 本测试用例中将走以下分支
        if (remaining != null && remaining.startsWith("language:")) {
            String lan = remaining.substring(9);
            answer.setLanguage(getCamelContext().resolveLanguage(lan));
        }
        
        // 得益于Camel优秀的类层次设计, 我们可以直接复用基类逻辑来实现赋值, 避免重复枯燥且易错的操作.
        setProperties(answer, parameters);
        return answer;
    }
    
  2. 关于ControlBusEndpoint。正如之前的文章,Endpoint作为Producer和Consumer的工厂,从其实现中我们就可以看出当前组件的支持情况。这里我们可以非常清晰地看出 ControlBus模式是只支持Producer的,也就是无法作为from()节点。

    @Override
    public Producer createProducer() throws Exception {
        CamelLogger logger = new CamelLogger(ControlBusProducer.class.getName(), loggingLevel);
        return new ControlBusProducer(this, logger);
    }
    
    @Override
    public Consumer createConsumer(Processor processor) throws Exception {
    	// 不支持Consumer
        throw new RuntimeCamelException("Cannot consume from a ControlBusEndpoint: " + getEndpointUri());
    }
    

至此,关于ControlBus的启动就算是完成了,剩下的就是等待用户的主动调用。

2.2 运行时

在上面的测试用例中,用户输入"timeRoute"(前面特意定义的routeId名称),逻辑最终会跳转到ControlBusProducer.process(Exchange exchange, AsyncCallback callback)中。

// ======================== ControlBusProducer.java
@Override
public boolean process(Exchange exchange, AsyncCallback callback) {
	// 本例中跳转到如下分支
	if (getEndpoint().getLanguage() != null) {
		try {
			processByLanguage(exchange, getEndpoint().getLanguage());
		} catch (Exception e) {
			exchange.setException(e);
		}
	} else if (getEndpoint().getAction() != null) {
		// action模式的调用更为简单, 但如果想要获得灵活性的支持, 就需要参考官方示例: 
		//	template.sendBody("controlbus:route?routeId=foo&action=start", null);
		try {
			processByAction(exchange);
		} catch (Exception e) {
			exchange.setException(e);
		}
	}

	callback.done(true);
	return true;
}

protected void processByLanguage(Exchange exchange, Language language) throws Exception {
	// 使用专门的内部类进行逻辑处理
	LanguageTask task = new LanguageTask(exchange, language);
	if (getEndpoint().isAsync()) {
		getEndpoint().getComponent().getExecutorService().submit(task);
	} else {
		task.run();
	}
}

// ======================== ControlBusProducer.LanguageTask.java
/**
 * Tasks to run when processing by language.
 */
private final class LanguageTask implements Runnable {

	private final Exchange exchange;
	private final Language language;

	private LanguageTask(Exchange exchange, Language language) {
		this.exchange = exchange;
		this.language = language;
	}

	@Override
	public void run() {
		String task = null;
		Object result = null;

		try {
			// create dummy exchange
			Exchange dummy = ExchangeHelper.createCopy(exchange, true);
			
			// 获取上一步传入的内容, 作为将要被执行的task
			task = dummy.getIn().getMandatoryBody(String.class);
			if (task != null) {
				// language为进行controlbus配置时候设置的, 本例中是simple
				//  其它支持的语言类型参见 ControlBusEndpoint类中字段language声明。
				Expression exp = language.createExpression(task);
				result = exp.evaluate(dummy, Object.class);
			}

			if (result != null && !getEndpoint().isAsync()) {
				// can only set result on exchange if sync
				exchange.getIn().setBody(result);
			}

			if (task != null) {
				logger.log("ControlBus task done [" + task + "] with result -> " + (result != null ? result : "void"));
			}
		} catch (Exception e) {
			logger.log("Error executing ControlBus task [" + task + "]. This exception will be ignored.", e);
		}
	}
}

因此,在ControlBus实现中:

  1. Language实现方式其实就是借助了Language拥有的强大指令执行能力来实现对于系统的监控管理。因此更具灵活性。
  2. Action实现方式则是通过约定的"start","stop"等关键字来实现对于系统的监控管理。

3. 总结

依托于Apache Camel的既有优秀设计,ControlBus模式的实现还是相当简单的,等同于对既有功能的一些简单回调,而无需额外的实现。这也正是我们产品开发中一直在追求的。

4. 福利环节

上面章节提到我们可以结合camel内置的servelt组件实现对于系统的远程控制,这里笔者给出一个经过验证的可行版本。

// ===== 为了确保读者快速上手看到效果, 这里笔者用jetty组件代替通常的servlet
restConfiguration().host("127.0.0.1")//127.0.0.1:8080/foo	
	.port("8080")//
	.contextPath("foo")//
	.setComponent("jetty");


// ===== 核心控制逻辑
from("rest:get:cb/{action}/{routeId}") // 以rest的形式约定调用格式
	.log("begin [ ${header.action} ] the route with id [ ${header.routeId}") // 
	// 其它诸如 suspend,resume,status,stats 可依据下面格式进行选择性添加
	.choice() //
	.when(simple("${header.action} == 'start'")).setBody(simple("camelContext.startRoute('${header.routeId}')"))//
	.when(simple("${header.action} == 'stop'")).setBody(simple("camelContext.stopRoute('${header.routeId}')"))//
	.otherwise().log("unkown action ${header.action}").setBody(simple("unkown action [ ${header.action} ] FOR [ ${header.routeId} ] ")).stop() // 传入未知action, 则返回提示性信息
	.end() //
	.to("controlbus:language:simple");

// ===== 请求示例:
//    1. 停止指定Route
//        http://127.0.0.1:8080/foo/cb/stop/timeRoute
//    2. 启动指定Route
//		  http://127.0.0.1:8080/foo/cb/start/timeRoute

5. Links

  1. Apache Camel监控之使用hawtio
  2. Office Site - Control Bus
  3. Apache Camel使用之集成SpingBoot Actuator2.0
  4. 《Apache Camel Developer’s Cookbook》P44
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章