工作流(2)——工作流引擎的底层架构:从Token到数据库的精密运转

引言

在上一篇中,我们从硬编码的痛点出发,明白了工作流引擎的核心价值是解耦,并抽象出了"流程定义(地图)"、"流程实例(火车)"和"任务(车站)"三大基础概念。

但是,这仅仅是外壳。

作为技术人,我们最关心的是:这台引擎的"发动机"到底是怎么转起来的?

试想一个场景:主管王五在请假系统里点了一下"同意"按钮,前端发了一个请求给后端。

此时,工作流引擎内部究竟发生了什么?它怎么知道下一步该去哪?如果流转到一半服务器突然停电宕机了,数据会乱吗?

今天,我们就扒开工作流引擎的底层架构,看看它是如何精密运转的。

1 工作流引擎的底层架构:从Token到数据库的精密运转

当王五点击"同意"时,他其实是调用了引擎的一个类似 completeTask(taskId) 的方法。

此时,引擎面临的首要问题是:当前节点结束了,下一步该去哪?

1.1 图的遍历与"Token(令牌)"机制

所有的工作流地图(BPMN XML),在引擎内部最终都会被解析成一个数据结构:有向图(Directed Graph)

为了在这个图里"走路",工作流引擎引入了一个极其核心、也是最精妙的概念:Token(令牌)
在一些开源框架(如Flowable/Camunda)的源码中,它被称为 Execution(执行实例)。

你可以把 Token 想象成一个在迷宫里探险的小人

  1. 当流程启动时,引擎在"开始节点"生成一个 Token
  2. Token 顺着连线(Sequence Flow)走到"主管审批"节点,停下来,并生成一个待办任务给王五。此时 Token 处于休眠状态
  3. 王五点击"同意",唤醒了 Token
  4. 引擎拿到当前 Token 所在的节点,读取流程定义的"图",发现只有一条出线(Outgoing)指向"HR审批"
  5. 于是引擎把 Token 移动到"HR审批"节点,再次休眠,并生成 HR 的待办任务

1.2 遇到分叉路口怎么办?(网关与Token分裂)

如果是单线流程,一个 Token 就够了。但真实的业务充满了并行和分支。

1.2.1 排他网关(Exclusive Gateway)------ 走哪条路?

类似于代码里的 if-else。Token 走到网关时,引擎会计算连线上的条件(比如 days > 3)。一旦某条线的条件满足,Token 就顺着那条线走,其他线直接忽略。

1.2.2 并行网关(Parallel Gateway)------ 影分身之术!

比如一个新员工入职流程,需要"IT部发电脑"和"行政部发工牌"同时进行

这个时候,奇迹出现了:

当主 Token 走到并行网关时,它会分裂(Fork)

主 Token 停在原地,分裂出两个子 Token(Token-A 和 Token-B),分别飞向 IT部 和 行政部节点。

此时,系统里同时存在两个待办任务,互不阻塞。

那什么时候合流呢?

当 IT部 和 行政部 都处理完后,Token-A 和 Token-B 会走到"并行合并网关"。引擎会检查:所有分支的 Token 都到了吗?

如果 Token-A 先到,它就停在那里等。直到 Token-B 也到了,引擎把它们俩销毁,唤醒主 Token 继续往下走。

这就是工作流引擎能轻松处理复杂并发的底层秘密! 业务代码里根本不需要写多线程和锁,引擎用 Token 的分裂与合并完美解决了。

2 核心问题二:服务器宕机了怎么办?(持久化机制)

前面说的 Token 走来走去,听起来很像是在内存里运行的。

但请假流程动辄几天、几周,如果 Token 放在内存里,服务器一重启,张三的请假单不就灰飞烟灭了吗?

这就引出了工作流引擎的第二个核心:它本质上是一个由关系型数据库(MySQL/Oracle)强力驱动的状态机。

所有的引擎(无论是Activiti、Flowable还是Camunda),底层都有几十张精心设计的数据库表。为了不让你晕,我们把它们分为三大阵营

2.1 静态定义表(Repository)

这里存的是"地图"。你画好的 XML 文件、流程的名字、版本号,都存在这里。这些数据一旦部署,基本上是只读的。

2.2 运行时表(Runtime)------ 绝对的核心!

这里存的是"正在跑的火车和当前的车站"。

刚才说的 Token(执行实例)待办任务(Task),就存在运行时表里。

  • 引擎的极速秘诀 :运行时表的数据量永远是很小的。当一个任务被完成后,引擎会立刻从运行时表中物理删除这条记录。这样保证了引擎在查询"我的待办任务"时,速度永远极快。

2.3 历史表(History)------ 审计的基石

既然运行时表会被删,那怎么查流程的流转记录呢?

答案是历史表。引擎在把数据写入/删除运行时表的同时,会往历史表里 INSERT 一份快照。这里记录了流程从头到尾的所有足迹,用于业务系统的"审批历史轨迹"展示。

2.4 宕机如何保证数据一致?(事务绑定)

回到王五点击"同意"的瞬间。引擎在底层其实执行了类似如下的 SQL 逻辑,并且全部包裹在一个数据库事务(Transaction)中

