流程编排漫谈

讲述流程编排的概念,分类,以及传统的流程引擎代表flowable

流程编排的使用场景

使用到流程编排的场景,大体都是复杂多变的,而且需要动态的执行,如果使用硬编码来开发,代码需要经常修改,而且如果代码耦合严重,代码的可维护性也非常差,
那么代码修改以后,出错的概率大大增加,常见的场景如下

  1. oa,企业办公场景
  2. 电商算促销
  3. etl清洗
  4. 规则引擎
  5. 微服务编排
  6. 其余复杂的流程场景,适合使用责任链的设计模式的场景

流程编排定义

设计好的流程,在流程引擎的驱动下,按照设计,一个个组件的执行动态执行下去,可以通过上下文共享数据

  1. 流程定义,定义一个流程如何执行
  2. 流程组件,流程执行的一个个节点
  3. 流程上下文,组件之间共享数据的机制

流程编排和服务编排

现在的流程编排一般分为传统的流程编排,比较学院派,以BPMN为主,典型的框架就是activity/Camunda/flowable,还有的就是一些比较冷门,或者没那么出名的框架,弱点的甚至是流线型的
流程定义,强大的就是DAG的流程定义,DAG系的相对功能更强大

这面这张图展示了bpmn的三兄弟,有篇文章对三兄弟做了相对比较全面的对比三兄弟

总结下来就是目前flowable更易用,功能比较强大,但是Camunda也很有前途

随便云的兴起和云原生的兴起,服务编排的概念兴起,所以服务编排,就是编排微服务,微服务编排的常见的有三种方式

  1. Orchestration(编制),就是有个流程引擎,然后控制各个服务,中心化管理,动态管理
  2. Choreography(编排),去中心化管理,用消息连接,节点之间对等,相对动态管理
  3. api网关,用网关来聚合各种服务,类似编制,但是切入点不一样,具体看实现,也可以硬编码,也可以使用流程的动态管理

流程编排与AOP

切面编程的思维,在如今非常常见,在spring中,随便一个方法可能有好多个切面,各种proxy,但是切面目前针对的都是方法级别,有方法,才能有切面,但是流程编排,可以随意的
前后添加组件,比如本来某个流程增加了一个步骤,那么就可以增加一个组件,然后插入其中,等于说,可以是无限扩展,所有,从某种程度来说,流程编排是切面的升级版,
流程编排通过上下文,让程序拥有了无限扩展的能力,而且随便可以方便的配置可视化的界面,可视化监控业务

初识BPMN

BPMN,业务流程模型和标记法(BPMN, Business Process Model and Notation),维基百科的描述就很不错,这里摘要一下

一些流程图的示例

bpmn要素

BPMN用很小一套图形要素做简单的图来建模,这将令业务用户与开发者一样容易理解其中的过程和流.它的四种基本要素如下:

  1. 流对象(Flow Object)
    事件(Events),活动(Activities),网关(Gateways)
  2. 连接对象(Connecting Objects)
    顺序流(Sequence Flow),消息流(Message Flow),关联(Association)
  3. 泳道(Swimlanes)
    池(Pool),道(Lane)
  4. 器物(Artifacts/Artefacts)
    数据对象(Data Object),组(Group),注释(Annotation)

流对象与连接对象

这些对象都是实际需要执行的对象,有真实的业务含义和代码执行

流对象(Flow Objects)是BPMN的主要描述对象,由三种核心要素(事件、活动、关口)组成.

事件(Event)

“事件”(Event)以圆环表示,指发生的事情(区分于“活动”代表所做的事情).圆环中的图标代表事件的类型(例如:信封为消息,时钟为时间).事件也被分为“捕获”(Catching,例如捕获输入的消息而开始一个流程)或“抛出”(Throwing,例如在流程结束抛出消息).

  1. 开始事件(Start event)
    作为流程的触发器;以细单线标明,并且只能“捕获”(Catch),所以它显示为空心(轮廓)的图标.
  2. 结束事件(End event)
    表现流程的结果;以粗单线标明,且只能“抛出”(Throw),所以显示为实心图标.
  3. 中间事件(Intermediate event)
    表现发生在开始和结束事件之间的事;以双线标明,可以是“抛出”或“捕获”(相应采用实心或空心图标).例如,一任务流到一事件,抛出一个消息到另一个池,然后由下一个事件守候,捕获其回应.

活动(Activity)

