查看原文
其他

B站流程引擎设计与实践

潘俊杰&刘昊 哔哩哔哩技术 2023-05-04

本期作者



潘俊杰

哔哩哔哩资深开发工程师


刘昊

哔哩哔哩基础架构部

SRE体系负责人



背景


随着企业的发展,软件开发的技术、架构以及理念也在快速更新迭代,如何搭建一套兼容当前IT架构且能适应未来变化的通用业务流程引擎是提升企业运营效率的关键。

Comet流程引擎自 2019 年启动研发并发布第一个版本,至今已陆续发布多个正式版本。该系统以其足够的灵活性、通用性和易用性,支撑了B站业务中大量需要借助流程引擎来实现逻辑流转和自动化的场景,跨度从底层基础设施、基础架构到上层各类业务,如各类资源申请、应用发布、推送审核、电子签约、版权评级等等。

在此背景下,本文以Comet为例,围绕流程引擎的作用、使用场景、技术实现和业务价值进行详细介绍。


概述


本文在企业级信息系统架构下设计了一套通用流程平台技术,通过先进的工作流(Workflow)技术,按照集中化、标准化、集成化的原则,建设了一个面向日常管理与事务处理的通用流程平台。一方面,从企业级信息系统架构的角度,建立一个与应用无关的通用流程模版,利用这种可配置的组件化模型来整合或改造现有应用的审批集成模式。通过集中或分布的方式建立一套或多套系统平台,降低流程系统的复杂度,使流程应用在能够快速开发部署的同时,简化管理维护工作、降低后期技术支持成本,另一方面可以实现各类管理与业务流程的统一管理和集中监控。

作为B站面向运维及业务的统一流程引擎,Comet支持移动端H5及Web访问,具备加签、转签、并签、或签等多种审批能力。通过开放式的表单和插件最大程度地提供了页面渲染和外部系统集成的灵活性并利用图算法来检测配置流程的正确性,帮助用户减少错误配置并提升接入效率。


场景实践


项目的建设模式以致力于为用户提供更低拥有成本、更高响应效率的完整解决方案。

Comet流程引擎是以业务为中心,以流程为导向,它既具备传统 BPM 产品所必须的流程服务能力,可以满足各种规模应用系统的流程业务需求;同时具备流程服务化所需要的动态伸缩、多租户等特性,适应于大型集团公司的集中化、标准化的流程服务平台建设。Comet流程引擎强调流程、人员和技术三大要素的有机结合,根据企业的具体情况制定人员的岗位职责、设计日常流程。以建立完备、关联的业务资源配置管理数据库为基础和切入点,实施事件管理、问题管理、变更管理、配置管理和发布管理等核心流程,实现配置管理数据库相关数据项与核心流程的关联和融合;使用规范化的流程管理办法将每一项规章制度在日常工作中进行模式化和固定化。

下面笔者通过几个具体的流程设计来窥探利用以上基础的能力实现的具体业务场景化需求的多样性和复杂度。


 SRE自动化类-缓存资源变更申请


在运维自动化的场景下,通过流程引擎的可视化流程模板、动态表单和插件执行功能,能够以很低的成本将周期性、重复性、规律性的日常工作流转化为自助化、自动化工单,例如应用系统维护的自动化、巡检过程自动化和故障处理自动化等等。基于在运维场景应用流程能力,极大的简化了运维、SRE同学将日常中琐事转化为业务自助化的成本,降低了人员琐事负载。

在具体的落地过程中,由于自动化运维依赖于各场景管控管理平台提供的管控能力来提升运维效率。像在缓存相关业务场景下,我们借助流程引擎实现相关资源大小的合理性评估以及缓存资源的自动化创建。该流程往往需要申请人填写对应的缓存资源的信息,如:内存大小、归属集群、申请应用。同时,根据变更的行为来控制流程的流转,保证了变更幅度比较大时的安全性。在与缓存管控平台的能力交互中,该业务流基于流程插件的回调功能来驱动缓存管控平台在适时创建缓存资源。
该业务流的具体流程图如下:



权限申请类-数据平台表权限申请


在数据平台的某些表权限申请的场景下,需要根据流程发起时的上下文信息作为入参来判断该流程中的节点是否激活,这里涉及了大量的环节判断和插件执行。另外在审批过程中,人员需要及时感知节点任务。基于Comet流程引擎的环节通知能力,减少了环节之间的审批时延,提高了流程审批的及时性。

