我们如何制作工作流引擎

在DIRECTUM,我们正在开发DirectumRX ECM系统。 ECM系统的工作流模块的核心元素是引擎。 他负责在生命周期内更改流程实例(实例)的状态。 在开始开发工作流模块之前,您应该决定:采用现成的引擎或编写自己的引擎。 最初,我们选择了第一个选项。 我们采用了Windows Workflow Foundation(WF)引擎,总体而言,它适合我们。 但是随着时间的流逝,我们意识到我们需要自己的引擎。 我将在下面说明这种情况如何发生以及产生了什么。

旧引擎


为什么是wf?


早在2013年,是时候为DirectumRX开发工作流程模块时,我们决定采用现成的引擎。 从Windows Workflow Foundation(WF),ActiveFlow,K2.NET,WorkflowEngine.NET,cDevWorkflow,NetBpm查看。 有些人对成本不满意,有些人还不满意,到那时,有些人已经很久没有得到支持了。
结果,选择权落在了WF上。 然后,我们积极使用Microsoft堆栈(WCF,WPF),并决定另一个W不会伤害我们。 另一个优势是我们作为Microsoft金牌应用程序开发合作伙伴的地位,这使得使用Microsoft技术开发产品成为可能。 好吧,总的来说,引擎的功能适合我们,并涵盖了几乎所有情况。

WF有什么问题?


使用WF的6年后,我们积累了许多问题,解决这些问题的成本过高。 我们开始考虑开发自己的引擎。 我会告诉你其中的一些。

昂贵的诊断和错误修复


几年过去了,产品安装数量和负载都在增加。 错误开始出现,对其进行诊断和纠正需要大量资源。 这是由于多种原因而造成的:缺乏竞争力,嵌入以前的引擎时出现设计错误以及WF的功能。
我们有足够的基本能力来构建WF DirectumRX,而同样的水平足以处理简单的错误。 对于复杂的案例,缺乏能力-日志分析,实例状态分析等都很困难。
可以派人参加WF课程,但是几乎没有教他们如何分析实例的状态并将其更改与日志相关联。 坦率地说,没有人特别希望通过几乎已死的技术来提高自己的技能。
另一种方法是雇用具有适当能力的人员。 但是在伊热夫斯克找到一个人并不是一件容易的事,也不是其水平足以解决我们的问题的事实。
实际上,我们支持WF的门槛很高。 如果没有其他原因,我认为可以以某种方式解决这个问题。
另一个问题是,在构建流程图时,我们使用自己的符号。 它更直观,更易于开发。 例如,WF不允许实现完整的图形,您不能绘制无用的块,有绘制并行分支的功能。 其回报是将我们的电路转换为WF电路,这不是那么简单,并且存在许多限制。 调试时,我必须分析WF电路的状态,因此,可见性丢失了,我不得不将块和面相互比较以了解实例所处的步骤。
图片

DirectumRX中的电路表示
图片

WF中的电路表示
此外,我们还面临着这样一个事实,即WF文档对实例存储库的描述很差。 如我上面所写,在分析错误以了解流程实例的状态时,这是必需的。 另外,部分数据被加密,这也干扰了分析。

Postgres作为DBMS


多年来,俄罗斯出现了替代进口的趋势,并且对该平台的要求越来越多地是对开源数据库管理系统(DBMS)或国内生产的DBMS的支持。 最常见的是Postgres。 WF开箱即用,仅支持MS SQL。 要与其他数据库一起使用,可以使用第三方提供程序。 我们从DevArt选择了dotConnect。
虽然负载很轻,但一切正常。 但是,一旦我们在负载下驱动系统,就会出现问题。 WF可能突然停止并停止处理实例(准备好的事务已结束),或者所有消息都进入了MSMQ Poisoned Queue,等等。 我们处理了所有这些问题,但是花了很多时间。 无法保证不会出现一个新的解决方案,而要解决该方案则必须花费相同的金额。

关心.net核心


