
LiteFlow简介
LiteFlow是什么?
LiteFlow是一款专注于逻辑驱动流程编排的轻量级框架,它以组件化方式快速构建和执行业务流程,有效解耦复杂业务逻辑。通过支持热加载规则配置,开发者能够即时调整流程步骤,将复杂的业务如价格计算、下单流程等拆分为独立且可复用的组件,从而实现系统的高度灵活性与扩展性,避免了牵一发而动全身的问题。旨在优化开发流程,减少冗余工作,让团队能够更聚焦于核心业务逻辑,而将流程控制层面的重任托付给该框架进行自动化处理。
LiteFlow整合了流程编排与规则引擎的核心特性,提供XML、JSON或YAML格式的灵活流程定义,以及本地文件系统、数据库、ZooKeeper、Nacos、Apollo、Redis等多种规则文件存储方案。其内建插件如liteflow-rule-nacos,以及开放的扩展机制,赋予开发人员自定义规则解析器的能力,满足多样化场景下的规则管理需求。
对于基于角色任务流转的场景,LiteFlow并非最佳选择,推荐使用Flowable或Activiti等专门的工作流引擎。
LiteFlow的架构
LiteFlow是从获取上下文开始的,这个上下文通常包含了执行流程所需的数据和环境信息。通过解析这些上下文数据,LiteFlow能够理解并执行对应的规则文件,驱动业务流程的执行。在LiteFlow中,业务流程被组织成一系列的链路(或节点),每个链路代表一个业务步骤或决策点。这些链路上的节点,也就是业务组件,是独立的,可以支持多种脚本语言,如Groovy、JavaScript、Python、Lua等,以便根据具体业务需求进行定制。下图为LiteFlow的整体架构图。

LiteFlow的作用
- LiteFlow将瀑布式代码进行组件化、灵活的编排体系,组件可独立调整替换,规则引擎语法简单易学。
利用LiteFlow可以把传统的瀑布式代码重构为以组件为中心的概念体系,从而获得灵活的编排能力。在这种结构里,各个组件彼此分离,允许轻松调整和替换。组件本身可通过脚本定制,而且组件间的过渡完全受规则引导。此外,LiteFlow具备简单易懂的DSL规则引擎语法,能快速入门掌握。

- LiteFlow强大的编排能力
LiteFlow的编排语法强大到可以编排出任何你想要的逻辑流程。如下图复杂的语法,如果使用瀑布式的代码去写,那种开发以及维护难度可想而知,但是使用LiteFlow你可以轻松完成逻辑流程的编排,易于维护。

- LiteFlow支持组件热部署
通过LiteFlow,你可以实现组件的实时热替换,同时也能在已有的逻辑流程中随时插入新的组件,以此动态调整你的业务逻辑。