在该场景下,一些低安全等级的数据往往可以通过上下文的方式减少审批链路,缩减流程整体的审批时长、提高审批效率。人员方面,Comet集成了OA和内部权限系统的角色,在环节配置了对应的角色后系统会根据发起人的不同去获取相应角色下的审批人员,减少了用户手动配置和维护的成本。
由于该场景需要使用到并签的功能,即对某些环节实现根据传递的人员组数不同来拆分多个节点,Comet将该环节的节点及上下游流转关系进行复制并增加自动节点作为收束节点(收束节点只有在上游所有审批节点都通过后才会激活)。这个功能补充了Comet在节点横向扩展的灵活性,满足了用户对多组审批人审批的场景需求。用户发起申请后,根据其所申请的表安全级别及部门信息来走不同的审批链。

基于流程条件的灵活性,业务方甚至可以将同一类流程抽象到一个流程来实现,减少了流程模版配置的成本并增加了流程审批场景的多样化。
该流程具体的流程图如下所示:



 业务审批类-推送任务审批


在执行推送任务的场景下,业务方通过其平台预先计算出某个流程的审批链并传递给流程引擎。在这个场景下,Comet通过动态线性流程的技术方案进行支持覆盖。该功能是在以流程模版配置功能之外的增强扩展,业务无需配置流程模版和流转关系,只需要按照顺序排列对应的审批人即可动态生成节点数量。

在实际实践中,业务方通过Push平台创建推送任务,Push平台开启审核工单。根据此次推送的上下文信息,实时计算出所需要的审批环节后,交由该权限组对应的审核组进行审核并记录流程状态变更消息,在审核流程完成后落入数据库。
动态线性流程减少了用户配置流程模版的成本、提高了发起流程的灵活度,满足了用户动态伸缩流程审批链的要求。同时,动态流程可以通过环节指定插件的方式将环节和流程的信息通知给平台方,平台方根据具体的环节和上下文做对应的处理。
除上述优势之外,动态线性流程在部分业务场景是有一定局限性。

由于动态线性流程在发起时,无法固定参数范围和数量,这会导致流程维护管理成本增高,后续流程实例的排障成本高

在复杂流程的审批场景下,动态配置流程需要业务自行传递大量流程图的相关参数,这块会导致业务理解成本增加,从而导致出错概率提升。因而,复杂的业务流程,仍然需要使用流程模版的方式配置。

下图是一个动态线性流程的例子:



设计实现


 工作流程


为了方便读者更好的理解Comet是如何工作的,笔者将简化后的系统流程程图绘制在下方。它主要由以下几个部分组成:

  • 用户定义流程:用户需要预先在Comet配置流程的流向、流转条件等基础模版元素。

  • 流程触达相关人员:参与人收到相关通知后决定执行具体的批准操作。

  • 三方系统异步回调结果:插件通知三方系统节点相关状态,由其执行内部操作并回调结果。



 整体架构



如上图所示,整体架构主要分为三个部分,它们分别是:

  • 管理层:负责模版、插件、请求的鉴权和图的合法性检测。

  • 引擎层:负责流程的核心功能如发起、流转、拒绝、关闭、加签和转签等。

  • 异步执行层:负责流程引擎产生的事件动作的处理。


 关键设计


流程模版


对于业务来说,流程模版即对其业务流程的具像化建模。通过配置和拖拽即可定义其流程节点的流向及流程元素的展示。流程模版和流程的关系比较类似程序设计语言中的类和对象的关系,如下图所示:



一个流程模版可以实例化多个流程,并且流程模版维护了自身的版本,每一个流程都会关联到具体的模版版本。流程模版支持节点编辑、节点抄送、流程抄送、延时通知、定时通知等多种功能,其可视化编辑页面如下图所示:



节点的逻辑属性


对于一个流程图来说,除了其开始节点和结束节点以外,其他节点的入度和出度都大于或等于1。我们可以先不考虑节点向下游流转的条件并默认为自动流转。在这种情形下,对于入度大于1的节点往往会持有两种逻辑属性,即且(and)和或(or)。下图说明了这两种条件的流转形式:



节点的激活不仅需要通过其持有的逻辑属性控制,往往还需要解析其边所持有的流转条件。


流转条件


将流转条件作为边的属性引入可以丰富流程的适配场景。它的使用需要依赖流程发起时以及过程中传递的上下文参数。譬如在一个出度为n的节点,我们希望只有当该节点的符合某个条件时才会激活某一分支的下游节点,如下图所示:



通过上下文参数n,我们可以控制被激活的节点。具体到某些实际场景来说,比如当申请某个权限时,通过判断上下文传递的数据安全级别可以控制到不同的节点,从而控制其审批流的差异度。

在Comet流程引擎中,我们采用的是一种MongoDB的条件表达式来做为流转条件的表达式。举个例子,当我们希望满足m>1或n>2任意一个条件就激活下游节点时,表达式可以是如下所示:



这里表达式由于是一个标准的Json格式,无需自实现或引入语法解析器。流程引擎只需递归解析流程上下文和该表达式所计算出的布尔值即可判断下级节点是否激活。


表单模版


Comet流程引擎除了支持两种模版类型:组合模版和HTML模版。


组合模版


通过在web页面提供表单画布和标准表单组件,用户可以通过拖拽的形式将标准表单组件放入到表单模版内。在单个表单的配置中,可以指定表单参数与流程上下文中的参数做绑定实现自动赋值和展示。

目前,我们支持了16个通用的表单组件,3个高级表单组件(与周边平台定制的通用组件),基本满足了日常业务流程设计中的所需要的表单能力。



HTML模版


在低代码模板之外,我们增加了HTML模板。该模板通过iframe嵌入的方式,将用户定义的前端代码在节点环节展示。业务可以通过自定义HTML、JS和CSS代码,并自行mock相应的上下文参数来进行完全灵活和高扩展的业务表单展示设计。

该功能的设计作为低代码模板功能的补充,用来覆盖特殊业务场景下的表单展示功能,主要是满足部分业务的复杂展示和表单移动端展示需求。

展示形式如下:



可视化流程


对于一个既定的流程来说,我们可以使用mermaid来实现它的可视化。具体来说,我们只需要将流程中所有的节点编号和流转条件以字符串的形式提供,并在前端使用该库,即自动产生流程图。举个例子:



将会产生如下流程图:



 引擎设计


除了这些概念之外,流程引擎还需要提供一组基础的动作用于控制流程的行为,譬如:流转、拒绝和关闭等。


节点流转


当节点被审批通过后,下游节点会根据与该节点间边的流转条件是否符合而被激活。在没有自动节点时,流转是一次性行为。我们通过遍历它和下游节点边的流转条件,依次判断条件表达式选择要推动的分支。但当引入自动流转的节点后,我们就需要通过递归来处理这一动作。伪代码如下:



节点拒绝


当然,节点动作还包含了拒绝操作。当节点被拒绝时,如果流程此时不存在一个节点能使其完结,置流程状态为拒绝。伪代码如下:



这里的关键是如何判断流程是否可以流转。一个简单的思路是:由于上游流转条件的逻辑属性存在于下游节点中,我们可以通过自底向上的方法来判断终止节点是否存在一条指向等待审批状态的节点的通路这一命题是否成立来确定流程的状态。Go版本代码如下:



下面的左图和右图分别为活跃态和拒绝态的流程:



由于C节点被拒绝,此时右图中已不存在可以使E节点到达B节点的通路了(D的激活条件时B、C均被流转)。


异步执行设计


插件


流程执行过程中往往需要和外部系统做交互已达到自动化执行某些操作。对于Comet流程引擎来说,它支持两种类型的插件形式:二进制文件和Python脚本。由于Python脚本相比于二进制文件可维护性更好,所以插件主要采用此种形式来书写。用户上传了插件文件之后,Comet会将其放置在其容器挂载的NAS中。流程中节点插件可以存在两种,一种是在当前节点状态变为待审批之前,另一种是在当前节点执行之后。具体如下图所示:



我们可以将节点的待审批通知放在pre-plugin中,将节点处理后通知业务方系统的http调用放在after-plugin中,从而达到相关事件和对应人和系统的绑定。事实上,将流程的所有动作(诸如:发起、流转、拒绝等)作为事件,然后配置相关事件的动作(插件执行、通知发送)会比这种方式更加灵活。

