首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >工作流自动节点的同步与异步:两条实现路径的取舍

工作流自动节点的同步与异步:两条实现路径的取舍

原创
作者头像
驰骋工作流程
发布于 2026-10-17 10:13:04
发布于 2026-10-17 10:13:04
110
举报

自动步骤要不要占一个事务、要不要落作业队列,是流程引擎设计里最实际的分歧之一。本文从建模单元、触发时机、失败恢复与可观测性四个角度做对照,并给出实施侧的取舍建议。

一、过程节点是什么:从一个枚举说起

NodeExt.ts 里 WhoExeIt 是节点属性面板上的一个系统枚举下拉:

代码语言:javascript
复制
@0=操作员执行 @1=机器执行 @2=混合执行 @3=流水线执行 @4=过程执行

它回答的问题是「这个节点由谁做完」。其中 4 就是过程执行(过程节点):

  • 由系统执行,不需要人点发送;
  • 在同一次发送请求内同步连发,不是等定时任务扫描;
  • 仍然是一个节点:有接收人、有方向条件、有发送前事件、有轨迹。

对照该平台自己的另外几种执行方式,就能看清过程节点的定位:

WhoExeIt

名称

触发时机

是否进普通待办

0

操作员执行

人点发送

进

1

机器执行

后台定时扫描

不进(查询排除)

2

混合执行

打开表单时先试发

失败才进

3

流水线执行

工位扫码发送

进独立工位通道

4

过程执行

当次发送请求内连发

不进

关键在最后一行:过程节点和机器节点都不是「人」,但它们的触发路径不同。机器节点把继续流转交给定时任务(AutoRun_WhoExeIt.cs 里那条 WHERE WhoExeIt IN (1,2) 的 SQL,明确把 3、4 排除掉);过程节点则在发送线程里当场串起来跑完。


二、该引擎过程节点怎么跑起来

2.1 属性落在节点,运行时抄到待办

发送生成待办时,引擎把节点上的值抄到待办行(WorkNode.cs):

代码语言:javascript
复制
wl.WhoExeIt = town.HisNode.WhoExeIt;  // 设置谁执行它

批量的插入语句同样带上这一列(WorkNodePlus.cs):

代码语言:javascript
复制
INSERT INTO WF_GenerWorkerList (WorkID,FK_Emp,FK_Node,Idx,AtPara,IsEnable,WhoExeIt) VALUES(...)

所以 WhoExeIt 不是设计期的装饰字段,而是运行时调度依据:待办查询、定时任务、消息推送、流水线列表、过程页标签读的都是它。

2.2 发送循环内的同步连发

过程节点不进定时任务,由发送时同步连发。逻辑可以概括为:

代码语言:javascript
复制
发送完成后
while 下一节点的 WhoExeIt == 4 且流程未结束 且存在待办:
    切换到该待办处理人(Port_Login)
    以系统身份走完整发送(方向条件、接收人、发送前事件仍生效)
    把本次过程返回叠加进总消息
切回原操作员

「同步」两个字的含义是:这一次 HTTP 请求不返回,直到这一串过程节点跑完、或者某一步抛错停下。这跟机器节点「先落一条待办、后台慢慢发」是两条完全不同的路径。

2.3 明确不进定时任务

AutoRun_WhoExeIt.cs 里的注释写得很直白:

代码语言:javascript
复制
// 只捞机器执行(1)、混合执行(2)。流水线(3)、过程执行(4)不能进定时任务:4 由发送时同步连发。
string sql = "SELECT WorkID,FID,FK_Emp,FK_Node,FK_Flow,AtPara FROM WF_GenerWorkerList WHERE WhoExeIt IN (1,2) AND IsPass=0 AND IsEnable=1 ORDER BY FK_Emp";

这条边界非常重要:配置人员如果把一串连续系统节点误设成机器执行(1),它们就会变成一条条等待扫描的待办,时延、事务边界、失败位置都和过程执行(4)不一样。 这是实施时最容易踩的坑。

2.4 过程可观测:时间轴与流程图

过程节点若仍弹一句「发送成功」,集成场景基本没法用——外部系统可能跑几十秒,人不知道卡在哪一步、返回了什么。该引擎用节点属性 发送后转向(6/7) 接上过程执行页(MyProNodes.vue):

  1. 工具栏先打开大窗(默认时间轴或流程图),状态置为「执行中」,再并发调用发送;
  2. 过程执行页轮询轨迹,后端额外下发全流程节点(含 WhoExeIt),前端据此打标签:
代码语言:javascript
复制
// 前端模块源码目录
const exeLabel = (whoExeIt?: number) => (Number(whoExeIt) === 4 ? '[过程执行]' : '[人工执行]');
  1. 时间轴展示节点名、并行分支、处理人、耗时、下达时间、过程返回;
  2. 发送返回里的过程明细(JSON、许可 / Kafka / FTP 报文)优先贴到「过程执行」的完成项上,并过滤引擎套话;
  3. 超时约一分钟加强「外部系统处理中」提示。