“活动”(Activity)用圆角矩形表示,并描述必需做的工作的种类.

  1. 任务(Task)
    任务代表单一工作单元,它不会或不能被分解为更深层次上的业务流程细节,而不包含操作程序步骤的图示(此非BPMN的目的).
  2. 子流程(Sub-process)
    用于隐藏或显露深层业务流程细节——收起时,在矩形底部用加号标明子流程;展开时,在矩形内显示全部的流对象、连接对象及器物.
    子流程自含开始及结束事件,来自“父”流程的顺序流不可跨过其边框.
  3. 事务(Transaction)
    子流程的一种形式,其所包含的全部活动必须作为一个整体对待,即它们必须完全结束以满足目标,其中任何一个失败就必须全部偿还(撤回).事务作为扩展的子流程,用双线环绕.

关口(Gateway)

“关口”(Gateway)用菱形表示,基于所示条件决定路径的分流与合并.
“流对象”通过“连接对象”(Connecting objects)互相连接.连接对象包括三个类型(顺序、消息、关联):

  1. 顺序流(Sequence Flow)
    “顺序流”用实心线和箭头表示,显示活动进行的顺序.“顺序流”还可以在开始端带有符号,以小菱形标明其中一些发自活动的“条件流”(conditional flow),同时以对角斜线标明发自活动或决定,带条件流的“默认流”(default flow).
  2. 消息流(Message Flow)
    “消息流”用虚线表示,起始端有一个空心圆圈,终端是一个空心箭头.它告诉我们哪些消息流跨过组织的边界(即介于池之间).消息流不可用于在同一个池中连接活动或事件.
  3. 关联(Association)
    “关联”(Association)用点线表示.它用于创建器物或文本到流对象的联系,并且可以用空心箭头标明某种方向性(指向器物表示结果,源自器物表示输入,同时出现则表示读和更新).当器物或文本联系到顺序或消息流时,关联无需标明方向(那些流已经显示了方向).

泳道与器物

这些对象不可执行,可用于业务流程的可读性和分析,整体类似代码的注释
流程引擎框架在实现的时候,这些可读性的组件,都是选择性的实现,未必都有,比如flowable,可视化就没有实现全

泳道(Swimlanes)

从视觉上对活动加以组织或分类的机制.它基于交叉功能流程图基础,在BPMN中有两种类型:

  1. 池(Pool)
    表示流程中的主要参与者,典型地,用来分开不同的组织.一个池可容纳一个或多个道(像真实的泳池一样).当池为展开的(显示出内部细节),绘做大的矩形;若为收起的(隐藏起内部细节),绘做沿着图的长或宽伸展的空的矩形.
  2. 道(Lane)
    在池中,用于活动按职能译注3或角色归类.绘做按池的长或宽展开的矩形.道包含流对象、连接对象和器物.

器物(Artifacts)

开发者可以带给模型/图更多的信息,通过这一方式,使模型/图更可读.预定的三种器物如下:

  1. 数据对象(Data Objects)
    “数据对象”向读者显示在活动中需要或产生哪些数据.
  2. 组(Group)
    组表现为虚线的圆角矩形.组用来将不同的活动分组,但不影响图中的流.
  3. 注释(Annotation)
    注释为模型/图的读者增加可理解性.

flowable简介

flowable功能比较强大,而且国内关注的人比较多,用的人也相对比较多,出了错,还可以搜到答案,所以,这里以flowable为例,介绍bpmn的典型框架,但是Flowable从6.4.1版本开始大力发展其商业版产品,
开源版本也不在及时维护.部分功能已经不再开源版发布,比如表单生成器(表单引擎)、历史数据同步至其他数据源、es等等.

概览

一个简单的流程示例

