热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Flowable引擎技术调研

主要概念BPMBusinessProcessManagement,业务流程管理BPMNBusinessProcessModelingNotation,BPMN是一个广泛接受与支持的


主要概念

BPM

Business Process Management,业务流程管理

BPMN

Business Process Modeling Notation,BPMN是一个广泛接受与支持的,展现流程的注记方法:OMG BPMN标准,BPMN2.0正式版本于2011年1月3日发布,常见的工作流引擎如:Activiti、Flowable、jBPM 都基于 BPMN 2.0 标准。

事件

事件(event)通常用于为流程生命周期中发生的事情建模。事件总是图形化为圆圈。例如:

顺序流

顺序流(sequence flow)是流程中两个元素间的连接器。在流程执行过程中,一个元素被访问后,会沿着其所有出口顺序流继续执行。用从源元素指向目标元素的箭头表示。例如:

网关

网关(gateway)用于控制执行的流向(或者按BPMN 2.0的用词:执行的 “标志(token)” )。网关用其中带有图标的菱形表示。例如:

活动

活动(Activity)用于表示需要执行的行为,任务用带有图标的圆角矩形表示。例如:

CMMN

Case Management Model and Notation,CMMN具有与BPMN不同的基本范例,CMMN没有顺序的流程,它以某种状态对案例建模。

DMN

Decision Model and Notation,DMN的目的是提供一个模型决策结构,从而使组织的策略可以用图形清晰的地描绘出来,这个标准可以用于实现规则引擎。


概览

Flowable是由Activiti的原核心开发人员,基于Activiti5开发的。

Flowable提供了多层次的解决方案


Web App

通过直接搭建Web应用,可直接获得产品化的解决方案。

Flowable IDM: 身份管理应用。为所有Flowable UI应用提供单点登录认证功能,并且为拥有IDM管理员权限的用户提供了管理用户、组与权限的功能。

Flowable Modeler: 让具有建模权限的用户可以创建流程模型、表单、选择表与应用定义。

Flowable Task: 运行时任务应用。提供了启动流程实例、编辑任务表单、完成任务,以及查询流程实例与任务的功能。

Flowable Admin: 管理应用。让具有管理员权限的用户可以查询BPMN、DMN、Form及Content引擎,并提供了许多选项用于修改流程实例、任务、作业等。


REST API

通过搭建REST服务,上层系统可通过调用HTTP API,使用引擎能力。

Flowable BPMN User Guide - REST API


Java API

Flowable提供了一套完整的API,供使用方调用,所有的服务都是无状态的,意味着依赖这套API的服务天然可在分布式环境进行部署。通过直接依赖jar包进行集成,并在各层都留出了接口,使用时可自行灵活扩展。结合使用场景,建议使用Java API进行开发,本文也将重点介绍基于该方式集成的调研结果。


类名说明
ProcessEngine引擎API的总入口
RepositoryService部署(deployments) 与 流程定义(process definitions)
RuntimeService启动新流程实例、读取与存储流程变量(process variables) 、查询流程实例与执行(Execution)
TaskService查询任务、分配执行用户(assignee) 、认领任务(claim) 、完成任务(complete)
HistoryService提供查询历史数据的能力
IdentityService用于管理组与用户,可选(引擎运行时并不做用户校验)
FormService表单相关服务,可选(表单不一定要嵌入流程定义)
ManagementService用于读取数据库表与表原始数据的信息,也提供了对作业(job)的查询与管理操作
DynamicBpmnService可用于动态修改流程定义中的部分内容,而不需要重新部署

典型场景

以如下“请假流程”为例,演示Flowable API使用方式,为减少干扰,此处特意没有与Spring集成,以演示最原生的API调用。

⬇️ 符合BPMN 2.0标准的流程图

⬇️ holiday-request.bpmn20.xml 文件内容,及上图所对应的数据文件


部署流程定义


代码逻辑


数据变更

⬇️ ACT_RE_DEPLOYMENT (Deployment) 对应生成了一条记录,表示本次部署。

⬇️ ACT_GE_BYTEARRAY (EngineResource) 生成了两条记录,对应两个资源文件,BYTES_字段保存了文件的原始内容。

⬇️ ACT_RE_PROCDEF (ProcessDefinition) 生成了三条记录,对应到XML中定义的三个流程。


启动流程实例


代码逻辑


数据变更

运行时相关表(ACT_RU_*):只保留当前运行时数据

