解决Activiti排他网关(ExclusiveGateway)陷入无限死循环问题

一、 问题现象

在某次工作流业务迭代后,测试环境突然出现接口响应超时、应用服务器 CPU 使用率飙升的现象。查看应用日志,发现 Activiti 引擎在短时间内疯狂打印 DEBUG 级别的执行日志,且伴随大量重复的节点流转记录。

截取其中一段核心日志如下:

text 复制代码
2026-09-16 15:38:33.174 [http-nio-5500-exec-27] DEBUG o.a.e.i.a.DefaultActivitiEngineAgenda - Operation class org.activiti.engine.impl.agenda.ContinueProcessOperation added to agenda
2026-09-16 15:38:33.175 [http-nio-5500-exec-27] DEBUG o.a.e.i.i.CommandInvoker - Executing operation class org.activiti.engine.impl.agenda.ContinueProcessOperation 
2026-09-16 15:38:33.175 [http-nio-5500-exec-27] DEBUG o.a.e.i.a.ContinueProcessOperation - Sequence flow 'sid-A31E8830-CCC6-41E8-8503-B828995B0B94' encountered. Continuing process by following it using execution 3515010
...
2026-09-16 15:38:33.181 [http-nio-5500-exec-27] DEBUG o.a.e.i.a.ContinueProcessOperation - Executing activityBehavior class org.activiti.engine.impl.bpmn.behavior.ExclusiveGatewayActivityBehavior on activity 'sid-26F76F2A-1691-4440-A305-7040EBA091D0' with execution 3515010
2026-09-16 15:38:33.182 [http-nio-5500-exec-27] DEBUG o.a.e.i.b.b.ExclusiveGatewayActivityBehavior - Leaving exclusive gateway 'sid-26F76F2A-1691-4440-A305-7040EBA091D0'
2026-09-16 15:38:33.182 [http-nio-5500-exec-27] DEBUG o.a.e.i.b.b.ExclusiveGatewayActivityBehavior - Sequence flow 'sid-A31E8830-CCC6-41E8-8503-B828995B0B94' selected as outgoing sequence flow.
...
2026-09-16 15:38:33.200 [http-nio-5500-exec-27] DEBUG o.a.e.i.a.ContinueProcessOperation - Executing activityBehavior class org.activiti.engine.impl.bpmn.behavior.ExclusiveGatewayActivityBehavior on activity 'sid-26F76F2A-1691-4440-A305-7040EBA091D0' with execution 3515010

从日志中可以提取出三个关键特征:

  1. 时间戳极短 :从 15:38:33.17415:38:33.200,在 26 毫秒内完成了多次节点流转,说明这不是正常的业务等待,而是同步的 CPU 密集型计算。
  2. Execution ID 固定 :所有的流转都发生在同一个执行实例 3515010 上,没有产生子执行(Child Execution)。
  3. 节点与连线重复 :流程在排他网关 sid-26F76F2A-1691-4440-A305-7040EBA091D0 和序列流 sid-A31E8830-CCC6-41E8-8503-B828995B0B94 之间反复横跳,形成闭环。

这是一个典型的 Activiti 引擎死循环(Infinite Loop)问题。

二、 引擎执行机制与根因剖析

要理解为什么会发生死循环,需要深入了解 Activiti 的核心执行机制。

1. Agenda 与 ContinueProcessOperation

Activiti 5/6 及 Flowable 引擎采用了基于 Agenda(议程)的命令模式来驱动流程流转。当流程向前推进时,引擎会将 ContinueProcessOperation 放入 Agenda 队列中。该操作负责判断当前节点类型:

  • 如果是连线(Sequence Flow),则继续寻找目标节点。
  • 如果是活动节点(Activity),则执行对应的 ActivityBehavior

2. 排他网关的路由逻辑

当日志显示执行到 ExclusiveGatewayActivityBehavior 时,引擎会调用其 leave() 方法。排他网关的职责是评估所有出线(Outgoing Sequence Flows)上的条件表达式(Condition Expression),并选择第一条 评估结果为 true 的连线继续执行。

3. 死循环的形成机制(根因)

经过排查 BPMN XML 流程定义文件,发现问题出在流程设计器的操作失误上:在排他网关上多拉了一根序列流,但这根序列流没有连接任何目标节点(悬空),或者在 XML 层面其 targetRef 错误地指向了网关自身。

当引擎执行到这根"无效"的序列流时,引发了以下连锁反应:

  • 条件评估失效 :悬空或自引用的序列流通常没有配置条件表达式。在某些引擎版本的解析逻辑中,没有条件表达式的连线可能会被默认视为 true,或者在缺乏 Default Flow 配置时,引擎的容错机制导致其错误地选中了这条连线。
  • 自引用闭环 :日志显示选中的出线是 sid-A31E8830...,而该连线的目标又回到了 sid-26F76F2A...(排他网关自身)。
  • 缺乏 Wait State(等待状态) :排他网关和序列流都是非等待节点。与 UserTask(用户任务)或 ReceiveTask(接收任务)不同,它们在执行时不会将控制权交还给数据库和调用方,而是在同一个数据库事务和同一个线程内同步执行完毕。

因此,引擎在 网关 -> 悬空/自引用连线 -> 网关 的路径上不断将 ContinueProcessOperation 压入 Agenda 并立即执行,形成了一个没有出口的同步死循环,最终导致线程阻塞、CPU 飙升,甚至引发 StackOverflowError 或事务超时。

三、 解决方案