对应的bpmn20.xml,bpmndi:BPMNDiagram是原始的图形信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:flowable="http://flowable.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.flowable.org/processdef" exporter="Flowable Open Source Modeler" exporterVersion="6.7.2">
<process id="pure1Service" name="pure1Service" isExecutable="true">
<startEvent id="startEvent1" name="开始" flowable:formFieldValidation="true"></startEvent>
<serviceTask id="sid-6F3577E0-2694-4732-A470-58E4074D0514" name="服务1" flowable:async="true" flowable:class="com.vzoom.flowable.flowableservice.CommonService">
</serviceTask>
<serviceTask id="sid-954B5B92-6338-4DBB-BA2F-A66816972875" name="服务2" flowable:async="true" flowable:class="com.vzoom.flowable.flowableservice.CommonService"></serviceTask>
<serviceTask id="sid-3A06C8B5-0ED9-4945-A5F5-9E3E498474F4" name="服务3" flowable:async="true" flowable:class="com.vzoom.flowable.flowableservice.CommonService"></serviceTask>
<endEvent id="sid-E70A0CBA-2FF8-4F27-A3D1-97BD51F1E526" name="结束"></endEvent>
<sequenceFlow id="sid-00C32123-9AA2-4420-AEBD-D21C2AE62870" name="线1" sourceRef="startEvent1" targetRef="sid-6F3577E0-2694-4732-A470-58E4074D0514"></sequenceFlow>
<sequenceFlow id="sid-65068C9A-EC24-499C-A287-CD019FF1C15D" name="线2" sourceRef="sid-6F3577E0-2694-4732-A470-58E4074D0514" targetRef="sid-954B5B92-6338-4DBB-BA2F-A66816972875"></sequenceFlow>
<sequenceFlow id="sid-582D7F57-14AE-40ED-A1C1-B6832959D4CA" name="线3" sourceRef="sid-954B5B92-6338-4DBB-BA2F-A66816972875" targetRef="sid-3A06C8B5-0ED9-4945-A5F5-9E3E498474F4"></sequenceFlow>
<userTask id="sid-40FA984E-4697-4A9B-B5D1-39F30B5EE97B" name="用户任务1" flowable:formFieldValidation="true"></userTask>
<sequenceFlow id="sid-B338251C-C581-4916-BA5D-F7BC94563232" name="线4" sourceRef="sid-3A06C8B5-0ED9-4945-A5F5-9E3E498474F4" targetRef="sid-40FA984E-4697-4A9B-B5D1-39F30B5EE97B"></sequenceFlow>
<sequenceFlow id="sid-8E49C236-A4AE-481A-B4E7-5F430477C0B4" name="线5" sourceRef="sid-40FA984E-4697-4A9B-B5D1-39F30B5EE97B" targetRef="sid-E70A0CBA-2FF8-4F27-A3D1-97BD51F1E526"></sequenceFlow>
</process>
<bpmndi:BPMNDiagram id="BPMNDiagram_pure1Service">
<bpmndi:BPMNPlane bpmnElement="pure1Service" id="BPMNPlane_pure1Service">
<bpmndi:BPMNShape bpmnElement="startEvent1" id="BPMNShape_startEvent1">
<omgdc:Bounds height="30.0" width="30.0" x="100.0" y="163.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="sid-6F3577E0-2694-4732-A470-58E4074D0514" id="BPMNShape_sid-6F3577E0-2694-4732-A470-58E4074D0514">
<omgdc:Bounds height="80.0" width="100.0" x="240.0" y="138.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="sid-954B5B92-6338-4DBB-BA2F-A66816972875" id="BPMNShape_sid-954B5B92-6338-4DBB-BA2F-A66816972875">
<omgdc:Bounds height="80.0" width="100.0" x="405.0" y="138.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="sid-3A06C8B5-0ED9-4945-A5F5-9E3E498474F4" id="BPMNShape_sid-3A06C8B5-0ED9-4945-A5F5-9E3E498474F4">
<omgdc:Bounds height="80.0" width="100.0" x="555.0" y="138.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="sid-E70A0CBA-2FF8-4F27-A3D1-97BD51F1E526" id="BPMNShape_sid-E70A0CBA-2FF8-4F27-A3D1-97BD51F1E526">
<omgdc:Bounds height="28.0" width="28.0" x="990.0" y="164.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="sid-40FA984E-4697-4A9B-B5D1-39F30B5EE97B" id="BPMNShape_sid-40FA984E-4697-4A9B-B5D1-39F30B5EE97B">
<omgdc:Bounds height="80.0" width="100.0" x="735.0" y="138.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge bpmnElement="sid-00C32123-9AA2-4420-AEBD-D21C2AE62870" id="BPMNEdge_sid-00C32123-9AA2-4420-AEBD-D21C2AE62870" flowable:sourceDockerX="15.0" flowable:sourceDockerY="15.0" flowable:targetDockerX="50.0" flowable:targetDockerY="40.0">
<omgdi:waypoint x="129.94999940317362" y="178.0"></omgdi:waypoint>
<omgdi:waypoint x="239.99999999993753" y="178.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sid-8E49C236-A4AE-481A-B4E7-5F430477C0B4" id="BPMNEdge_sid-8E49C236-A4AE-481A-B4E7-5F430477C0B4" flowable:sourceDockerX="50.0" flowable:sourceDockerY="40.0" flowable:targetDockerX="14.0" flowable:targetDockerY="14.0">
<omgdi:waypoint x="834.9499999999925" y="178.0"></omgdi:waypoint>
<omgdi:waypoint x="990.0" y="178.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sid-B338251C-C581-4916-BA5D-F7BC94563232" id="BPMNEdge_sid-B338251C-C581-4916-BA5D-F7BC94563232" flowable:sourceDockerX="50.0" flowable:sourceDockerY="40.0" flowable:targetDockerX="50.0" flowable:targetDockerY="40.0">
<omgdi:waypoint x="654.9499999999431" y="178.0"></omgdi:waypoint>
<omgdi:waypoint x="734.9999999998699" y="178.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sid-582D7F57-14AE-40ED-A1C1-B6832959D4CA" id="BPMNEdge_sid-582D7F57-14AE-40ED-A1C1-B6832959D4CA" flowable:sourceDockerX="50.0" flowable:sourceDockerY="40.0" flowable:targetDockerX="50.0" flowable:targetDockerY="40.0">
<omgdi:waypoint x="504.9499999999581" y="178.0"></omgdi:waypoint>
<omgdi:waypoint x="554.9999999999363" y="178.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sid-65068C9A-EC24-499C-A287-CD019FF1C15D" id="BPMNEdge_sid-65068C9A-EC24-499C-A287-CD019FF1C15D" flowable:sourceDockerX="50.0" flowable:sourceDockerY="40.0" flowable:targetDockerX="50.0" flowable:targetDockerY="40.0">
<omgdi:waypoint x="339.9499999998897" y="178.0"></omgdi:waypoint>
<omgdi:waypoint x="404.99999999998465" y="178.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>