⬇️ ACT_RU_ACTINST(ActivityInstance) :“活动”数据,包含了流程经过的所有节点(包括连线在内,可以理解为流程经过的全部XML Element)。

⬇️ ACT_RU_EXECUTION(Execution) :“执行”数据,标示了当前的分支路径,一般是遇到网关或并行流程时,会分裂为多个执行,与分裂前的Execution会存在父子关系,只会保留当前正在进行中的Execution。


Q:为什么初始有两个Execution?

A:Flowable 默认会创建一个最父级Execution对象,用来表示“流程实例(ProcessInstance)”,其他表中的PROC_INST_ID,关联的记录实际也是这条ROOT级别的Execution对象。

Q:多条并行的分支路径时,Execution会如何生成?

A:会拆分为多个Execution,并行网关前的其中一个Execution(最后触发分支的这个)会继续延展到网关后的第一个分支,其他的分支会创建出新的Execution。


⬇️ ACT_RU_TASK(Task) :“任务”数据,即用户的代办事项,只会保留当前未完成的任务。

⬇️ ACT_RU_VARIABLE(VariableInstance) :“变量”数据,当前流程实例所设置的变量。


也可以通过setVariableLocal方法将变量绑定在Execution/Task上,作用域相应降低,Local的变量会在流程离开当前作用域时被删除。

Q:如果流程运行到某个表达式,但表达式依赖的变量没有设置会怎么样?

A:会直接抛异常,同时会回滚本次事务。


⬇️ ACT_RU_IDENTITYLINK(IdentityLink) :“身份关联”数据,标志了用户和组在本次流程中的角色。

历史记录相关表(ACT_HI_*),保留全部历史

⬇️ ACT_HI_ACTINST(HistoricActivityInstance) ,与ACT_RU_ACTINST对应。

⬇️ ACT_HI_PROCINST(HistoricProcessInstance) ,流程实例历史。


Q:为什么这个表没有对应的ACT_RU_*表?

A:这个表的数据实际对应的就是ACT_RU_EXECUTION表中,PARENT_ID为NULL数据,可以理解为一次流程Execution的最父级,即为一个ProcessInstance对象。


⬇️ ACT_HI_TASKINST(HistoricTaskInstance) ,与ACT_RU_TASK对应,区别是任务结束后数据仍会保留。

⬇️ ACT_HI_VARINST(HistoricVariableInstance) ,与ACT_RU_VARIABLE对应。

⬇️ ACT_HI_IDENTITYLINK(HistoricIdentityLink) ,与ACT_RU_IDENTITYLINK对应。

当前位置


完成任务


代码逻辑


数据变更

运行时相关表(ACT_RU_*):只保留当前运行时数据

⬇️ ACT_RU_ACTINST(ActivityInstance)

⬇️ ACT_RU_EXECUTION(Execution)

⬇️ ACT_RU_TASK(Task)

⬇️ ACT_RU_VARIABLE(VariableInstance)

⬇️ ACT_RU_IDENTITYLINK(IdentityLink)

历史记录相关表(ACT_HI_*),保留全部历史

⬇️ ACT_HI_ACTINST(HistoricActivityInstance)

⬇️ ACT_HI_PROCINST(HistoricProcessInstance)

无变更

⬇️ ACT_HI_TASKINST(HistoricTaskInstance)

⬇️ ACT_HI_VARINST(HistoricVariableInstance)

⬇️ ACT_HI_IDENTITYLINK(HistoricIdentityLink)

当前位置


结束流程


代码逻辑


数据流转

运行时相关表(ACT_RU_*):只保留当前运行时数据


  • 本次流程实例相关的数据均被清空

历史记录相关表(ACT_HI_*),保留全部历史

⬇️ ACT_HI_ACTINST(HistoricActivityInstance)

⬇️ ACT_HI_PROCINST(HistoricProcessInstance)

⬇️ ACT_HI_TASKINST(HistoricTaskInstance)

⬇️ ACT_HI_VARINST(HistoricVariableInstance)


  • 无变化

⬇️ ACT_HI_IDENTITYLINK(HistoricIdentityLink)


  • 无变化

源码分析

调用层次


  • Flowable将所有执行逻辑抽象为“命令”(Command),并通过CommandExecutor对Command对执行调用,CommandExecutor通过职责链模式,对Command包装了多层拦截器,默认拦截器的功能包含了:构建本次命令执行的上下文CommandContext、限定事务边界、打印日志等。
  • 通过依赖关系,CommandContext -> (engineConfigurations, sessionFactories),Command中的逻辑可以间接获取到下层的Manager对象以及DBSession对象,进行相应逻辑的实现。