在Microsoft宣布.Net Core之后,我们决定逐步使用它,以实现解决方案的跨平台。 微软决定不使用WF,这阻碍了我们将Workflow模块以其存在的形式转移到.Net Core的方式。 我们知道,.Net Core上存在非官方的WF端口,其中甚至包括WF开发人员提供的端口,但它们并非100%兼容。 另一个因素是微软拒绝开发.Net。 支持.Net Core。

新引擎


考虑到所有这些问题,解决方案选项,重构和更正的复杂性,权衡了所有利弊之后,我们决定改用新引擎。 我们首先分析现有的。

选择


选择发动机的主要要求是:
  • 在.Net Core上工作;
  • 可扩展性
  • 转换现有流程实例,并能够在转换后继续执行
  • 分析现有问题的合理成本
  • 使用不同的DBMS

此外,还要求活动(Activity)可以在C#中执行应用程序代码,并且可以调试块等。
作为对现有引擎的分析的一部分,我们查看了:
  1. 核心wf
  2. Flowwright
  3. K2工作流程
  4. 工作流程核心
  5. Zeebe
  6. 工作流程引擎
  7. 耐用的任务框架
  8. 卡蒙达
  9. 奥尔良活动

在对所审查的解决方案强加所有要求并增加付费解决方案的成本之后,我们认为我们的引擎并不是很昂贵,尽管它会100%适合我们的要求,并且很容易改进。

实施/架构


在先前的实现中,WF模块是WF库连接到的WCF服务。 他能够创建流程实例,启动流程,执行块,包括业务逻辑(开发人员编写的代码)。 所有这些都托管在IIS应用程序中。
在新的实现中,随着微服务体系结构的发展趋势,我们决定立即将服务分为两部分:工作流流程服务(WPS)和工作流块服务(WBS),它们可以分别托管。 该链中的另一个链接是应用程序服务,该服务实现DirectumRX系统和业务逻辑,并且客户端可以使用它。
WPS根据该方案“行走”,WBS在每个步骤处理块并运行业务逻辑。 启动该过程的命令来自Application Server。 服务之间的交互是使用RabbitMQ进行的。 下面我将告诉您有关它们的更多信息。


Wps


工作流流程服务是一种微服务,负责启动流程和绕过流程图。
服务存储库包含流程图,该流程图支持流程实例的版本控制和序列化状态。 您可以使用MS SQL和Postgres作为存储。
该服务能够处理通过RabbitMQ从其他服务接收到的消息。 本质上,消息是服务API。 服务可以接收的消息类型:
  • StartProcess-创建一个新的流程实例并在其上开始爬网;
  • CompleteBlock-块的完成,在此消息之后,服务将流程实例沿方案进一步移动;
  • Suspend / ResumeProcess-暂停流程实例的执行,例如由于处理块时发生错误而导致的错误,并在错误修复后恢复执行;
  • Abort / RestartProcess-停止执行流程实例,然后重新启动;
  • DeleteProcess-删除流程实例。

该方案由块和它们之间的连接(面)组成。 每个面孔都有一个标识符,即所谓的“执行结果”。 有5种类型的块:
  • 启动块
  • OrBlock;
  • 和块;
  • FinishBlock。

图片

WPS模式视图
当消息到达流程的开始时,服务将创建一个实例,并根据该方案开始“遍历”。 按照计划负责“步行”的班级,我们开玩笑地称其为“阶梯”。 电路始终以StartBlock开始。 然后,跨步者将所有传出的面孔都激活并激活它们。 每个块均按照“与”块的原理工作,即 所有传入的面都必须处于活动状态,以便可以激活该块。 然后,算法确定可以激活哪些块,并发送WBS消息以激活这些块。 WBS处理该块并返回WPS的结果。 根据执行结果,跨步程序选择从块中出来的相应面以进行激活,然后过程继续进行。
在开发过程中,我们遇到了与块之间的循环连接有关的有趣情况,这在决定激活/停止哪个块时增加了逻辑。
该服务是自主的,即 只需将其以Json格式传递给他,编写您自己的块处理程序,就可以交换消息了。

b