针对此类问题,处理过程分为紧急止血和根本修复两个阶段。

1. 紧急止血(运维侧)

死循环会持续占用线程池和 CPU 资源,必须立即中断。

  • 终止流程实例 :通过 Activiti 的 API 或管理后台,强制删除陷入死循环的流程实例。

    java 复制代码
    // 删除流程实例,reason 可以写 "死循环强制终止"
    runtimeService.deleteProcessInstance("3515010", "Infinite loop detected");

    注意:如果引擎已经因为死循环导致该线程完全卡死,API 调用可能会超时。此时需要重启应用节点,并在重启后立刻清理该实例。

  • 调整日志级别 :死循环会产生海量 DEBUG 日志,极易撑爆磁盘。临时将 org.activiti 的日志级别调整为 INFOWARN

2. 根本修复(开发侧)

修改 BPMN 流程定义文件,消除结构缺陷。

  • 清理无效序列流 :在流程设计器中删除那根多余的、未连接目标节点的序列流。如果是直接修改 XML,需确保 <sequenceFlow> 标签的 sourceReftargetRef 指向合法且非自身的节点,或者直接删除该 <sequenceFlow> 标签。

  • 配置默认流(Default Flow) :这是排他网关设计的核心规范。必须为排他网关指定一条 default 属性,指向一条兜底的序列流。当所有业务条件都不满足时,引擎会走默认流,从而避免路由失败或走入错误分支。

    xml 复制代码
    <exclusiveGateway id="sid-26F76F2A..." default="defaultFlowId" />
    <sequenceFlow id="defaultFlowId" sourceRef="sid-26F76F2A..." targetRef="endEvent_1" />
  • 重新部署:修正 XML 后,重新部署流程定义。对于已经运行的旧版流程实例,如果存在悬空线风险,建议通过脚本进行数据迁移或作废处理。

四、 最佳实践

依赖人工检查 BPMN XML 是不靠谱的,尤其是在流程复杂、分支众多的情况下。建议在工程体系中引入以下机制来杜绝此类低级错误:

1. 引入 BPMN 自动化校验

在 CI/CD 流水线或应用启动阶段,增加流程定义的静态校验。

可以使用 bpmnlint 等开源工具,或者在 Activiti/Flowable 中自定义 ProcessValidator。校验规则应至少包含:

  • 所有 SequenceFlow 必须具有合法的 sourceReftargetRef
  • SequenceFlow 的 sourceReftargetRef 不能相同(禁止直接自循环)。
  • ExclusiveGateway 和 InclusiveGateway 必须配置 default 属性(默认流)。
  • 流程中不能存在孤立节点(无入线或无出线的非 Start/End 节点)。

在 Spring Boot 中,可以通过实现 DeploymentConfigurer 或在部署前调用 ProcessValidator 进行拦截:

java 复制代码
@Autowired
private ProcessValidator processValidator;

public void validateAndDeploy(BpmnModel bpmnModel) {
    List<ValidationError> validationErrors = processValidator.validate(bpmnModel);
    if (!validationErrors.isEmpty()) {
        // 记录错误并抛出异常,阻断部署
        throw new IllegalStateException("BPMN 模型校验失败: " + validationErrors);
    }
    repositoryService.createDeployment().addBpmnModel("process.bpmn20.xml", bpmnModel).deploy();
}

2. 规范流程设计器使用

很多悬空线是由于前端流程设计器(如 bpmn.js)的拖拽误操作产生的,且在 UI 上不易察觉(例如线头没有吸附到节点的锚点上)。

  • 培训业务人员和开发人员正确使用建模工具。
  • 在前端设计器中增加保存拦截逻辑:在生成 XML 前,遍历所有连线,检查其 targetRef 是否为空,若为空则禁止保存并高亮提示。

3. 完善监控与告警

  • 线程监控 :配置 APM 工具(如 SkyWalking、Arthas),监控 Tomcat/Undertow 线程池的状态。如果发现某个线程长时间(如超过 5 秒)停留在 ContinueProcessOperationExclusiveGatewayActivityBehavior 的堆栈上,立即触发告警。
  • 日志限流:在 Logback/Log4j2 中配置限流策略,防止单个节点在极短时间内打印成千上万条重复日志,保护磁盘 I/O。
相关推荐
李少兄3 天前
解决 Activiti UEL 表达式解析异常 TreeBuilderException
activiti
大龄码农有梦想1 个月前
Camunda 7 与 Camunda 8 不是升级关系:架构差异与选型边界
activiti·flowable·流程引擎·camunda·开源工作流·开源工作流引擎
xrl20124 个月前
JeecgBoot集成Activiti工作流实现定时器案例
activiti·定时器·flowable·camunda·jeecgboot
spencer_tseng8 个月前
activiti-engine-7.0.0.Beta2.jar org/activiti/db/create/*.sql
activiti
hhzz9 个月前
Activiti7工作流(五)流程操作
java·activiti·工作流引擎·工作流
D_alyoo10 个月前
06 Activiti 与 Spring Boot 整合
java·activiti·activiti7源码
D_alyoo1 年前
Activiti 中各种 startProcessInstance 接口之间的区别
java·activiti
老马啸西风1 年前
工作流引擎-18-开源审批流项目之 plumdo-work 工作流,表单,报表结合的多模块系统
vue.js·开源·activiti·workflow·flowable·oa·bpm
老马啸西风1 年前
工作流引擎-16-开源审批流项目之 整合Flowable官方的Rest包
开源·activiti·workflow·flowable·erp·oa·bpm