流转逻辑


  • Flowable将流程源文件的内容,统一解析为Process对象,将各种节点抽象为FlowElment,并缓存在进程中,通过在Process对象上进行查找,来确认流程的当前节点的类型,以及后续节点的类型,这套逻辑都在内存中完成,理论上会比较高效(见左图)。
  • Flowable实现了一套类似深度优先的机制,将涉及到流程流转的逻辑,统一抽象为“操作”AbstractOperation,CommandInvoker通过对操作队列的顺序执行,完成流程节点的流转,直至当前的各个Execution都达到“等待状态”(见右图)。

仍然以以下流程为例,本次换成对请假申请进行拒绝。



具体流转步骤如下



编号当前操作节点流转数据变更
1TriggerExecutionOperationUserTask
2TakeOutgoingSequenceFlowsOperationUserTask -> SequenceFlowActivityInstance:UserTask 标记为结束Execution:变更当前节点为 SequenceFlow
3ContinueProcessOperationSequenceFlow -> ExcusiveGateway ->SequenceFlowActivityInstance:创建SequenceFlow并标记为结束Execution:变更当前节点为ExcusiveGatewayActivityInstance:创建ExcusiveGateway并标记为开始Execution:变更当前节点为SequenceFlow
4TakeOutgoingSequenceFlowsOperationSequenceFlowActivityInstance:标记ExcusiveGateway为结束
5ContinueProcessOperationSequenceFlow -> ServiceTaskActivityInstance:创建SequenceFlow并标记为结束Execution:变更当前节点为ServiceTaskActivityInstance:创建ServiceTask并标记为开始。
6TakeOutgoingSequenceFlowsOperationServiceTask -> SequenceFlowActivityInstance:标记ServiceTask记录为结束Execution:变更当前节点为SequenceFlow
7ContinueProcessOperationSequenceFlow -> EndEventActivityInstance:创建SequenceFlow并标记为结束Execution:变更当前节点为EndEventActivityInstance:创建EndEvent并标记为开始
8TakeOutgoingSequenceFlowsOperationEndEventActivityInstance:标记EndEvent记录为结束
9EndExecutionOperation删除本次运行时的相关数据

注:该例子流程比较简单,如果存在多个并行分支,Execution则会分裂出多个,通过这套机制,可以比较好的降低复杂性。


异步执行

Flowable 实现了一套异步执行器,用于的将一些逻辑切换为异步进行执行。

定时器(Timers)


保存在ACT_RU_TIMER_JOB表中,并带有给定的到期日期。异步执行器中有一个线程,周期性地检查是否有需要触发的定时器。当需要触发定时器时,从JOB表中移除该定时器,并创建一个异步作业(async job)。


异步延续 (Asynchronous Continuations)


Flowable 支持将某些任务节点配置为异步执行,只需要按以下方式配置节点属性,即可使任务的执行切换为异步执行。Flowable默认通过DB实现了一个类似消息队列的机制,引擎通过启动线程池来完成消息的消费,Flowable也留出了扩展性,可以替换为使用真实的消息队列实现异步。


flowable:class="my.custom.Delegate"
flowable:async="true" />

异步历史(Asynchronous History)


Flowable 支持将History数据的写入配置为异步,只需要配置isAsyncHistoryEnabled=true,即可将历史数据的写入切换为异步写入,缩短客户端的等待时间。附:flowable异步历史性能基准测试



事件机制


  • Flowable实现了一套标准的事件机制,并在支持对多种事件进行监听,当Listener执行失败时也支持事务的回滚(可选),详见:3.18. 事件处理器
  • 通过这套机制,可以在流程进行的同时,通过监听相应的时间,对业务系统进行额外操作。

变量作用域

引擎可在多个级别设置变量,作用域各不相同,设置方法如下:

RuntimeService
void setVariable(String executionId, String variableName, Object value);
void setVariableLocal(String executionId, String variableName, Object value);
TaskService
void setVariable(String taskId, String variableName, Object value);
void setVariableLocal(String taskId, String variableName, Object value);
VariableScope
void setTransientVariable(String variableName, Object variableValue);
void setTransientVariableLocal(String variableName, Object variableValue);
// setVariables,setVariableLocals 为对应函数的批量形式,以上省略

作用域如下


