Java 流程引擎 mac-flow 简要性能评测

你们常常用流程图来分析和表述业务逻辑。在流程图上,业务流程一目了然,很是清晰,是需求分析、代码设计的利器。可是优秀且开源的Java流程引擎和设计器并很少,一般是一些专业公司开发的“工做流引擎”,这些“工做流引擎”一般较为笨重、配置复杂,开发起来并不方便。更为关键的是,因为Java语言的特色,要在功能上作到灵活而强大就已十分困难,一般必须依赖反射、动态字节码等技术,而一旦使用反射或动态字节点码技术,就会使用程序执行的速度变慢、效率低下,代码结构变得复杂、难以调试。相信使用过这些“工做流引擎”的小伙伴们都会有一样的感觉。java

注:工做流引擎和流程引擎在功能上较为类似,但从严格意义上讲区别仍是很大,主要在于工做流引擎包含:任务的指派、信息的流转、公文的审阅等交互功能。而流程引擎则专一于业务逻辑的图形化展现、编辑与执行。express

今天给你们介绍一款使用Java开发的轻量级流程引擎mac-flow,优秀的设计理念加上对循环和递归的巧妙运用,使得 mac-flow 可以在彻底不使用反射、动态字节码等技术的状况下,实现了灵活和强大功能,可以轻松胜任多种类型的应用场景。天然在性能方面,mac-flow的表现十分优异,几乎可以与硬编码方式的代码相媲美。多线程

接下来咱们经过一个示例,来展现mac-flow(2.0.1)是如何工做的。咱们虚构了一个业务流程,并把它画出来,那么它在 流程设计器(在线演示) 上看起一是这样的:并发

用户在登陆成功后,后台程序并行去查询用户的现金、股票和基金帐户,计算并返回其总资产。app

硬编码实现大概是这样的:dom

public Long start(User u) throws InterruptedException {
		String rid = RandomUtil.randomUUID();
		// 打印日志
		log.info("Req{}: Start login", rid);
		// 登记流水
		this.logRequest(rid, u);
		// 登陆
		if (this.login(u)) {
			// 检查帐户资产
			long total = this.check(rid, u.getId());
			log.info("Req{}: My total asset = {}", rid, total);
			// 更新流水
			this.updateLog(rid, u);
			return total;
		}
		log.info("Req{}: Invalid login {}", rid, u.getName());
		// 更新流水
		this.updateLog(rid, u);
		return -1L;
	}

	public void updateLog(String rid, User u) {
		log.info("Update loign log {} to DB", rid);
	}

	public void logRequest(String rid, User u) {
		log.info("Log loign request {} to DB", rid);
	}

	public long check(final String rid, final String userId) {
		log.info("Req{}: Check accounts of user {}", rid, userId);
		//
		log.info("Query cash account of {}", rid, userId);
		Future<Long> cashFt = threadPool.submit(new Callable<Long>() {
			@Override
			public Long call() throws Exception {
				long cash = Math.round(Math.random() * 10000);
				log.info("Req{}: User {} cash account = {}", rid, userId, cash);
				return cash;
			}
		});
		log.info("Query stock account of {}", rid, userId);
		Future<Long> stockFt = threadPool.submit(new Callable<Long>() {
			@Override
			public Long call() throws Exception {
				long stock = Math.round(Math.random() * 10000);
				log.info("Req{}: User {} stock account = {}", rid, userId, stock);
				return stock;
			}
		});
		log.info("Query fund account of {}", rid, userId);
		Future<Long> fundFt = threadPool.submit(new Callable<Long>() {
			@Override
			public Long call() throws Exception {
				long fund = Math.round(Math.random() * 10000);
				log.info("Req{}: User {} fund account = {}", rid, userId, fund);
				return fund;
			}
		});
		//
		long total = 0L;
		try {
			total += cashFt.get(1000L, TimeUnit.MILLISECONDS);
			total += stockFt.get(1000L, TimeUnit.MILLISECONDS);
			total += fundFt.get(1000L, TimeUnit.MILLISECONDS);
		} catch (InterruptedException | ExecutionException | TimeoutException e) {
			log.error("Error on sum total money", e);
		}
		log.info("User {} total asset = {}", userId, total);
		return total;
	}

	public boolean login(User u) throws InterruptedException {
		Thread.sleep(10L);
		log.info("User {} login with encode {}", u.getName(), encode);
		if ("Mac_J".equals(u.getName()) && "123".equals(u.getPassword())) {
			u.setId("00000000000000000000000000000000");
			return true;
		}
		return false;
	}

咱们使用流程引擎来实现,那么只需在流程设计器右侧的面板作一些简单配置,再点击右上角的菜单“生成...”-“XML配置”,生成出来的流程配置是这样的:异步

注:为了演示流程的嵌套和引用,咱们画了两个流程来实现,一个登陆流程,一个是帐户查询流程。分布式

登陆流程 login.xmlide