用户上传插件后,系统将为其生成唯一ID并存入NAS中,执行日志将以日期与插件两个维度组织存入磁盘,以方便批量清理及读取供用户排障,展示形式如下图所示:



前后的执行时间为系统写入,方便定位插件超时问题。


回调


为了配合插件达到在节点/流程中耦合业务处理代码逻辑,Comet流程引擎还实现了回调功能。在业务方处理逻辑需要被融合到节点成功/失败的状态中时,业务方可以利用插件调用其处理接口并用处理结果回调Comet。其时序图如下图所示:



通过这种方式,业务方可以非常方便的处理流程引擎产生的事件动作并灵活的定义其处理手段。


 合法性检测算法


在流程模版的创建过程中,我们必须检测其合法性。在计算机科学领域中,图是一种抽象的数据类型。通常它由节点组成,并且按照边的有向性无向性分为有向图和无向图。流程图作为有向图的一个分支,通常从一个起始点(入度为0)发散到一个终止点(出度为0)收敛。如下图所示:



起始点A和终止点F可以辅助组织流程,并且可以方便我们定义流程的起止状态(即起/终点被激活)。


邻接矩阵


领接表、领接矩阵关联矩阵是图的几种常见的数据结构。对于邻接矩阵(adjacency matrix)来说,它是指一种方阵,用来表示有限图。它的每个元素代表各点之间是否有边相连。如下图所示:



从左到右、从上到下分别代表A-F六个节点。方阵中元素为1代表从节点行到节点列存在一条有向通路。

对于流程来说它需要满足只有一个入度为0和一个出度为0的节点,也就是说在添加了辅助节点的流程的邻接矩阵中必然有且仅有一行和一列全为0。然而仅通过此方式还存在一个问题:当图中有环时可能会导致流程一直循环,无法终止。因此我们引入另一个概念 - 有向无环图。


有向无环图


在图论中,如果一个有向图从任意顶点出发无法经过若干条边回到该点,则这个图是一个有向无环图DAGDirected Acyclic Graph)。对于一个流程来说,它也必将从某一节点发散,到某一节点收敛。因此一个有向图为合法的流程是该有向图为有向无环图的充分不必要条件。

判断一个图是否为有向无环图的算法有卡恩算法深度优先搜索(DFS)。深度优先搜索以任意顺序循环遍历图中的每个节点。若搜索进行中碰到之前已经遇到的节点,或碰到叶节点,则中止算法。下面简单看下Go语言版本的实现:



总结


工作流是业务流的一部分,而流程引擎就是驱动业务按照公司设定的固定流程去流转,在复杂多变的业务情况下,使用既定的流程能够提高工作效率,降低设计业务成本,保证业务执行的准确性。自Comet 2019年立项以来,累计多版本迭代并稳定运行至今,业务涵盖基数据平台、运维平台、中间件平台和推送平台等多个内部平台和业务。

本文以Comet流程引擎为例,介绍了其基本的运作流程、软件架构及代码实现。对于Comet来说,灵活性是其产品设计的最大特点。通过插件、节点、流转条件三者的组合,业务可以灵活的配置和实现绝大部分审批和自动化场景的诉求。插件提高了业务与Comet的交互场景、流转条件丰富了流程审批链的多样性。Comet在复杂多变的业务情况下驱动业务按照设定的流程去流转,使用既定流程大大降低设计业务的成本并保证了业务执行的准确性。同时,由于流程引擎是低代码平台的核心,它可以帮助我们去实现非常灵活的流程设计,极大的助力企业实现数据流转的的规范化和自动化。


参考

[1] 业务流程建模和标注规范:https://www.bpmn.org

[2] 邻接矩阵:https://zh.wikipedia.org/wiki/%E9%82%BB%E6%8E%A5%E7%9F%A9%E9%98%B5

[3] 有向无环图:https://zh.wikipedia.org/wiki/%E6%9C%89%E5%90%91%E6%97%A0%E7%8E%AF%E5%9B%BE

[4] About Mermaid:https://mermaid.js.org/#/README


以上是今天的分享内容,如果你有什么想法或疑问,欢迎大家在留言区与我们互动,如果喜欢本期内容的话,欢迎点个“在看”吧!


往期精彩指路

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存