api简介

  1. 执行流程
    1
    ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("callActivity");
  2. 部署流程
    1
    2
    3
    4
    DeploymentBuilder deployment = repositoryService.createDeployment();
    Deployment deploy = deployment.name("asyncErrorService1").addString(UUID.randomUUID() + ".bpmn20.xml", "xmlContext").deploy(); //必须以.bpmn20.xml结尾
    List<ProcessDefinition> list = repositoryService.createProcessDefinitionQuery().deploymentId(deploy.getId()).list();
    BpmnModel bpmnModel = repositoryService.getBpmnModel(list.get(0).getId());
  3. 流程回退
    1
    2
    Task task = taskService.createTaskQuery().taskAssignee("signUser").singleResult();
    runtimeService.createChangeActivityStateBuilder().processInstanceId(task.getProcessInstanceId()).moveActivityIdTo(task.getTaskDefinitionKey(),"task1").changeState();
  4. 流程重跑
    1
    2
    3
    4
    List<Job> list = managementService.createDeadLetterJobQuery().processInstanceId(hiProcinstId).list();
    for (Job job : list) {
    managementService.moveDeadLetterJobToExecutableJob(job.getId(), 1);
    }

概括

flowable的翻译文档有flowable中文文档,
这个不是最新版的,官网的最新文档是flowable官网文档

  1. 用户任务,可以认为是留白,流程跑到用户任务会阻塞,然后什么也不做,自己调用api完成以后,才能执行以后的流程
  2. 服务任务需要继承接口org.flowable.engine.delegate.JavaDelegate,和spring集成的时候,可以是spring的bean,需要这样设置
    1
    2
    serviceTask.setImplementationType(BpmnXMLConstants.ATTRIBUTE_TASK_SERVICE_DELEGATEEXPRESSION); //ATTRIBUTE_TASK_SERVICE_DELEGATEEXPRESSION是"delegateExpression"
    serviceTask.setImplementation("${" + nodeCode + '}'); //nodeCode就是bean 的 name
  3. 服务任务抛出BpmnError的时候,才能使用重跑的功能,所以,一定要有个父类,去捕获异常,重新抛出成BpmnError
  4. 流程默认有spring的事务,如果不想要使用事务,需要jdbc的方法上添加
    1
    @Transactional(propagation = Propagation.NOT_SUPPORTED)
  5. 任务最好设置成异步,否则失败了以后,流程没有记录
  6. 使用的时候,有执行监听器,类似于切面,分为前置后置等等,用户任务和服务任务的监听器不能共用,需要额外设置,可以参考 监听器