后端在取待办数据时专门补了一张表,把节点的 WhoExeIt 带给前端:

代码语言:javascript
复制
// ProNodes:全流程节点含 WhoExeIt,用于时间轴 [过程执行]/[人工执行] 标签
Nodes flowNodes = new Nodes(this.FlowNo);
ds.Tables.Add(flowNodes.ToDataTableField("WF_FlowNodes"));

人看到的不再是黑盒自动任务,而是人机混合轨迹:人工节点显示处理人头像,过程节点显示系统返回与等待时长。

2.5 过程节点不是「跳过引擎」的脚本钩子

这是设计上必须强调的一点:连发过程仍走完整发送——ExecEvent.DoNode(EventListNode.SendWhen, ...) 照常触发,方向条件、接收人规则、阻塞模式全部生效(见 WorkNode.cs NodeSend)。身份切到待办处理人再切回,审计主体仍然是人,而不是一个匿名的「引擎账号」。

外部 API 失败时,抛出带「系统执行发送」前缀的异常,流程停在具体那个过程节点,前端过程页展示失败节点与返回原文。对国内审批留痕的习惯来说,这比「引擎账号执行、活动上无办理人」更贴近预期。


三、Flowable / Camunda 怎么表达「系统执行」

3.1 换图元,而不是改属性

BPMN 2.0 的答案不是「节点的执行方式」,而是换一种活动类型:

BPMN 图元

职责

与该引擎大致对等

User Task

人工等待,TaskService 查询/签收/完成

操作员执行(0)

Service Task + 类/表达式

引擎内同步调用 Java 逻辑

过程执行(同步部分)

Service Task + asyncBefore

落作业,Job Executor 续跑

机器执行(1)

External Task(C7/Flowable)/ Job Worker(C8)

外部进程按类型拉取、加锁、完成/失败

无直接一等对等

Timer Event

到点继续

延期发送 / 机器执行

Script / Business Rule Task

脚本或决策表

无独立图元

同一张流程图上,人做的、系统做的、外部系统做的是不同的图形元素。图上信息量大、语义精确,代价是实施人员要熟悉规范。

3.2 Service Task 的两种形态

同步委托(camunda:class / flowable:class):引擎事务里直接调用 Java 代码。这跟该引擎过程执行的「同一次请求内连发」最接近——都在调用方线程/事务里把逻辑跑完。

异步延续(asyncBefore="true" / flowable:async="true"):到达活动时先提交当前事务、落一条作业,返回调用方,由 Job Executor 用独立事务、独立线程续跑。官方文档对它的定位是「引入事务边界 / 保存点」,而不只是「让节点自动跑」:

By setting the asyncBefore='true' attribute we introduce an additional save point at which the process state will be persisted and committed to the database. A separate job executor thread will continue the process asynchronously by using a separate database transaction.

3.3 Job Executor 在解决什么

Flowable 的作业在 API 调用期间就写入 ACT_RU_JOB 表,成功的提交会触发 Async Executor 执行;执行器有可配置线程池,并有「acquire」线程去抢占未被加锁的作业。Camunda 7 的 JobExecutor 默认使用 exclusive job:同一流程实例的排他作业同一时刻只执行一个,避免并行合流处产生乐观锁冲突。

失败处理是这套模型的核心价值:

  • 作业失败会自动重试(Camunda 默认重试 2 次,可用 camunda:failedJobRetryTimeCycle 配成 R5/PT5M 这类策略);
  • 抛普通异常 → 走重试;抛 BpmnError → 跳过重试,交给边界错误事件;
  • 重试耗尽 → 生成 Incident(事故),人在 Cockpit / Operate 里处理。

对照该引擎:机器执行用「扫待办、发一次、失败记日志」近似「有活就发」,没有同等的作业锁协议、重试退避和事故工作台。社区/集群部署下要靠部署约定而不是引擎级 job lock。这一点对比必须写明:在高并发自动节点与失败恢复上,BPMN 作业模型更完整。

3.4 External Task 与 Job Worker:把「自动步骤」也变成待办

External Task 的类比是:服务任务也是一种待办,只是领取者不是人而是程序。fetchAndLock → 做事 → complete / failure,工人可多语言、可水平扩展,引擎不持有长调用。

Camunda 8(Zeebe)把这条路走得更彻底:Service Task 到达即生成一条持久化 job,worker 用 ActivateJobs(带 type、worker、timeout、maxJobs)拉取,再回 CompleteJob / FailJob(递减重试、可设 backoff)/ ThrowError。超时会重新分配作业,因此投递语义是 at-least-once,处理逻辑必须幂等。文档明确指出:worker 与引擎之间不存在横跨两边的技术事务,要靠业务事务来兜一致性。