LiteFlow的环境支持
- JDK
LiteFlow要求的最低的JDK版本为8,支持JDK8~JDK17所有的版本。当然如果使用JDK11以上,确保LiteFlow的版本为v2.10.6及其以上版本。
如果你使用JDK11及其以上的版本,请确保jvm参数加上以下参数:--add-opens java.base/sun.reflect.annotation=ALL-UNNAMED
- Spring
LiteFlow要求的Spring的最低版本为Spring 5.0。支持的范围是Spring 5.X ~ Spring 6.X。
- SpringBoot
LiteFlow要求的Springboot的最低的版本是2.0。支持的范围是Springboot 2.X ~ Springboot 3.X。
LiteFlow的性能
LiteFlow框架在启动时完成大部分工作,包括解析规则、注册组件和组装元信息,执行链路时对系统资源消耗极低。在设计之初就注重性能表现,对核心代码进行了优化。
实际测试中,LiteFlow表现出色,50多个业务组件组成的链路在压测中单点达到1500 TPS,成功应对双11、明星顶流带货等大规模流量挑战。
尽管LiteFlow框架自身性能卓越,但实际执行效率取决于业务组件的性能。若组件包含大量循环数据库查询、不良SQL 或大量RPC同步调用,整体TPS也会较低。但这归咎于业务组件的性能问题,而非LiteFlow框架本身的性能问题。整体系统吞吐量的高低不只依赖于某个框架,而是需要整体优化业务代码才能提升。
数据来源于LiteFlow官方文档说明。
LiteFlow使用
以下我们结合SpringBoot环境使用。
LiteFlow在使用上可以按照引入依赖,LiteFlow相关配置,规则文件,定义组件,节点编排,执行流程进行。
引入依赖
- <dependency>
- <groupId>com.yomahub</groupId>
- <artifactId>liteflow-spring-boot-starter</artifactId>
- <version>2.11.1</version>
- </dependency>
复制代码目前liteflow的稳定版本已经更新到2.11.4.2。本文依托于2.11.1做讲解演示。好多新的功能均在2.9.0以后的版本中才有。
配置项
LiteFlow有诸多配置项,大多数配置项有默认值,可以不必配置,同时官方也建议某个配置项不了解它有什么用时,就不要去随意的改它的值。- liteflow:
- #规则文件路径
- rule-source: config/flow.el.xml
- #-----------------以下非必须-----------------
- #liteflow是否开启,默认为true
- enable: true
- #liteflow的banner打印是否开启,默认为true
- print-banner: true
- #zkNode的节点,只有使用zk作为配置源的时候才起作用,默认为/lite-flow/flow
- zk-node: /lite-flow/flow
- #上下文的最大数量槽,默认值为1024
- slot-size: 1024
- #FlowExecutor的execute2Future的线程数,默认为64
- main-executor-works: 64
- #FlowExecutor的execute2Future的自定义线程池Builder,LiteFlow提供了默认的Builder
- main-executor-class: com.yomahub.liteflow.thread.LiteFlowDefaultMainExecutorBuilder
- #自定义请求ID的生成类,LiteFlow提供了默认的生成类
- request-id-generator-class: com.yomahub.liteflow.flow.id.DefaultRequestIdGenerator
- #并行节点的线程池Builder,LiteFlow提供了默认的Builder
- thread-executor-class: com.yomahub.liteflow.thread.LiteFlowDefaultWhenExecutorBuilder
- #异步线程最长的等待时间(只用于when),默认值为15000
- when-max-wait-time: 15000
- #异步线程最长的等待时间(只用于when),默认值为MILLISECONDS,毫秒
- when-max-wait-time-unit: MILLISECONDS
- #when节点全局异步线程池最大线程数,默认为16
- when-max-workers: 16
- #并行循环子项线程池最大线程数,默认为16
- parallelLoop-max-workers: 16
- #并行循环子项线程池等待队列数,默认为512
- parallelLoop-queue-limit: 512
- #并行循环子项的线程池Builder,LiteFlow提供了默认的Builder
- parallelLoop-executor-class: com.yomahub.liteflow.thread.LiteFlowDefaultParallelLoopExecutorBuilder
- #when节点全局异步线程池等待队列数,默认为512
- when-queue-limit: 512
- #是否在启动的时候就解析规则,默认为true
- parse-on-start: true
- #全局重试次数,默认为0
- retry-count: 0
- #是否支持不同类型的加载方式混用,默认为false
- support-multiple-type: false
- #全局默认节点执行器
- node-executor-class: com.yomahub.liteflow.flow.executor.DefaultNodeExecutor
- #是否打印执行中过程中的日志,默认为true
- print-execution-log: true
- #是否开启本地文件监听,默认为false
- enable-monitor-file: false
- #是否开启快速解析模式,默认为false
- fast-load: false
- #简易监控配置选项
- monitor:
- #监控是否开启,默认不开启
- enable-log: false
- #监控队列存储大小,默认值为200
- queue-limit: 200
- #监控一开始延迟多少执行,默认值为300000毫秒,也就是5分钟
- delay: 300000
- #监控日志打印每过多少时间执行一次,默认值为300000毫秒,也就是5分钟
- period: 300000
复制代码只要使用规则,则必须配置rule-source配置,但是如果你是用代码动态构建规则,则rule-source自动失效。
规则文件
从上面LiteFlow的整体架构图中可以看出LiteFlow支持多种规则文件源配置:本地文件,数据库,zk,Nacos,Apollo,Etcd,Redis以及自定义配置源。本文将会以本地规则文件为例讲解,其余配置源将在后续文章中讲解实时修改流程中在进行分享,
LiteFlow支持3种规则文件格式:XML,JSON,YML,3种文件的配置相差无几。LiteFlow的组成很轻量,主要由Node以及Chain元素构成。值得一提的是:如果在非Spring环境下,Node节点是必须的,配置配置,否则会导致报错找不到节点。当然在Spring环境下,我们可以不必配置Node节点,只需要将相应的节点注册到Spring上下文即可。- <?xml version="1.0" encoding="UTF-8"?>
- <flow>
- <chain name="chain1">
- THEN(a, b, c);
- </chain>
-
- <chain name="scChain">
- SWITCH(s1).to(s2, THEN(a,b).id("d"));
- </chain>
- </flow>
复制代码 组件
在介绍具体的组件之前,我们先来了解下@LiteflowComponent注解。- @Target({ ElementType.TYPE })
- @Retention(RetentionPolicy.RUNTIME)
- @Documented
- @Inherited
- @Component
- public @interface LiteflowComponent {
- @AliasFor(annotation = Component.class, attribute = "value")
- String value() default "";
- @AliasFor(annotation = Component.class, attribute = "value")
- String id() default "";
- /**
- * 可以给节点起别名
- **/
- String name() default "";
-
- }
复制代码 @LiteflowComponent继承自@Component注解,在Spring环境中,可以将组件注入到容器中。它的value或者id即对应规则文件中的node的id。例如上述规则文件中的a,b,c等。
普通组件:NodeComponent
普通组件节点需要继承NodeComponent,需要实现process方法。可用于THEN和WHEN编排中。- @LiteflowComponent("a")
- public class AComponent extends NodeComponent {
-
- @Override
- public void process() throws Exception {
- System.out.println("执行A规则");
- }
- }
复制代码 当然NodeComponent中还有一些其他方法可以重写,以达到自己的业务需求。例如:
- isAccess():表示是否进入该节点,可以用于业务参数的预先判断。
- isContinueOnError():表示出错是否继续往下执行下一个组件,默认为false
- isEnd():是否结束整个流程(不往下继续执行)。
如果返回true,则表示在这个组件执行完之后立马终止整个流程。此时由于是用户主动结束的流程,属于正常结束,所以流程结果中(LiteflowResponse)的isSuccess是true。
- beforeProcess()和afterProcess():流程的前置和后置处理器,其中前置处理器,在isAccess 之后执行。
- onSuccess()和onError():流程的成功失败事件回调
- rollback():流程失败后的回滚方法。
在任意组件节点的内部,还可以使用this关键字调用对应的方法:
- 获取流程初始入参参数
我们在组件节点内部可以通过this.getRequestData()去获取流程初始的入参。例如:
- @LiteflowComponent("a")
- public class AComponent extends NodeComponent {
-
- @Override
- public void process() throws Exception {
- DataRequest dataRequest = this.getRequestData();
- System.out.println("执行A规则");
- }
- }
复制代码
- 获取上下文
在组件节点里,随时可以通过方法this.getContextBean(clazz)获取当前你自己定义的上下文,从而可以获取到上下文的数据。例如:
- @LiteflowComponent("a")
- public class AComponent extends NodeComponent {
-
- @Override
- public void process() throws Exception {
- ConditionContext context = this.getContextBean(ConditionContext.class);
- System.out.println("执行A规则");
- }
- }
复制代码
- setIsEnd
是否立即结束整个流程 ,用法为this.setIsEnd(true)。
还有一些其他的方法,可以参考源码。
选择组件:NodeSwitchComponent
实际业务中,我们针对不同的业务类型,有不同的业务处理逻辑,例如上一篇文章中的订单类型一样,此时就需要节点动态的判断去执行哪些节点或者链路,所以就出现了选择组件。
选择组件需要实现NodeSwitchComponent,并且需要实现processSwitch()方法。用于SWITCH编排中。
processSwitch()方法返回值是一个String,即下一步流程执行的节点ID或者链路tag。- @LiteflowComponent("s)
- public class SwitchComponent extends NodeSwitchComponent {
-
- @Override
- public String processSwitch() throws Exception {
- System.out.println("执行switch规则");
- return "a";
- }
- }
复制代码 规则文件中,配置的SWITCH编排信息为:- <chain name="scChain">
- SWITCH(s).to(a, b, c);
- </chain>
复制代码 此时s节点就会返回要执行的节点id为a,即要执行a流程。通常switch的节点的逻辑我们需要具体结合业务类型,例如订单类型枚举去使用。
除了可以返回id以外,我们还可以返回tag(标签)。例如我们在规则文件中这么写:
在规则表达式中我们可以这样使用:- <chain name="scChain">
- SWITCH(s).to(a.tag("td"), b.tag("td"), c.tag("td));
- </chain>
复制代码 然后在SWITCH中返回tag:- @LiteflowComponent("s)
- public class SwitchComponent extends NodeSwitchComponent {
-
- @Override
- public String processSwitch() throws Exception {
- System.out.println("执行switch规则");
- return ":td" // 进入 b 节点,含义:选择第一个标签为td的节点
- return "tag:td" // 进入 b 节点,含义:选择第一个标签为td的节点
- return "a"; // 进入 b 节点,含义:选择targetId是b的节点
- return "b:"; // 进入 b 节点,含义:选择第一个targetId是b的节点
- return "b:td"; // 进入 b 节点,含义:选择targetId是b且标签是td的节点
- return ":"; // 进入 b 节点,含义:选择第一个节点
- return "d"; // 进入 d 节点,含义:选择targetId是d的节点
- return "d:"; // 进入 d 节点,含义:选择第一个targetId是d的节点
- return "d:td"; // 进入 d 节点,含义:选择targetId是d且标签是td的节点
- return "b:x"; // 报错,原因:没有targetId是b且标签是x的节点
- return "x"; // 报错,原因:没有targetId是x的节点
- return "::"; // 报错,原因:没有找到标签是":"的节点
- }
- }
复制代码 NodeSwitchComponent继承至NodeComponent,其节点的内部可以覆盖的方法和this关键字NodeComponent。
条件组件:NodeForComponent
条件组件,也是IF组件,返回值是一个boolean。需要继承NodeForComponent,实现processIf()方法。可用于IF...ELIF...ELSE编排。例如:- <chain name = "ifChain">
- IF(x, a, b);
- </chain>
复制代码 该例中x就是一个条件组件,如果x返回true,则会执行a节点,否则执行b节点。- @LiteflowComponent("x")
- public class IfXComponent extends NodeIfComponent {
-
- @Override
- public boolean processIf() throws Exception {
- System.out.println("执行X节点");
- return false;
- }
- }
复制代码 NodeIfComponent继承至NodeComponent,其节点内部可以覆盖的方法和this关键字NodeComponent。
次数循环组件:NodeForComponent
次数循环组件。返回的是一个int值的循环次数。继承NodeForComponent,实现processFor()方法, 主要用于FOR...DO...表达式。在紧接着DO编排中的节点中,可以通过this.getLoopIndex()获取下标信息,可以从对应数组或者集合中通过下表获取对应的元素信息。- <chain name = "forChain">
- FOR(f).DO(a);
- </chain>
复制代码- @LiteflowComponent("f")
- public class ForComponent extends NodeForComponent {
-
- @Override
- public int processFor() throws Exception {
- DataContext dataContext = this.getContextBean(DataContext.class);
- List<String> dataList = dataContext.getDataList();
- return dataList.size();
- }
- }
- @LiteflowComponent("a")
- public class AComponent extends NodeComponent {
-
- @Override
- public void process() throws Exception {
- Integer loopIndex = this.getLoopIndex();
- DataContext dataContext = this.getContextBean(DataContext.class);
- List<String> dataList = dataContext.getDataList();
- String str = dataList.get(loopIndex);
- System.out.println("执行A规则:"+str);
- }
- }
复制代码 其中f组件相当于定义一个数组或者集合的元素个数,类似
[code]for(int i=0;i processIterator() throws Exception { DataContext dataContext = this.getContextBean(DataContext.class); return Optional.ofNullable(dataContext.getDataList()) .orElse(Lists.newArrayList()).iterator(); } }@LiteflowComponent("a") public class AComponent extends NodeComponent { @Override public void process() throws Exception { String str = this.getCurrLoopObj(); System.out.println("执行A规则:"+str); } }... contextBeanClazzArray)//第一个参数为流程ID,第二个参数为流程入参,后面可以传入多个上下文的Beanpublic LiteflowResponse execute2Resp(String chainId, Object param, Object... contextBeanArray)// 第一个参数为流程ID,第二个参数为流程入参,第三个参数是用户的RequestId,后面可以传入多个上下文的Beanpublic LiteflowResponse execute2RespWithRid(String chainId, Object param, String requestId, Class... contextBeanClazzArray) |