如果把流程引擎比作一台计算机,那么:
-
流程定义 = 程序代码(描述了要做什么)
-
流程实例 = 运行中的进程(程序的运行态)
-
PVM = CPU + 操作系统(读取"指令"、调度执行、管理状态)
本文将以 EKP 平台的 PVM 层真实源码为基础,带你从概念到实现,彻底理解流程虚拟机的设计思想。
一、PVM 是什么?
1.1 概念定义
PVM(Process Virtual Machine)是流程引擎的核心执行层。它的职责是:
读取流程定义,按图索骥地驱动流程实例从一个节点走到下一个节点,同时管理整个执行过程中的状态变迁。
PVM 层的设计理念是与具体流程引擎解耦------这意味着同一套 PVM 可以驱动不同"方言"的流程定义(BPMN、自定义流程等),只要它们遵循 PVM 定义的接口契约。
1.2 为什么需要虚拟机?
| 问题 | PVM 的解决方案 |
|---|---|
| 流程定义格式多样(BPMN、自定义JSON等) | PVM 抽象出统一的节点/路由模型,不同格式只需适配 |
| 执行逻辑分散在各处,难以维护 | 所有执行逻辑收敛到原子操作 + 执行路径模型 |
| 并发分支、聚合、跳转逻辑复杂 | PVM 提供树形执行路径 + 原子操作队列,统一调度 |
| 状态管理混乱 | PVM 维护明确的状态机(6种状态,严格转换规则) |
二、PVM 的核心架构
2.1 整体分层
PVM 在 EKP 中的位置属于 引擎层 的核心,整体架构如下:
┌──────────────────────────────────────────────┐
│ 应用层 (Service) │
│ 发起流程、审批、驳回、加签、转办等业务操作 │
├──────────────────────────────────────────────┤
│ 引擎层 (Engine + PVM) │
│ ┌─────────────────────────────────────────┐ │
│ │ PVM(流程虚拟机) │ │
│ │ ┌─────────┐ ┌──────────┐ ┌─────────┐ │ │
│ │ │ 原子操作 │ │ 执行路径 │ │ 事件系统 │ │ │
│ │ │ 调度器 │ │ 状态管理 │ │ 监听分发 │ │ │
│ │ └─────────┘ └──────────┘ └─────────┘ │ │
│ └─────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────┐ │
│ │ 引擎层实现 (Engine Support) │ │
│ │ 节点行为、路由策略、持久化实现等 │ │
│ └─────────────────────────────────────────┘ │
├──────────────────────────────────────────────┤
│ 持久层 (DAO / ORM) │
│ 流程实例表、工作项表、执行路径表等 │
├──────────────────────────────────────────────┤
│ 基础层 (Common) │
│ Spring容器、事务管理、工具类等 │
└──────────────────────────────────────────────┘
2.2 三大核心组件
PVM 由三个紧密协作的核心组件构成:
| 组件 | 对应类/接口 | 职责 |
|---|---|---|
| 执行路径 | Execution / ExecutionWraper |
流程的"程序计数器",记录当前走到哪了 |
| 原子操作调度器 | AtomicOperation + ExecutionWraper 中的队列 |
将原子操作排队执行,支持异步队列和同步阻塞两种模式 |
| 事件系统 | EventExecutionWraper / EngineEventMulticaster |
在关键节点发布事件,驱动监听器链式执行 |
这三个组件的关系可以用一句话概括:
执行路径 是载体,原子操作调度器 是引擎,事件系统 是神经系统。
三、执行路径(Execution)------ 流程的"程序计数器"
3.1 概念类比
在操作系统中,每个线程都有一个程序计数器(Program Counter),指向当前正在执行的指令地址。
在 PVM 中,执行路径(Execution) 就是流程的"程序计数器"------它记录了:
-
当前在哪个活动(节点)
-
当前关联哪个任务(工作项)
-
当前处于什么状态
-
它的父执行路径是谁(树形结构)
-
它所属的流程实例
3.2 六种状态 ------ PVM 的状态机
Execution 接口定义了流程执行路径的6种核心状态:
状态转换图:
┌──────────┐
│ ACTIVE │ ← 执行路径被创建 / 收到 signal 信号
└────┬─────┘
│
┌────────────┼────────────┐
↓ ↓ ↓
┌──────────┐ ┌──────────┐ ┌──────────┐
│ WAITING │ │ ENDING │ │CONCURRENT│
│(等人审批) │ │(准备流转) │ │(并行分支) │
└────┬─────┘ └────┬─────┘ └────┬─────┘
│ │ │
↓ ↓ │
┌──────────┐ ┌──────────┐ │
│SUSPENDED │ │ ENDED │ ←────┘(所有子路径结束后)
│ (被挂起) │ │ (已结束) │
└──────────┘ └──────────┘
状态转换的典型场景:
-
ACTIVE → WAITING:自动节点执行完毕,进入人工节点,等待用户审批
-
ACTIVE → ENDING :调用了
take()方法,指定了后续路由 -
ACTIVE → CONCURRENT:遇到并行网关,分裂成多个子执行路径
-
WAITING → ACTIVE:用户完成审批,执行路径被重新激活
-
ENDING → ENDED:任务结束事件触发完成,执行路径标记为结束
3.3 树形执行路径模型
这是 PVM 设计中非常精妙的一点:执行路径是树形结构。
流程主执行路径 (ProcessExecution)
├── 子执行路径-分支A (并发分支1)
│ └── 子执行路径-分支A-1 (嵌套并发)
├── 子执行路径-分支B (并发分支2)
└── 子执行路径-分支C (并发分支3)
这个树形结构天然支持了嵌套并发------并行分支内部还可以继续分裂出子分支,形成了父子层级关系。当子分支全部完成后,父分支才能继续向前流转。
四、原子操作调度器 ------ PVM 的"指令执行单元"
4.1 原子操作接口回顾
// AtomicOperation.java
public interface AtomicOperation {
/** 启动任务操作 */
String TRANSITION_START_TASK = "TransitionStartTask";
/** 执行任务操作 */
String EXECUTE_TASK = "ExecuteTask";
/** 结束任务操作 */
String TRANSITION_END_TASK = "TransitionEndTask";
/** 移到父任务操作 */
String PROPAGATE_TO_PARENT = "MoveToParentTask";
/** 广播事件 */
String EXECUTE_EVENT_LISTENER = "ExecuteEventListener";
/** 执行操作 */
void propagate(ExecutionWraper execution);
/** 是否允许在队列中重复出现 */
boolean allowRepeat();
}
4.2 双队列调度机制
PVM 的原子操作调度采用双队列模型
双队列的设计意图:
| 队列类型 | 触发时机 | 特点 |
|---|---|---|
| 异步队列(COMMON) | 正常的流程流转 | 先进先出,排队执行,允许流程逐步推进 |
| 同步队列(SYNC) | 同步事件监听器触发 | 保存当前异步队列 → 立即执行同步操作 → 恢复异步队列 |
这种设计可以类比操作系统的中断处理:正常流程(异步队列)在执行中,突然来了一个必须立即处理的同步事件(如同步监听器),PVM 就"中断"当前队列,执行完同步操作后再"恢复现场"。
4.3 SyncAtomicOperation ------ 阻塞式原子操作
// SyncAtomicOperation.java
public interface SyncAtomicOperation extends AtomicOperation {
/**
* 不相同的执行路径范围,则直接触发当前原子操作;
* 相同的执行路径范围,则阻塞当前执行路径的队列,
* 触发当前原子操作,执行结束后重置为阻塞前的队列。
*
* @return 是否当前执行路径
*/
boolean isLocalScope();
}
SyncAtomicOperation 拓展了原子操作的能力------它要求立即同步执行,而不是排到队列尾部。典型实现:
-
MoveToChildTask------ 移动到子任务 -
AbandonTask------ 废弃任务 -
ExecuteSyncEventListener------ 执行同步事件监听器
五、事件系统 ------ PVM 的"神经系统"
5.1 事件驱动模型
PVM 采用事件驱动的方式串联整个执行过程。在关键节点上,PVM 会发布事件,然后由事件监听器链式响应。
5.2 事件驱动的原子操作链
PVM 中流程的推进本质上是**"事件 + 原子操作"的链式调用**。以"启动一个任务"为例:
TransitionStartTask
│
├─ 1. 设置执行路径状态为 ACTIVE
├─ 2. 关联目标任务
└─ 3. fire(EnterTaskEvent, task, new ActivateTask())
│
├─ 4. 发布 EnterTaskEvent → 触发所有监听器
└─ 5. 监听器执行完毕 → 调用 ActivateTask
│
└─ 6. fire(ActivateTaskEvent, task, EXECUTE_TASK)
│
├─ 7. 发布 ActivateTaskEvent
└─ 8. 调用 ExecuteTask
│
└─ 9. behaviour.execute(execution)
│
├─ 自动节点:执行业务逻辑 → 结束
└─ 人工节点:创建待办 → WAITING
5.3 任务结束与路由选择
当一个任务完成后,PVM 需要决定下一步往哪走。
路由决策矩阵:
| 条件 | 路由方向 | 含义 |
|---|---|---|
destination == null 且 parent == null |
End |
流程正常结束 |
destination == null 且 parent != null |
Parent |
回到父执行路径(子分支结束) |
destination 在当前活动范围内 |
Next |
继续往前走到下一个节点 |
destination 不在当前活动范围内 |
Parent |
跳回父路径再中转 |
| 特权跳转标记命中 | Jump |
管理员特权跳转 |
六、核心流程完整追踪
让我们用一个请假审批流程来追踪 PVM 的完整执行链路:
[开始] → [提交请假单] → [部门经理审批] → [结束]
6.1 启动流程
1. 应用层调用 startProcess()
2. 引擎层创建 ExecutionContextImpl
├── 创建流程主执行路径 (ProcessExecution)
├── 加载流程定义 (OpenProcessDefinition)
└── 设置流程参数 (Parameters)
3. 创建 ExecutionWraper 包裹执行路径
4. execution.start()
└── fire(ProcessStartEvent, null, TransitionStartProcess)
└── TransitionStartProcess.propagate()
└── 找到开始节点的默认出口路由
└── performAtomicOperation(TRANSITION_START_TASK)
6.2 进入"提交请假单"节点
5. TransitionStartTask.propagate()
├── setFdState(ACTIVE)
├── setActivity("提交请假单"节点)
└── fire(EnterTaskEvent) → ActivateTask
└── fire(ActivateTaskEvent) → ExecuteTask
└── behaviour.execute()
├── [自动节点] 执行业务逻辑后自动结束
│ └── performAtomicOperation(TRANSITION_END_TASK)
└── [人工节点] 创建待办工作项
└── setFdState(WAITING) ← 停下来等人处理
6.3 用户提交请假单
6. 应用层调用 completeWorkitem()
7. 引擎层找到对应的执行路径
8. execution.signal() ← 重新激活
└── Signal.propagate()
└── behaviour.signal() ← 节点收到信号
└── 执行审批通过逻辑
└── execution.takeDefaultTransition() ← 指定出口路由
└── setFdState(ENDING)
└── performAtomicOperation(TRANSITION_END_TASK)
6.4 流转到"部门经理审批"
9. TransitionEndTask.propagate()
├── getNextRoute() → Route.Next ("部门经理审批")
└── TransitionTake.propagate()
└── propagateToNext()
├── setActivity("部门经理审批"节点)
└── performAtomicOperation(TRANSITION_START_TASK)
└── [回到步骤5,循环往复]
6.5 流程结束
10. 最后一个节点完成
11. getNextRoute() → Route.End
12. TransitionTake.propagate()
└── fire(ProcessEndEvent, ..., new DestroyScope())
└── 清理所有执行路径
└── 流程实例标记为已完成
七、PVM 的设计精髓
7.1 分层解耦 ------ EngineWire 接口
PVM 层不关心流程定义怎么存、执行路径怎么持久化。它只通过 EngineWire 接口向引擎层"要东西":
这种设计的价值在于:你可以换掉整个引擎层实现(比如从关系数据库换到NoSQL),PVM 层一行代码都不用改。
7.2 原子操作 + 事件 = 可扩展的执行链
每个原子操作在执行前后都可以发布事件,事件监听器可以插入自定义逻辑。这形成了一条可插拔的执行链:
原子操作A
├── 前置事件 → [监听器1] → [监听器2] → ...
├── 核心逻辑
└── 后置事件 → [监听器3] → 下一个原子操作B
7.3 树形执行路径 = 天然的并发模型
并行分支就是创建子执行路径,聚合就是等待子执行路径完成。整个模型清晰且强大:
7.4 Operation 注册表 ------ 简单的工厂模式
通过字符串名称即可获取原子操作实例,原子操作之间通过名称引用(如 TRANSITION_START_TASK),形成了松耦合的操作网络。
八、本课小结
| 概念 | 一句话理解 |
|---|---|
| PVM | 流程引擎的CPU + 操作系统,读取定义、调度执行、管理状态 |
| 执行路径 | 流程的"程序计数器",树形结构,记录当前位置和状态 |
| 六种状态 | ACTIVE → WAITING/CONCURRENT/ENDING → ENDED,严格的状态机 |
| 双队列调度 | 异步队列(正常流转)+ 同步队列(事件中断),模拟中断处理 |
| 事件系统 | 在关键节点发布事件,驱动原子操作链式执行 |
| EngineWire | PVM与引擎层的解耦契约,更换持久化实现不影响PVM |
| 树形执行路径 | 天然的并发模型,分支=子路径,聚合=子路径结束 |