这条路的代价是运维更重:要跑一套消费者、要处理超时与重试、要保证幂等。换来的是跨语言、长耗时、可独立扩缩的集成能力。


四、三类实现的核心区别

4.1 一张总表

维度

该引擎(WhoExeIt=4)

Flowable / Camunda 7

Camunda 8(Zeebe)

建模方式

同一节点改属性

换 BPMN 图元

换 BPMN 图元

过程执行的默认形态

发送请求内同步连发

Service Task 同步委托

默认走 Job Worker

异步执行载体

待办行(WhoExeIt 区分)

ACT_RU_JOB

持久化 job 队列

调度者

发送线程 / 定时任务

Job Executor

Worker 轮询 / 推送

作业锁

无引擎级锁

exclusive job

job 独占 + 超时重分配

重试

需再次发送或人工处理

引擎自动重试

worker 上报递减重试 + backoff

失败落点

具体过程节点,返回带异常原文

作业/事故(Incident)

job 失败/事故,Operate 可见

人怎么看

业务侧过程执行页(时间轴/流程图)

运维侧 Cockpit / 历史活动

运维侧 Operate

事务边界

一次请求内可能很长

asyncBefore 切分保存点

每步独立提交、at-least-once

典型优势

同一套节点生命周期、配置面直接、过程可视化给业务看

规范、作业可靠、生态大

分布式、多语言工人、水平扩展

4.2 差异一:自动步骤「还算不算原来那个节点」

该引擎的过程节点仍是胖节点:接收人、方向条件、退回、撤销、轨迹、表单权限都挂在它身上。把过程节点从「操作员」改成「过程执行」,这些能力一个都不用重配。

BPMN 里 Service Task 是瘦活动:它天然没有办理人、没有中国式审批的退回/加签/抄送。要在自动步骤上做这类事,得靠监听器、执行表达式或扩展属性自己补。

这不是谁优谁劣,而是复杂性放在了哪里:

  • 该引擎把复杂度放在「同一套节点生命周期的正确性」上——共享生命周期,就要自己处理待办过滤和消息短路。机器节点仍然生成待办行(只是查询排除),若还发「请审批」到达消息,产品就是错的。所以 WorkNode.cs 里对机器节点显式短路:
代码语言:javascript
复制
if (this.town != null && this.town.HisNode.WhoExeIt == 1)
    return;   // 机器执行不推到达消息
  • BPMN 把复杂度放在「规范与作业协议」上——Service Task 往往根本不生成 User Task,自然不会进 Tasklist,也就没有这个正确性义务。

4.3 差异二:触发时机的哲学

该引擎过程执行优化的是「人点一次发送、盯着集成跑完」。它把一串系统节点串在同一次请求里,业务上等价于「这一批动作是一件事」。

代价同样清楚:

  • HTTP 超时、中途宕机、逐步重试不是这条路径的强项;
  • 请求内事务可能拉得很长;
  • 长耗时、需重试的外部调用,更适合机器执行(定时再发)或自建工人,而不是无限拉长当次请求。

Camunda 7 的官方建议恰恰相反:即便一串自动活动可以在同一事务里跑完,也建议用 asyncBefore / asyncAfter 切开,避免长事务与锁竞争。Flowable / Camunda 用作业把外部 IO 移出引擎事务,这是多年踩坑后的默认结论。

环节

该引擎的过程执行

Flowable / Camunda 的异步作业

起点

人工点发送

人工完成任务并提交

第 1 步

过程节点 A,同一请求内连发

提交事务,落作业 1

第 2 步

过程节点 B,同一请求内连发

独立事务执行活动 A,落作业 2

第 3 步

过程节点 C,同一请求内连发

独立事务执行活动 B,落作业 3

收尾

汇总结果返回调用方

独立事务执行活动 C

事务边界

整条链路一个事务

每个活动一个保存点

失败后

停在具体过程节点,返回异常原文

作业重试,耗尽后生成事故

左边一条链在同一事务里走完,快、直观、可视化好;右边每一步都有保存点,崩了能从作业恢复,但多了调度与运维成本。

左边一条链在同一事务里走完,快、直观、可视化好;右边每一步都有保存点,崩了能从作业恢复,但多了调度与运维成本。

4.4 差异三:失败之后谁看见、谁重试

该引擎过程执行

Camunda 同步委托

异步作业 / Worker

失败位置

当前过程节点,返回带「系统执行发送」前缀

当前活动(事务回滚范围取决于实现)

作业/工人失败,实例停在该活动

重试

需再次发送或人工处理

同请求内难自动重试

引擎/worker 重试 + 事故