工作流块服务是一种处理框图的服务。 该服务了解业务逻辑的本质,例如任务,任务等。 可以将这些实体添加到DirectumRX Development Studio(DDS)开发环境中。 例如,我们的块有一个事件来启动该块。 该事件处理程序的代码由开发人员在DDS中编写,WBS执行该代码。 实际上,这是我们对块处理程序的实现;您可以用自己的替换它。
该服务存储块的状态。 除了基本属性(Id,状态)之外,该块还可以包含执行/终止/暂停该块所需的其他信息。
块可能处于以下状态:
  • 已完成-成功完成块上的工作后进入此状态;
  • 待定-当在该块内执行某些工作时处于等待状态,例如,需要用户做出某种响应;
  • 已中止-进程停止时进入此状态;
  • 挂起-发生错误时进程停止时进入此状态。

当消息到达执行该块时,将执行该块,并且WBS发送一条带有该块结果的消息。

可扩展性


WPS和WBS可以部署在多个实例中。 在某一时间点,只有一项WPS服务可以处理一个流程实例。 处理块也是如此-一个流程实例一次只能处理一个块。 处理期间在流程上施加的锁有助于此操作。 如果队列中有多个消息要处理一个进程/一个进程中有多个块,则该消息会延迟一段时间。 同时,每个服务可以同时在多个流程实例上执行工作。
当几个消息在一个进程中接一个进程处理多个块(并行分支)时,可能会出现这种情况。 为了减少不得不推迟消息的情况,WBS一次处理几条消息,然后一次又一次地运行它们,由于进程的阻塞,绕过了发送到队列以重新执行。

转换次数


过渡到新引擎后,出现了一个问题,如何处理现有流程实例? 首选的选择是进行转换,以便他们继续使用新引擎。 优势显而易见:我们仅支持一个引擎,而支持旧引擎的问题就消失了(见上文)。 但是存在一些风险,我们无法完全弄清楚如何从序列化流程实例中获取所需的数据。 还存在一个后备:让现有实例在旧引擎上最终确定,并在新引擎上启动新实例。 此选项的缺点来自于前一个选项的优点,另外还需要额外的资源来扭转两个引擎。
为了进行转换,我们需要以WF格式获取流程的旧状态并生成流程和块的状态。 我们编写了一个实用程序,该实用程序获取数据库中流程实例的序列化状态,从中获取活动块的列表,面的执行结果以及虚拟地执行流程。 结果,我们获得了转换时实例的状态。
如何在WF中正确反序列化流程实例数据出现了难题。 WF的流程(实例)实例的状态以xaml的形式存储在数据库中。 我们找不到这个xml的结构的清晰描述,我们不得不从经验上一直走下去。 手动解析数据并提取我们所需的信息。 作为此任务的一部分,我们找到了另一个选择-使用WF工具反序列化实例的状态并尝试从对象获取信息。 但是由于这些对象的结构非常复杂,因此我们放弃了这个想法,而是选择了“手动”解析xaml。
结果,转换成功,新引擎开始处理所有流程实例。

结论


那么工作流引擎给了我们什么? 实际上,我们设法克服了本文开头提到的所有问题:
  • 该引擎是用.NET Core编写的;
  • 它是独立于IIS的自托管服务;
  • 作为测试操作,我们在公司系统中积极使用新引擎,并设法确保错误分析花费的时间大大减少;
  • 根据初步数据,在Postgres上进行了负载测试,WPS + WBS捆绑包可以轻松应对5000个并发用户的负载;
  • 当然,就像任何有趣的作品一样,这是一次有趣的经历。

另外,我们获得了清晰,受支持的代码,可以适应自己的情况。
事实证明,该引擎的成本与我们在购买/改装第三方产品时所花费的成本相当。 目前,我们认为开发自己的引擎的决定是合理的。
我们还在等待10,000多名并发用户的负载测试。 也许将需要一些优化,或者它会取得成功? ;-)
我们最近发布了DirectumRX 3.2,其中包括新的工作流程。 让我们看看引擎如何向客户展示自己。

Source: https://habr.com/ru/post/zh-CN468803/


All Articles