非典型性流程编排

分布式方向

现在都是微服务时代了,集群已经是一种常见的现象,所以分布式服务编排也是一种常见的需求 阿里的maat

为什么要做Maat

我们在项目的开发过程中,经常遇到一些流程化调度的需求,如上线发布流程、分析任务流程等。对于这些流程化的调度任务,我们尝试过自己开发了一套流程调度系统,也尝试过接入集团的工作流,但难免会遇到一些问题:

  1. 业务代码和调度代码耦合严重,修改流程基本需要入侵到代码级别
  2. 对于定时触发的任务流程,没有一个统一管控的系统
  3. 多分支的复杂流程不能很好支持
  4. 缺少可视化的UI,不能很好追踪流程进度

核心模块

Maat核心模块完成了任务调度的整个流程。核心模块的每个节点都独立运行在机器上,启动上互相不依赖,所有消息通信通过DB(数据库)和MQ(消息队列)完成。

Web Api Service

Web Api Service提供了丰富的与外部交互的Api,包括任务增删改、历史任务状态、任务状态修改、任务的触发、任务的重试等接口。
另外原生airflow提供的web展示功能也是由该角色完成。

Scheduler

scheduler是maat关键角色,它决定了所有开启运行的流程何时可以触发一次运行,也决定一次任务运行中,哪些节点可以被执行。被判定执行的节点会被scheduler通过mq发送给worker执行。

随着任务的增多,单一的scheduler负载过高导致调度周期增长,为了减轻scheduler的压力,maat将scheduler按照队列拆分。不同队列的任务有独立的scheduler负责调度,将任务分发给对应队列的worker执行。

目前每个队列仅有一个scheduler负责调度,之后会改造为每个队列多scheduler的实现方式。

Worker

worker为具体执行任务的角色,worker会接受scheduler发出的任务,在worker上执行节点中描述的具体任务。worker角色有多个,任务会在任意一个对等的worker上机器,当worker资源不足时,可以动态扩容。

由于不同队列任务所需的基础环境不同,如Python、Java、Hadoop、zk等,不同队列的worker角色启动参数有配置上的差异,不同队列的worker启动时会按照配置中描述的资源完成部署安装。

worker上任务完成后会回写db,scheduler察觉到当前任务状态变化后会继续任务的调度。

Distributers

任务分发层负责将scheduler需要调度的任务发送到指定的Worker上。

MQ:原生Airflow使用MQ完成scheduler到worker的消息传递,底层由celery+Rabbitmq实现。scheduler将需要运行的任务发送到MQ中,发送到MQ中包含任务对应的队列信息。worker从MQ获取消息时,仅获取相应队列任务,拉取到对应worker上执行。MQ在maat中以rabbitmq实现,MQ和其他角色一样,也是独立部署的。

FaaS:FaaS(Function as a Service)是基于搜索生态实现的ServerLess框架,Maat将其作为执行器。Maat的所有任务都抽象成function,任务执行时则调用相应的function,完成后返回任务状态。目前已完成与FaaS的初步对接,期望未来能基于FaaS做更多优化,如:多样化的任务执行方式,可以将轻量级的任务函数化,将重量级的任务服务化;任务资源动态调整,甚至某些任务可以执行时分配资源,完成后即释放。

总结

分布式场景中,Scheduler负责调度,worker负责执行,然后Distributers负责将scheduler需要调度的任务发送到指定的Worker上,然后提供web api来做任务的管理

DAG方向

bpmn比较全面,比较重,对于etl场景来说,就过于复杂了,对于etl场景,需要的就是一个简单的流程引擎,airflow,就很适合做etl,而且使用DAG作为定义,
airflow的文档 DAG方向

低代码方向

低代码平台中,需要一个流程引擎来动态的执行代码

  1. 低代码平台中的oa需要动态的设置流程,其中就需要流程引擎
  2. 低代码平台,组件化开发也需要一个流程引擎来驱动代码
  3. 服务编排,微服务的服务编排,针对微服务的接口调用场景,提供专用的节点,和一些特定的功能,来满足微服务的编排,比如提供参数化节点来对应api

总结

流程编排,是一个很好的实践,现在的世界,信息技术是基石,软件的复杂度非常高,流程编排是一个很好的方向,来面对复杂多变且易出错的需求,而且几乎所有的场景都适合流程编排