你们常常用流程图来分析和表述业务逻辑。在流程图上,业务流程一目了然,很是清晰,是需求分析、代码设计的利器。可是优秀且开源的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 了解更多