方法说明
RuntimeService.setVariable变量关联最顶级Execution,当前流程内的Execution/Task 均可以获取到这些变量。
RuntimeService.setVariableLocal变量关联指定的ExecutionId,只有对应的Execution(包括子执行)及所属的Task可以获取到这些变量。
TaskService.setVariable同 RuntimeService.setVariable。
TaskService.setVariableLocal变量关联指定的TaskId,只有对应的Task可以获取到这些变量。
VariableScope.setTransientVariable不持久化,仅存在于内存中,会在下一个“等待状态”消失,等待状态意味着流程实例会持久化至数据存储中。等待状态可以是用户任务/异步活动。
VariableScope.setTransientVariableLocal不持久化,变量作用域取决于是对哪个对想设置。

以下代码用于验证

代码输出结果

Execution 15004 variables:
var_local_execution_15004: 1
var_local_execution_15001: 1
var_execution_15004: 1
var_execution_15001: 1
var_task_15008: 1
Execution 15001 variables:
var_local_execution_15001: 1
var_execution_15004: 1
var_execution_15001: 1
var_task_15008: 1
Task 15008 variables:
var_local_execution_15004: 1
var_local_execution_15001: 1
var_execution_15004: 1
var_local_task_15008: 1
var_execution_15001: 1
var_task_15008: 1

持久化数据


扩展性


  • Flowable 在各个层次均留出了接口,用于扩展,通过使用ProcessEngineConfiguration进行配置,可以轻松替换各种实现,如:


    • 可通过替换 DeploymentDataManager 接口的实现,将底层存储替换为其他任意存储介质。
    • 可通过替换 IdGenerator 接口的实现,将ID生成器替换为其他类型。
  • Flowable 各模块解耦比较充分,引擎核心并不依赖一些上层应用,比如身份相关模块、表单相关模块,可以只使用引擎做状态流转,但用户身份等仍然使用公司的主数据。


性能

缓存机制

通过对源码的分析,Flowable中的缓存大致分为两类:


  1. 对流程定义(ProcessDefinition)这类数据的缓存,因为变更较少且访问频繁,将数据解析后常驻缓存在了进程中,且因每次部署时都是重新插入新的数据,所以不会存在有一致性的问题。
  2. 对于各数据实体的缓存,Flowable 设计了生命周期为一次命令的缓存,这类缓存能有效降低一次调用中相同数据对DB的多次查询,并随着CommandContext的销毁而销毁。

同时,Flowable留出了接口,可以比较轻松的进行扩展,将缓存替换成Redis这类集中式缓存。

长流程

因为流程只能单向流动,Flowable的机制保证了,即使流程中节点很多,也只需要加载当前节点附近的节点,并不会因为处在一个长流程中,而使每一步的耗时线性增加。

吞吐量

Flowable没有一些NIO、Reative相关的实现,所以吞吐量取决于一次请求中的IO耗时于线程数配置的。


多租户

原生支持

Flowable原生支持多租户,如“典型场景”中的使用方式,只需要在调用API时传入tenantId,即支持在某个租户下进行操作,体现在数据上,便是各实体均有TENANT_ID字段用于标志租户。

数据隔离

Flowable默认将所有租户都放在相同的表中,用TENANT_ID区分,进行数据隔离。但由于Flowable的高度可定制,可以通过重载配置类,将数据分配到不同database,进行保存。官方也提供了对应的配置类实现:MultiSchemaMultiTenantProcessEngineConfiguration。

数据迁移

当某个租户需要从集群迁移出来,进行独立集群部署/私有化部署时,可通过数据库表的TENANT_ID导出对应数据,进行相应部署。这部分目前没有发现官方有现成的迁移工具,但理论上复杂性并不高,主要需要关注如何平滑迁移集群流量这类常见问题。

官方同样提供了changeDeploymentTenantId这种一键替换某个租户的TENANT_ID的方法,这个在数据量大的情况下可能会导致事务很大,个人建议这类操作还是自行控制更合适。


相关竞品

Activiti

目前的最新版 7.1.x,从2016年与Flowable分家之后,主要方向在云化;而Flowable主要专注于升级引擎的核心,Activiti 与 Flowable 个人更推荐后者。



Camunda

相关的资料比较少,是三个中最早支持CMMN、DMN的引擎,功能上的对比可以参考本文中的对比表格:工作流选型专项,Camunda or flowable or?(未完全求证),Camunda是否比Flowable更合适还有待调研。



作者:孔咯
链接:https://juejin.cn/post/7002503394707374111
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。



推荐阅读
author-avatar
呼吸的雨儿作_741
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有