sql 复制代码
BEGIN TRANSACTION;
-- 1. 更新 Token 的位置到下一个节点
UPDATE ACT_RU_EXECUTION SET ACT_ID_ = 'HR_Approval' WHERE ID_ = 'token_123';

-- 2. 删除王五当前的待办任务
DELETE FROM ACT_RU_TASK WHERE ID_ = 'task_456';

-- 3. 插入下一个 HR 的待办任务
INSERT INTO ACT_RU_TASK (ID_, NAME_, ASSIGNEE_) VALUES ('task_789', 'HR审批', 'hr_user');

-- 4. 记录历史操作日志
INSERT INTO ACT_HI_ACTINST ... 
COMMIT;

如果执行到第3步时服务器突然断电,数据库事务会回滚。

重启后,数据库里依然是王五的任务,Token 还在原来的位置。一点数据都不会丢,完美的一致性!

3 核心问题三:引擎如何与我们的业务数据绑定?

现在引擎内部转得很欢快了,但暴露出了最后一个致命问题:
引擎是个瞎子。

它只知道:"流程实例 1001 走到了任务 5002,处理人是 张三"。

但它根本不知道:"这是谁的请假单?请了几天?理由是什么?"

如果不解决这个问题,业务系统和引擎就是割裂的。为了搭建这两座桥梁,引擎提供了两个极其重要的机制:

3.1 业务键(Business Key)------ 身份的绑定

当你的业务系统往 leave_request 表插入一条请假单记录(假设主键 ID 是 9527)后,你在启动工作流时,必须把这个 9527 告诉引擎。

java 复制代码
// 伪代码:启动流程,并传入 Business Key
engine.startProcessInstanceByKey("leaveProcess", "9527");

引擎会把 9527 存到自己的流程实例表中。

以后,当张三打开"我的待办"列表时,引擎查出了一堆 Task,前端拿到 Task 对应的 Business Key(9527),再去你的业务表里 SELECT * FROM leave_request WHERE id = 9527,就能把请假详情展示出来了。

3.2 流程变量(Process Variables)------ 决策的依据

前面说到,排他网关需要根据 days > 3 来判断走哪条路。引擎怎么知道 days 是多少?

你需要在流程流转的过程中,把业务数据作为**变量(Variables)**塞进引擎里。

java 复制代码
Map<String, Object> variables = new HashMap<>();
variables.put("days", 5);
variables.put("applicant", "zhangsan");

// 完成任务,并把变量告诉引擎
engine.completeTask(taskId, variables);

引擎会把这些变量序列化后存入数据库(甚至有专门的变量表)。当 Token 走到网关时,引擎内置的表达式解析器(比如 JUEL 表达式)就会取出 days 的值进行计算,从而决定 Token 的走向。

4 总结与思考

至此,我们彻底看清了工作流引擎运转的底层逻辑:

  1. 执行机制 :将流程图转化为有向图,利用 Token(令牌) 的移动、分裂、合并来驱动流程流转
  2. 持久化与一致性 :依托关系型数据库本地事务,通过运行时表和历史表的读写分离,保证了引擎的高性能和抗宕机能力
  3. 业务融合 :通过 Business Key 绑定业务数据,通过 流程变量 驱动网关路由,实现了引擎与业务系统"松耦合"的完美配合

然而,现实总是残酷的。

以上讲的,都是理想状态下的"正向流转"。

但在中国特色的企业管理中,领导们的要求往往千奇百怪:

  • "这个节点我要加个人一起审批!"(动态加签
  • "我点错了,我要把流程退回到上一步!"(驳回
  • "我要把流程直接退回到发起人!"(任意节点跳转
  • "我们三个人审批,只要有两个人同意就算过!"(复杂会签

BPMN 2.0 规范主要源自西方,很多中国特色的复杂流转逻辑,原生引擎根本不支持!

在下一篇**《工作流引擎系列(三):中国式复杂工作流的破局与实战》**中,我们将直面这些令人头秃的"变态需求",教你如何基于开源引擎进行改造和扩展,真正将工作流引擎落地到复杂的企业级项目中。

相关推荐
剑飞的编程思维2 小时前
架构评审核心维度自查表
架构·个人开发·代码复审
roman_日积跬步-终至千里2 小时前
【案例题】知识点考试思路(基于新版考纲)
架构
weisian1512 小时前
进阶篇-LangChain篇-15--高级Agent架构—复杂任务拆解(Plan-and-Execute架构)和多智能体协作(LangGraph)
java·架构·langchain·langgraph·planexecute架构
尚雷55802 小时前
Oracle 核心体系架构学习系列一:从内存、进程到磁盘的底层逻辑学习
学习·oracle·架构
heimeiyingwang2 小时前
【架构实战】边缘计算架构设计与应用场景
人工智能·架构·边缘计算
喜欢流萤吖~2 小时前
微服务的统一大门:SpringCloud Gateway
微服务·云原生·架构
雪碧聊技术2 小时前
微服务实战:彻底解决子项目找不到父项目工具类、实体类的问题
微服务·云原生·架构
前端不太难2 小时前
鸿蒙游戏如何设计可扩展架构?
游戏·架构·harmonyos