人怎么看

业务侧过程执行页失败态 + 返回原文

异常栈 / 业务自绘

Operate / Cockpit(运维视角)

过程执行页把集成报文展示给业务人员;Cockpit / Operate 面向运维人员。两者不互相替代。


五、场景怎么选

场景

更贴该引擎的用法

更贴 Flowable / Camunda 的用法

人点发送后连续调内部 API,要盯进度

过程执行(4)+ 发送后转向 6/7

一串 Service Task + 自建进度页;要可靠则每步 async

夜间过账、不占待办

机器执行(1)

asyncBefore Service Task

打开即过、过不了再填

混合执行(2)

网关拆分,或监听器 Complete

工位扫码过站

流水线执行(3)

User Task + 自研工位端

跨语言、长耗时、需重试扩缩

不宜单靠过程同步连发

External Task / Job Worker

到点再发已填好的单

延期发送(改执行方式)

Timer 事件(图上可见)

混合使用也常见:核心审批用该引擎,超长集成用独立工人回写后「发送」推进。那是架构组合,不是某引擎的失败。


六、给实施与二次开发的要点

  1. 连续系统步骤请设过程执行(4),不要设成机器执行(1)——后者进定时扫描,前者在当次发送内连发,时延与事务边界不同。
  2. 过程节点仍要配接收人:连发会按待办人切换身份,便于权限与审计。
  3. 外部调用放在节点发送事件 / 过程里,返回信息会出现在过程页「结果」区。
  4. 需要人盯进度时,上一人工节点的「发送后转向」选 6 或 7。
  5. 长耗时、要重试的外部集成,不要无限拉长过程执行的同步请求——改用机器执行或独立工人。
  6. 机器/流水线节点默认不进普通待办:若业务必须通知人,用独立消息策略,不要依赖默认到达推送。

七、收束

三家都在解决同一件事:人工等待与系统继续如何在一条流程里共存。分歧不在「有没有自动节点」,而在建模单元和调度哲学:

  • Flowable / Camunda 问的是:活动是什么类型,令牌和作业怎么往前走;
  • 该引擎问的是:这个节点由谁、按什么节奏执行,同一节点进哪条通道。

实现上可以记成:同一节点、五种执行方式、同一套发送。机器靠待办扫描,过程靠当次连发,混合靠打开试发,流水线走工位接口;WhoExeIt 同时管待办过滤、消息短路和轨迹标签。

过程节点(WhoExeIt=4)是这套设计里最典型的一个切面:它把「一串系统动作」仍然当成节点来管——有接收人、有事件、失败停在具体节点、业务人员能在过程页看见人机混合轨迹。而 Flowable / Camunda 用作业模型换来了可恢复、可重试、可多语言扩展的自动步骤。复杂性放的位置不同,验收标准就不该混用。


附:本文涉及的代码与文件索引

文件

作用

前端模块源码目录

WhoExeIt 前端属性定义(@4=过程执行)

前端模块源码目录

设计器枚举与写回节点

引擎源码模块

后端节点实体枚举定义

引擎源码模块

WhoExeIt 字段常量

引擎源码模块

Node.WhoExeIt 取属性值

引擎源码模块

待办抄写 WhoExeIt、发送前事件、机器节点消息短路

引擎源码模块

待办批量插入带 WhoExeIt

引擎源码模块

机器/混合节点定时执行(排除 3、4)

引擎源码模块

下发全流程节点 WhoExeIt 给过程页

前端模块源码目录

过程执行页时间轴 / 流程图 / 标签

引擎源码模块

流水线(WhoExeIt=3)待办接口

引擎源码模块卫星设计院任务流程控制系统/...xml

实际流程中过程节点(WhoExeIt=4)的配置样例

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 一、过程节点是什么:从一个枚举说起
  • 二、该引擎过程节点怎么跑起来
    • 2.1 属性落在节点,运行时抄到待办
    • 2.2 发送循环内的同步连发
    • 2.3 明确不进定时任务
    • 2.4 过程可观测:时间轴与流程图
    • 2.5 过程节点不是「跳过引擎」的脚本钩子
  • 三、Flowable / Camunda 怎么表达「系统执行」
    • 3.1 换图元,而不是改属性
    • 3.2 Service Task 的两种形态
    • 3.3 Job Executor 在解决什么
    • 3.4 External Task 与 Job Worker:把「自动步骤」也变成待办
  • 四、三类实现的核心区别
    • 4.1 一张总表
    • 4.2 差异一:自动步骤「还算不算原来那个节点」
    • 4.3 差异二:触发时机的哲学
    • 4.4 差异三:失败之后谁看见、谁重试
  • 五、场景怎么选
  • 六、给实施与二次开发的要点
  • 七、收束
  • 附:本文涉及的代码与文件索引
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档