你們常常用流程圖來分析和表述業務邏輯。在流程圖上,業務流程一目瞭然,很是清晰,是需求分析、代碼設計的利器。可是優秀且開源的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 瞭解更多