<bean id="login-login" parent="processNode">
		<property name="handler">
			<bean class="com.boarsoft.flow.demo.login.LoginHandlerImpl">
				<property name="encode" value="MD5" />
			</bean>
		</property>
		<property name="next" value="check" />
	</bean>

	<bean id="loginService" parent="simpleFlowService">
		<property name="flowId" value="login"/>
	</bean>

	<bean id="login" parent="simpleFlow" >
		<property name="flowId" value="login" />
		<property name="mutex" value="false" />
		<property name="entry" value="login" />
	</bean>

	<bean id="login-check" parent="judgeNode">
		<property name="expression" value="id != null" />
		<property name="yes" value="accountQry" />
		<property name="no" value="end" />
	</bean>

	<bean id="login-accountQry" parent="subflowNode">
		<property name="wrapper">
			<bean class="com.boarsoft.flow.demo.query.UserIdWrapHandlerImpl">
			</bean>
		</property>
		<property name="ref" value="accountQry" />
		<property name="next" value="end" />
	</bean>

帐户查询与汇总流程(子流程) acctQry.xml性能

<bean id="accountQryService" parent="simpleFlowService">
		<property name="flowId" value="accountQry"/>
	</bean>

	<bean id="accountQry" parent="simpleFlow" >
		<property name="flowId" value="accountQry" />
		<property name="mutex" value="false" />
		<property name="entry" value="fork" />
	</bean>

	<bean id="accountQry-fork" parent="forkNode">
		<property name="join" value="join" />
		<property name="branches">
			<set>
				<value>cash</value>
				<value>stock</value>
				<value>fund</value>
			</set>
		</property>
	</bean>

	<bean id="accountQry-cash" parent="processNode">
		<property name="handler">
			<bean class="com.boarsoft.flow.demo.query.CashHandlerImpl">
			</bean>
		</property>
		<property name="next" value="join" />
	</bean>

	<bean id="accountQry-stock" parent="processNode">
		<property name="handler">
			<bean class="com.boarsoft.flow.demo.query.StockHandlerImpl">
			</bean>
		</property>
		<property name="next" value="join" />
	</bean>

	<bean id="accountQry-fund" parent="processNode">
		<property name="handler">
			<bean class="com.boarsoft.flow.demo.query.FundHandlerImpl">
			</bean>
		</property>
		<property name="next" value="join" />
	</bean>

	<bean id="accountQry-join" parent="joinNode">
		<property name="handler">
			<bean class="com.boarsoft.flow.demo.query.SumJoinHandlerImpl">
			</bean>
		</property>
		<property name="next" value="end" />
	</bean>

能够看出,mac-flow并无自行定义XML标签,而是直接使用Spring的bean标签库,懂Spring的就能轻松上手,学起来很是容易。

用户登陆 LoginHandlerImpl.java

@Override
	public Object process(String entry, Object data, Throwable e) throws InterruptedException {
		Thread.sleep(10L);
		User u = (User) data;
		log.info("User {} login with encode {}", u.getName(), encode);
		if ("Mac_J".equals(u.getName()) && "123".equals(u.getPassword())) {
			u.setId("00000000000000000000000000000000");
		}
		return u;
	}

现金帐户查询 CashHandlerImpl.java  股票和基金帐户查询代码相似

@Override
	public Object process(String entry, Object data, Throwable e) throws InterruptedException {
		String userId = (String) data;
		long cash = Math.round(Math.random() * 10000);
		log.info("User {} cash account = {}", userId, cash);
		return cash;
	}

用于合计用户资产的 SumJoinHandlerImpl.java

@Override
	public Object join(Object flowData, Map<String, Object> resultMap) {
		String userId = (String) flowData;
		Long total = 0L;
		for (String k : resultMap.keySet()) {
			Long v = (Long) resultMap.get(k);
			total += v;
		}
		log.info("User {} total asset = {}", userId, total);
		return total;
	}

要注意的是,上面硬编码的代码并无实现诸如:超时检测、流程事件、调用链采样、日志采样、服务接口装配等等功能接口。而这些个接口能力,mac-flow都提供了。

如今,咱们分别用两种方式来测试上面的代码,并将日志级别调至ERROR,不开启mac-flow的“调用链跟踪”和“服务治理”功能,执行结果以下:

一、首次执行(首次装配bean比较耗时)

Flow engine take 124ms
Hard code take 25ms

二、方式一:300线程并发,每线程依次执行10000次(同步)

Flow engine take 103447ms
Hard code take 100897ms

二、方式二:在主线程使用600固定大小线程池发起300*10000次调用(异步)
Flow engine take 87651ms
Hard code take 82155ms

综上所述,即便用最为简单的硬编码实现,不考虑诸如:超时检测、流程事件、调用链采样、日志采样、服务接口装配等等能力的状况下,执行300万条调用时,硬编码方式也仅仅比mac-flow快3~5秒!性能表现至关优异。

额外再补充一下:mac-flow主要提供fork/join方式来实现并行(异步),你也能够自行使用多线程等形式来实现更多异步。不过在分布式场景下,强烈推荐结合 mac-rpc 提供的丰富的异步功能,可以实现更为强大的异步功能。相关文章请戳这里:dubbo vs mac-rpc 性能评测之异步调

有兴趣的小伙伴能够访问 http://www.boarsoft.com 了解更多

相关文章
相关标签/搜索