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 瞭解更多

相關文章
相關標籤/搜索