流程编排漫谈
流程编排漫谈
讲述流程编排的概念,分类,以及传统的流程引擎代表flowable
流程编排的使用场景
使用到流程编排的场景,大体都是复杂多变的,而且需要动态的执行,如果使用硬编码来开发,代码需要经常修改,而且如果代码耦合严重,代码的可维护性也非常差,
那么代码修改以后,出错的概率大大增加,常见的场景如下
- oa,企业办公场景
- 电商算促销
- etl清洗
- 规则引擎
- 微服务编排
- 其余复杂的流程场景,适合使用责任链的设计模式的场景
流程编排定义
设计好的流程,在流程引擎的驱动下,按照设计,一个个组件的执行动态执行下去,可以通过上下文共享数据
- 流程定义,定义一个流程如何执行
- 流程组件,流程执行的一个个节点
- 流程上下文,组件之间共享数据的机制
流程编排和服务编排
现在的流程编排一般分为传统的流程编排,比较学院派,以BPMN为主,典型的框架就是activity/Camunda/flowable,还有的就是一些比较冷门,或者没那么出名的框架,弱点的甚至是流线型的
流程定义,强大的就是DAG的流程定义,DAG系的相对功能更强大
这面这张图展示了bpmn的三兄弟,有篇文章对三兄弟做了相对比较全面的对比三兄弟
总结下来就是目前flowable更易用,功能比较强大,但是Camunda也很有前途
随便云的兴起和云原生的兴起,服务编排的概念兴起,所以服务编排,就是编排微服务,微服务编排的常见的有三种方式
- Orchestration(编制),就是有个流程引擎,然后控制各个服务,中心化管理,动态管理
- Choreography(编排),去中心化管理,用消息连接,节点之间对等,相对动态管理
- api网关,用网关来聚合各种服务,类似编制,但是切入点不一样,具体看实现,也可以硬编码,也可以使用流程的动态管理
流程编排与AOP
切面编程的思维,在如今非常常见,在spring中,随便一个方法可能有好多个切面,各种proxy,但是切面目前针对的都是方法级别,有方法,才能有切面,但是流程编排,可以随意的
前后添加组件,比如本来某个流程增加了一个步骤,那么就可以增加一个组件,然后插入其中,等于说,可以是无限扩展,所有,从某种程度来说,流程编排是切面的升级版,
流程编排通过上下文,让程序拥有了无限扩展的能力,而且随便可以方便的配置可视化的界面,可视化监控业务
初识BPMN
BPMN,业务流程模型和标记法(BPMN, Business Process Model and Notation),维基百科的描述就很不错,这里摘要一下
一些流程图的示例
bpmn要素
BPMN用很小一套图形要素做简单的图来建模,这将令业务用户与开发者一样容易理解其中的过程和流.它的四种基本要素如下:
- 流对象(Flow Object)
事件(Events),活动(Activities),网关(Gateways) - 连接对象(Connecting Objects)
顺序流(Sequence Flow),消息流(Message Flow),关联(Association) - 泳道(Swimlanes)
池(Pool),道(Lane) - 器物(Artifacts/Artefacts)
数据对象(Data Object),组(Group),注释(Annotation)
流对象与连接对象
这些对象都是实际需要执行的对象,有真实的业务含义和代码执行
流对象(Flow Objects)是BPMN的主要描述对象,由三种核心要素(事件、活动、关口)组成.
事件(Event)
“事件”(Event)以圆环表示,指发生的事情(区分于“活动”代表所做的事情).圆环中的图标代表事件的类型(例如:信封为消息,时钟为时间).事件也被分为“捕获”(Catching,例如捕获输入的消息而开始一个流程)或“抛出”(Throwing,例如在流程结束抛出消息).
- 开始事件(Start event)
作为流程的触发器;以细单线标明,并且只能“捕获”(Catch),所以它显示为空心(轮廓)的图标. - 结束事件(End event)
表现流程的结果;以粗单线标明,且只能“抛出”(Throw),所以显示为实心图标. - 中间事件(Intermediate event)
表现发生在开始和结束事件之间的事;以双线标明,可以是“抛出”或“捕获”(相应采用实心或空心图标).例如,一任务流到一事件,抛出一个消息到另一个池,然后由下一个事件守候,捕获其回应.
活动(Activity)
“活动”(Activity)用圆角矩形表示,并描述必需做的工作的种类.
- 任务(Task)
任务代表单一工作单元,它不会或不能被分解为更深层次上的业务流程细节,而不包含操作程序步骤的图示(此非BPMN的目的). - 子流程(Sub-process)
用于隐藏或显露深层业务流程细节——收起时,在矩形底部用加号标明子流程;展开时,在矩形内显示全部的流对象、连接对象及器物.
子流程自含开始及结束事件,来自“父”流程的顺序流不可跨过其边框. - 事务(Transaction)
子流程的一种形式,其所包含的全部活动必须作为一个整体对待,即它们必须完全结束以满足目标,其中任何一个失败就必须全部偿还(撤回).事务作为扩展的子流程,用双线环绕.
关口(Gateway)
“关口”(Gateway)用菱形表示,基于所示条件决定路径的分流与合并.
“流对象”通过“连接对象”(Connecting objects)互相连接.连接对象包括三个类型(顺序、消息、关联):
- 顺序流(Sequence Flow)
“顺序流”用实心线和箭头表示,显示活动进行的顺序.“顺序流”还可以在开始端带有符号,以小菱形标明其中一些发自活动的“条件流”(conditional flow),同时以对角斜线标明发自活动或决定,带条件流的“默认流”(default flow). - 消息流(Message Flow)
“消息流”用虚线表示,起始端有一个空心圆圈,终端是一个空心箭头.它告诉我们哪些消息流跨过组织的边界(即介于池之间).消息流不可用于在同一个池中连接活动或事件. - 关联(Association)
“关联”(Association)用点线表示.它用于创建器物或文本到流对象的联系,并且可以用空心箭头标明某种方向性(指向器物表示结果,源自器物表示输入,同时出现则表示读和更新).当器物或文本联系到顺序或消息流时,关联无需标明方向(那些流已经显示了方向).
泳道与器物
这些对象不可执行,可用于业务流程的可读性和分析,整体类似代码的注释
流程引擎框架在实现的时候,这些可读性的组件,都是选择性的实现,未必都有,比如flowable,可视化就没有实现全
泳道(Swimlanes)
从视觉上对活动加以组织或分类的机制.它基于交叉功能流程图基础,在BPMN中有两种类型:
- 池(Pool)
表示流程中的主要参与者,典型地,用来分开不同的组织.一个池可容纳一个或多个道(像真实的泳池一样).当池为展开的(显示出内部细节),绘做大的矩形;若为收起的(隐藏起内部细节),绘做沿着图的长或宽伸展的空的矩形. - 道(Lane)
在池中,用于活动按职能译注3或角色归类.绘做按池的长或宽展开的矩形.道包含流对象、连接对象和器物.
器物(Artifacts)
开发者可以带给模型/图更多的信息,通过这一方式,使模型/图更可读.预定的三种器物如下:
- 数据对象(Data Objects)
“数据对象”向读者显示在活动中需要或产生哪些数据. - 组(Group)
组表现为虚线的圆角矩形.组用来将不同的活动分组,但不影响图中的流. - 注释(Annotation)
注释为模型/图的读者增加可理解性.
flowable简介
flowable功能比较强大,而且国内关注的人比较多,用的人也相对比较多,出了错,还可以搜到答案,所以,这里以flowable为例,介绍bpmn的典型框架,但是Flowable从6.4.1版本开始大力发展其商业版产品,
开源版本也不在及时维护.部分功能已经不再开源版发布,比如表单生成器(表单引擎)、历史数据同步至其他数据源、es等等.
概览
一个简单的流程示例
对应的bpmn20.xml,bpmndi:BPMNDiagram
是原始的图形信息
1 |
|
api简介
- 执行流程
1
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("callActivity");
- 部署流程
1
2
3
4DeploymentBuilder 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()); - 流程回退
1
2Task task = taskService.createTaskQuery().taskAssignee("signUser").singleResult();
runtimeService.createChangeActivityStateBuilder().processInstanceId(task.getProcessInstanceId()).moveActivityIdTo(task.getTaskDefinitionKey(),"task1").changeState(); - 流程重跑
1
2
3
4List<Job> list = managementService.createDeadLetterJobQuery().processInstanceId(hiProcinstId).list();
for (Job job : list) {
managementService.moveDeadLetterJobToExecutableJob(job.getId(), 1);
}
概括
flowable的翻译文档有flowable中文文档,
这个不是最新版的,官网的最新文档是flowable官网文档
- 用户任务,可以认为是留白,流程跑到用户任务会阻塞,然后什么也不做,自己调用api完成以后,才能执行以后的流程
- 服务任务需要继承接口
org.flowable.engine.delegate.JavaDelegate
,和spring集成的时候,可以是spring的bean,需要这样设置1
2serviceTask.setImplementationType(BpmnXMLConstants.ATTRIBUTE_TASK_SERVICE_DELEGATEEXPRESSION); //ATTRIBUTE_TASK_SERVICE_DELEGATEEXPRESSION是"delegateExpression"
serviceTask.setImplementation("${" + nodeCode + '}'); //nodeCode就是bean 的 name - 服务任务抛出BpmnError的时候,才能使用重跑的功能,所以,一定要有个父类,去捕获异常,重新抛出成BpmnError
- 流程默认有spring的事务,如果不想要使用事务,需要jdbc的方法上添加
1
- 任务最好设置成异步,否则失败了以后,流程没有记录
- 使用的时候,有执行监听器,类似于切面,分为前置后置等等,用户任务和服务任务的监听器不能共用,需要额外设置,可以参考 监听器
非典型性流程编排
分布式方向
现在都是微服务时代了,集群已经是一种常见的现象,所以分布式服务编排也是一种常见的需求 阿里的maat
为什么要做Maat
我们在项目的开发过程中,经常遇到一些流程化调度的需求,如上线发布流程、分析任务流程等。对于这些流程化的调度任务,我们尝试过自己开发了一套流程调度系统,也尝试过接入集团的工作流,但难免会遇到一些问题:
- 业务代码和调度代码耦合严重,修改流程基本需要入侵到代码级别
- 对于定时触发的任务流程,没有一个统一管控的系统
- 多分支的复杂流程不能很好支持
- 缺少可视化的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方向
低代码方向
低代码平台中,需要一个流程引擎来动态的执行代码
- 低代码平台中的oa需要动态的设置流程,其中就需要流程引擎
- 低代码平台,组件化开发也需要一个流程引擎来驱动代码
- 服务编排,微服务的服务编排,针对微服务的接口调用场景,提供专用的节点,和一些特定的功能,来满足微服务的编排,比如提供参数化节点来对应api
总结
流程编排,是一个很好的实践,现在的世界,信息技术是基石,软件的复杂度非常高,流程编排是一个很好的方向,来面对复杂多变且易出错的需求,而且几乎所有的场景都适合流程编排