工作流引擎全景与选型

本文给出一张工作流引擎的全景地图,回答 BPMN、持久化执行、数据编排、状态机四类引擎各自解决什么问题、什么场景该选哪一类。覆盖 Camunda、Temporal、Airflow、Dagster、Step Functions 的定位差异,给出七维度对比表、吞吐量级参考与选型决策树,并用同一个订单流程的三种实现对比代码形态与运维成本。

引言

只要系统里出现「多个步骤按某种顺序执行、中间要等待、失败要回滚」的需求,你就已经在写工作流引擎了。区别只在于这个引擎是你自己用状态字段和定时任务拼出来的,还是用一个成熟引擎托管起来的。自研的代价通常在第三个月显现:状态分支爆炸、超时无人处理、运维想看某个实例卡在哪一步却只能翻日志。

工作流引擎解决的核心问题有三个:把流程定义从代码里抽出来(可读、可改、可审计),把执行状态持久化下来(进程挂了流程不死),把编排的横切关注点收敛到引擎(重试、超时、补偿、并发控制、人工任务)。不同引擎对这三个问题的回答方式差别极大,这也是选型真正的分歧点。

选型讨论里最常见的错误是拿功能清单做对比。「支持定时」「支持重试」「支持可视化」几乎每款引擎都打勾,但语义完全不同:Camunda 的定时是 BPMN Timer 事件,绑定在流程模型上;Temporal 的定时是代码里的 Workflow.sleep,绑定在执行上下文里;Airflow 的定时是调度周期,绑定在 DAG 上。同样是「定时」,三者的可测试性、可观测性、失败行为天差地别。

本文先按执行模型把引擎分成四类,再逐类给出代表产品与适用边界,然后用七个维度做横向对比,给出决策树与评估清单,最后用同一个订单流程在三种引擎里的写法做对照,让抽象差异变成可触摸的代码差异。

目录

  1. 工作流引擎的准确定义与边界
  2. 什么时候该上引擎
  3. 四类执行模型的分野
  4. BPMN 系:Camunda、Flowable 与 Activiti
  5. 持久化执行系:Temporal、Cadence 与 Restate
  6. 数据编排系:Airflow、Dagster 与 Prefect
  7. 云托管工作流:Step Functions 与 Cloudflare Workflows
  8. 轻量状态机与自研方案
  9. 核心概念对照表
  10. 七个维度的横向对比
  11. 吞吐与延迟的量级参考
  12. 选型决策树
  13. 一份可执行的评估清单
  14. 与消息中间件、分布式理论的边界
  15. 同一个订单流程的三种写法
  16. 混合架构:两类引擎协同
  17. 迁移与共存策略
  18. 团队与运维成本
  19. 成本模型与容量规划
  20. 落地路线图
  21. 权衡取舍
  22. 常见坑清单
  23. 小结

1. 工作流引擎的准确定义与边界

工作流引擎是一套「流程定义 + 执行器 + 状态存储 + 观测界面」的组合。流程定义描述步骤与流转条件,执行器负责推进实例,状态存储保证崩溃恢复,观测界面让你看到每个实例当前在哪一步。四者缺一,就会退化成脚本调度器或状态表。

需要划清三条边界。第一,工作流引擎不是消息中间件:Kafka 负责事件传输与解耦,工作流引擎负责多步骤的时序与状态,两者经常一起用,参见 事件驱动架构 里的编排与协同之争。第二,工作流引擎不是定时任务框架:cron 只知道「几点执行」,不知道「上次没跑完怎么办」。第三,工作流引擎不是业务规则引擎:规则引擎回答「这个订单该不该人工审核」,工作流引擎回答「审核通过之后往哪走」,两者在 规则引擎与决策表 中会详细区分。

还有一条容易被忽略的边界:工作流引擎不是分布式事务协调器。它能让「等待」「重试」「补偿」变得可管理,但不会替你把跨服务的原子性变出来。跨服务的最终一致性仍要靠幂等与补偿设计,这是 Saga 与分布式事务补偿 的内容。

2. 什么时候该上引擎

引入引擎是有成本的:新增一个需要运维的组件、新增一门需要学习的 DSL、新增一层排查问题时要穿透的抽象。所以第一步不是选型,而是判断该不该上。

下面五条只要命中两条,引入引擎的收益通常就超过成本:

  • 流程步骤数会随时间增长,且增长由业务方驱动而非工程方。
  • 存在跨小时的等待(等支付回调、等审批、等对账窗口)。
  • 失败时需要反向操作(退款、释放库存、撤销额度)。
  • 需要审计轨迹,能回答「这单是谁在什么时候推进到哪一步」。
  • 同一个流程有多个变体(不同地区、不同客户等级走不同分支)。

反过来,下面这些情况不建议上引擎:

  • 步骤少于 5 个、全同步、失败直接返回错误码。
  • 流程只在单个数据库事务内完成,没有外部副作用。
  • 团队规模小于 5 人且没有平台工程人力,流程本身也不是公司核心资产。

一个务实的中间态是「先自研状态表,留好扩展点」。把状态定义成枚举、把流转写成显式的 transition(from, to, event) 校验函数、把每次流转记录成事件行。这样将来迁移到引擎时,历史事件可以直接导入,状态机定义可以翻译成 BPMN 或 Workflow 代码。

3. 四类执行模型的分野

按「流程定义存在哪里、由谁解释执行」,主流引擎分成四类:

类别流程定义形态执行方式典型产品
BPMN 系XML 图形化模型引擎解释令牌推进Camunda、Flowable、Activiti
持久化执行系普通编程语言代码事件溯源重放Temporal、Cadence、Restate、DBOS
数据编排系Python 代码声明的 DAG调度器按依赖触发Airflow、Dagster、Prefect
轻量状态机表结构或状态枚举应用内条件分支Spring StateMachine、XState、AWS Step Functions

这四类的差异不是「谁更先进」,而是「谁把复杂度放在哪」。BPMN 把复杂度放在建模语言上,换来业务方可读的流程图;持久化执行把复杂度放在确定性重放上,换来开发者可以直接写 if-else;数据编排把复杂度放在调度器上,换来对时间维度(补数、回填、分区)的原生支持;状态机把复杂度放在状态设计上,换来极致的可预测性。

另一个观察角度是「谁拥有控制流」。BPMN 与状态机里控制流在模型里,代码是被模型调用的仆从;持久化执行里控制流在代码里,引擎只是记录器;数据编排里控制流在调度器里,任务是独立的黑盒。这个视角能解释很多现象,比如为什么 Airflow 里做条件分支那么别扭(控制流不在代码里),为什么 Camunda 里写循环那么别扭(控制流不在代码里)。

选错类别的代价很高:用 Airflow 做需要人工审批的订单流程,你会发现它没有「等人」这个原语;用 Temporal 做每天凌晨批量跑 5000 个 SQL,你会发现它的调度能力远不如 Airflow。

4. BPMN 系:Camunda、Flowable 与 Activiti

BPMN 2.0 是 OMG 制定的流程建模标准,用图形符号表达事件、任务、网关、子流程。它的最大价值是「业务分析师能看懂并且能改」,这在审批流、保险理赔、银行开户这类流程规则由业务方主导的场景里是决定性优势。

Camunda 是目前社区最活跃的实现,7.x 是嵌入式引擎(流程定义存数据库,Java 应用内嵌引擎),8.x 是云原生的 Zeebe(流程定义存日志流,独立网关进程)。Flowable 是 Activiti 分叉而来,在国内审批流项目里存量很大,中文资料多,它的表单引擎与多实例会签实现比 Camunda 更「开箱即用」。Activiti 5/6 已基本停止演进,新项目不建议。

选 BPMN 系要先接受它的两个现实。第一,图形化不等于简单:BPMN 的元素超过 40 个,网关就有排他、并行、包容、事件四类,加上边界事件、补偿事件、多实例,很容易写出业务方和你都看不懂的图。第二,图形化意味着流程变更需要走「建模 → 评审 → 部署」的流程,比改一行代码重,所以适合稳定的、低频变更的核心流程。

实践经验是约束自己只用 15 个左右的元素子集,把复杂逻辑推到外部服务里。Camunda 官方也建议把 ServiceTask 的粒度控制在「一次远程调用」级别,不要在一个节点里做十件事。元素子集的取舍细节见 BPMN 2.0 与 Camunda 实战 。

5. 持久化执行系:Temporal、Cadence 与 Restate

持久化执行(Durable Execution)的核心思想是:你写的就是普通函数,但引擎会把函数的每一次副作用(调用外部 API、发消息、设置定时器)记录成事件,进程崩溃后重放事件流,把函数恢复到崩溃前的状态。这样「代码即流程」,不需要画图,也不需要学新语言。

Temporal 是这一派的代表,源自 Uber Cadence。它的三个原语是 Workflow(确定性编排逻辑)、Activity(副作用执行单元)、Worker(拉取任务的进程)。好处非常直接:你可以写一个 for 循环、sleep(30天)、try/catch,引擎保证它跑完。重试、超时、心跳、版本兼容都由引擎处理。

// 一个跨越 30 天的编排,在 Temporal 里就是普通代码
@Override
public void execute(OrderInput input) {
    activities.lockStock(input.orderId());
    try {
        activities.charge(input.orderId(), input.amount());
    } catch (ActivityFailure e) {
        activities.unlockStock(input.orderId());
        throw e;
    }
    // 最长等 30 天,期间可以收到信号提前唤醒
    boolean shipped = Workflow.await(Duration.ofDays(30), () -> shipped);
    if (!shipped) {
        activities.refund(input.orderId());
    }
}

代价是确定性约束:Workflow 代码里不能有随机数、当前时间、直接 I/O,必须走 Workflow.randomUUID()、Workflow.currentTimeMillis()、Activity。这会让不熟悉的人写出「本地跑得好、线上重放报错」的代码,排查这类问题需要理解事件历史。

Restate 是这一派的新玩家,用日志做持久化,支持无状态函数编程模型,把「持久化」下沉到 SDK 与日志层,不需要独立 Worker 概念。DBOS 走的是另一条路:把状态直接存进 PostgreSQL,用数据库事务保证持久化。这类「数据库即引擎」的方案部署简单,适合已有重度 PostgreSQL 依赖的团队。详细的执行语义与版本兼容策略见 Temporal 与持久化执行 。

6. 数据编排系:Airflow、Dagster 与 Prefect

数据编排引擎面向的是「按时间或数据到达触发的一批计算任务」。它的核心原语是 DAG 与调度,天然支持补数(backfill)、幂等重跑、按分区增量处理,这些在 BPMN 和 Temporal 里都要自己造。

Airflow 是事实标准,2.x 引入 TaskFlow API 让 DAG 写起来像 Python,2.9 之后引入 Asset(原 Dataset)做数据驱动调度,3.0 把调度器性能提了一个量级。它的短板是调度延迟(默认最小 1 分钟粒度)与 DAG 解析开销,以及「DAG 文件即配置」带来的版本管理困难。

Dagster 用「软件定义资产」把编排单位从「任务」换成「数据资产」,能回答「这张表是谁产的、上游变了要不要重跑」。它的类型系统(Dagster Types)在 DAG 边界做数据校验,比 Airflow 的 XCom 裸传 JSON 严谨得多。Prefect 2/3 主打动态工作流与更友好的开发者体验,支持运行时动态生成任务,适合「任务数量在运行时才知道」的场景。

# Airflow 3.0 的资产驱动调度写法
import airflow.sdk as sdk

@sdk.asset(schedule="@daily")
def raw_orders(): ...

@sdk.asset
def clean_orders(raw_orders): ...

三者的详细对比见 Airflow DAG 调度体系 与 Dagster 与 Prefect 数据编排 。选型上有一条经验:如果团队里做编排的人是数据分析师而不是后端工程师,Dagster 的资产模型心智负担更低;如果需要最多现成的 Operator 与云服务集成,Airflow 的生态优势很难被替代。

7. 云托管工作流:Step Functions 与 Cloudflare Workflows

云厂商的托管工作流把运维成本降到零,代价是锁定与表达能力受限。AWS Step Functions 用 Amazon States Language(ASL)描述状态机,原生集成 Lambda、ECS、DynamoDB,支持 Standard(最长 1 年、恰好一次)与 Express(最长 5 分钟、至少一次、高吞吐)两种模式,按状态转换次数计费。

{
  "StartAt": "LockStock",
  "States": {
    "LockStock": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:cn-north-1:123:function:lockStock",
      "Retry": [{"ErrorEquals": ["States.TaskFailed"], "MaxAttempts": 3,
                 "BackoffRate": 2.0}],
      "Catch": [{"ErrorEquals": ["States.ALL"], "Next": "FailFlow"}],
      "Next": "Charge"
    },
    "Charge": {"Type": "Task", "Resource": "arn:aws:states:::lambda:invoke",
               "Next": "WaitShip"},
    "WaitShip": {"Type": "Wait", "Seconds": 1800, "Next": "Refund"},
    "Refund": {"Type": "Task", "Resource": "arn:aws:states:::lambda:invoke",
               "End": true},
    "FailFlow": {"Type": "Fail", "Error": "OrderFailed"}
  }
}

Cloudflare Workflows 是 2024 年推出的边缘持久化工作流,基于 Durable Objects,支持 step.do、step.sleep、step.waitForEvent,按 CPU 时间计费。它的适用场景是「跟用户会话绑定的短流程」,比如订阅续费、注册引导、多步骤表单,而不是企业级审批流。

选云托管的前提是:流程逻辑简单、能接受厂商锁定、团队没有专职的平台工程人力。一旦流程需要复杂的人工任务、跨云集成、或者按业务方需求频繁改图,托管方案就会变成枷锁,因为迁出时你要重写所有状态定义与重试策略。

8. 轻量状态机与自研方案

如果流程的状态不超过 10 个、流转条件稳定、不需要可视化,那么一个状态机库甚至一张状态表就够了。Spring StateMachine 提供状态、事件、转移、守卫、动作的完整抽象;XState 在前端做同样的工作;最朴素的做法是在数据库里放 status 字段加一张 state_transition 表,用乐观锁保证并发安全。

CREATE TABLE order_state (
  order_id   VARCHAR(64) PRIMARY KEY,
  status     VARCHAR(32) NOT NULL,
  version    BIGINT      NOT NULL DEFAULT 0,
  updated_at TIMESTAMP   NOT NULL DEFAULT CURRENT_TIMESTAMP
);

-- 带前置状态校验的乐观更新,返回 0 行表示并发冲突
UPDATE order_state
   SET status = 'PAID', version = version + 1, updated_at = NOW()
 WHERE order_id = ? AND status = 'PENDING' AND version = ?;

自研的合理边界是:步骤少于 5 个、没有跨天等待、没有人工介入、失败只需返回错误。一旦出现「等支付回调」「等人工审批」「失败要反向补偿」中的任意一条,自研成本就会指数上升,因为你要开始处理超时扫描、状态对账、重复执行。

自研还有一个隐性成本:可观测性要自己搭。引擎自带实例列表、当前节点、耗时分布,自研方案里这些都要自己写 SQL 拼。当运维问「昨天有多少单卡在待支付」时,你得临时写查询。状态机引擎的完整讨论见 状态机引擎与状态流转 。

9. 核心概念对照表

跨引擎沟通时最容易混淆的是术语。下表把同一件事在四种引擎里的叫法对齐:

概念BPMN / CamundaTemporalAirflowStep Functions
流程定义Process DefinitionWorkflow DefinitionDAGState Machine
一次执行Process InstanceWorkflow ExecutionDagRunExecution
步骤Task / ActivityActivityTaskState
执行者Job ExecutorWorkerExecutor / Worker托管
等待人工User TaskSignal无原生支持回调 Token
定时等待Timer EventTimer / SleepSensorWait
分支Gatewayif-elseBranchOperatorChoice
循环MultiInstancefor 循环Dynamic Task MappingMap
子流程Call ActivityChild WorkflowSubDAG / TaskGroup嵌套状态机
状态查询REST + ACT_RU 表Describe + 可见性库元数据库DescribeExecution
重试配置在节点上RetryPolicyretries 参数Retry 字段
补偿CompensateEvent手写补偿无Catch + 反向状态

把这张表记住,跨引擎讨论时就不会各说各话。它也能帮你快速判断一个需求在某引擎里有没有原生支撑。

10. 七个维度的横向对比

维度Camunda 7TemporalAirflowStep Functions
流程定义BPMN XMLJava/Go/Python 代码Python DAGASL JSON
持久化关系库(ACT_ 表)事件历史 + 可见性库元数据库 + 调度器云托管
人工任务原生支持需自建 Signal 机制无需自建回调
补偿语义BPMN 补偿事件手写补偿 Activity无手写 Catch 分支
定时与延迟Timer 事件Timer + Sleep调度周期Wait 状态
可视化Modeler + CockpitWeb UI + 事件历史Grid + Gantt控制台图形
最小调度粒度秒级毫秒级分钟级秒级
单实例最长生命周期无硬限制无硬限制单次运行受限于超时配置1 年(Standard)
版本兼容机制部署新版本,老实例走老定义Worker 版本与 GetVersionDAG 文件覆盖,历史运行不变状态机别名与版本

注意吞吐量级是数量级参考,实际取决于部署形态。Temporal 的吞吐来自无状态 Worker 水平扩展,Camunda 7 的吞吐受关系库写入限制,Camunda 8 用 Zeebe 的分区日志把这块瓶颈解开了。

11. 吞吐与延迟的量级参考

选型时最常被忽略的是量级:一个每秒 100 笔的订单系统和每天 500 个批处理任务,对引擎的压力完全不同。

场景实例量级推荐引擎关键约束
电商下单编排100 到 1000 TPSTemporal / Camunda 8状态写入吞吐、实例生命周期短
企业审批流每天 1 万到 10 万Camunda 7 / Flowable人工任务查询、历史表膨胀
数据 ETL 调度每天 1000 到 10 万个任务Airflow / Dagster调度延迟、并发槽位
IoT 事件处理每秒 1 万以上不用工作流引擎,用流处理引擎的状态存储扛不住
边缘会话流程每天百万级短流程Cloudflare Workflows单次 CPU 时间预算

一条重要判断:实例生命周期越短、QPS 越高,越不该用基于关系库的引擎。因为每个实例都要写状态、写历史,短生命周期实例的写入放大最严重。这类场景要么用日志型引擎(Zeebe、Temporal),要么根本不该用工作流引擎,而应该用流处理或事件溯源。

12. 选型决策树

按下面的顺序问自己四个问题,通常三分钟内能收敛:

  1. 流程规则是否由业务方主导并需要他们能看懂、能改?是则选 BPMN 系。
  2. 流程是否包含大量人工审批、会签、加签、表单?是则选 BPMN 系,Temporal 在这块要自建一套。
  3. 流程是否以「时间周期 + 数据分区」为核心,需要补数、回填、按分区重跑?是则选数据编排系。
  4. 流程是否是纯服务编排(调 API、发消息、等回调),团队是工程师主导?是则选持久化执行系。

如果四个问题都答「否」,流程又很短,那么不要上引擎,直接写代码加状态字段。

反过来,如果同时命中两类(比如既有人工审批又有海量 API 编排),常见做法是分层:用 Camunda 做「人机交互的外层」,把真正的服务编排下沉给 Temporal,两层之间用消息或 RPC 交互。不要试图让一个引擎同时满足两种诉求。

决策树上还有一个分支常被忽略:如果流程是「一次性数据迁移」「运维脚本编排」,那么答案可能是 Argo Workflows 或 Tekton 这类 K8s 原生工作流,它们把每个步骤跑成 Pod,天然获得资源隔离与镜像环境。

13. 一份可执行的评估清单

选型评审时,把下面 12 个问题逐条过一遍,能挡住大部分事后返工:

[ ] 崩溃恢复:杀掉 Worker 后,实例能否自动从断点继续?
[ ] 幂等边界:引擎保证的是至少一次还是恰好一次?副作用如何幂等?
[ ] 版本兼容:流程定义变更后,运行中的老实例如何处理?
[ ] 人工任务:是否有原生的任务分配、认领、转办、超时?
[ ] 补偿机制:回滚是引擎原生还是业务自建?顺序如何保证?
[ ] 观测能力:能否按 businessKey 查到当前节点与完整历史?
[ ] 调度粒度:最小调度间隔是多少?是否满足延迟要求?
[ ] 吞吐上限:单集群能跑多少实例/秒?扩容方式是水平还是垂直?
[ ] 状态存储:用什么数据库?备份与恢复策略是什么?
[ ] 多语言:团队是否有非 JVM / 非 Python 的服务需要参与?
[ ] 升级路径:大版本升级是否需要停机?有没有官方迁移工具?
[ ] 退出成本:如果两年后要换引擎,数据和流程定义能导出吗?

其中「退出成本」最容易被跳过,但它决定了你未来两年的谈判地位。能导出 BPMN XML 或事件历史的引擎,迁移成本可控;流程定义存在私有二进制格式里的引擎,一旦绑定就很难脱身。

14. 与消息中间件、分布式理论的边界

工作流引擎站在消息中间件之上。Kafka 提供至少一次的投递与分区有序,工作流引擎在其上提供「多步骤的时序保证」与「状态可查询」。典型组合是:Kafka 承载领域事件,工作流引擎消费事件推进实例,实例状态变化再发回 Kafka。这个模式在 Kafka 生产者 里提到的幂等生产者与事务配置是前提。

分布式理论层面,工作流引擎把「一致性」这个难题收敛到了自己的状态存储里。引擎内部通常用事件溯源 + 乐观锁保证单个实例的状态一致,用分区键保证同一实例路由到同一节点。跨实例的一致性仍然要业务自己处理,这正是 Saga 与分布式事务补偿 的主题。

一个常见的架构误区是把引擎当成「万能协调器」,让所有服务都通过引擎通信。这会把引擎变成单点瓶颈与耦合中心。健康的用法是:引擎只在需要「跨多步的时序保证」时出现,其余的服务间通信直接走消息或 RPC。判断标准是「这段逻辑是否需要知道上一步的结果才能决定下一步」,需要才编排。

15. 同一个订单流程的三种写法

流程需求:下单后扣库存、扣款、通知,任一失败则回滚,扣款后若 30 分钟未发货则自动取消。

BPMN 写法用 XML 声明,可视化是免费的:

StartEvent -> ServiceTask(lockStock)
           -> ServiceTask(chargePayment)
           -> BoundaryTimerEvent(PT30M) -> CancelFlow
           -> ServiceTask(notify)
           -> EndEvent

Temporal 写法用代码表达,控制流就是语言本身:

public void orderWorkflow(OrderInput input) {
    String orderId = input.orderId();
    Saga saga = new Saga(new Saga.Options.Builder().build());
    saga.addCompensation(activities::unlockStock, orderId);
    activities.lockStock(orderId);
    saga.addCompensation(activities::refund, orderId);
    activities.chargePayment(orderId, input.amount());

    boolean shipped = Workflow.await(Duration.ofMinutes(30), () -> this.shipped);
    if (!shipped) {
        saga.compensate();
        return;
    }
    activities.notifyUser(orderId);
}

Airflow 写法则是把每一步做成任务,用调度周期驱动:

@dag(schedule="*/5 * * * *", catchup=False, max_active_runs=1)
def order_flow():
    lock = PythonOperator(task_id="lock_stock", python_callable=lock_stock)
    charge = PythonOperator(task_id="charge", python_callable=charge)
    timeout = BranchPythonOperator(task_id="check_shipped",
                                   python_callable=is_shipped)
    lock >> charge >> timeout

三种写法的可读性差异一目了然:BPMN 对业务方最友好但代码化程度最低,Temporal 对工程师最友好但可视化弱,Airflow 适合周期性批量而不适合事件驱动的单笔流程。注意 Airflow 版本里 30 分钟等待被「每 5 分钟轮询一次」替代了,这就是把事件驱动硬塞进调度模型的典型代价。

16. 混合架构:两类引擎协同

真实系统里很少只用一个引擎。最常见的组合是「BPMN 管人、Temporal 管机器」:

  • Camunda 承载审批流,用户任务负责收集人的决策。
  • 审批通过后,Camunda 的服务任务发起一个 Temporal Workflow,然后等待其完成。
  • Temporal 负责后续的多步骤服务编排,包括重试、超时、补偿。
  • Temporal 完成后通过消息通知 Camunda,流程继续。

两个引擎之间的连接点要设计成幂等的:Camunda 侧用 businessKey 保证不重复发起,Temporal 侧用 WorkflowId 做去重(Temporal 的 WorkflowId 默认唯一,重复启动会返回已存在的实例)。这种「双引擎」架构的复杂度主要在两个状态存储的对账上,建议加一个每日对账任务,找出「Camunda 认为在等、Temporal 认为已完成」的悬挂实例。

另一种组合是「Airflow 管调度、Temporal 管单笔」。比如每天凌晨 Airflow 触发一批数据管道,其中「调用外部 API 并保证最终成功」的部分交给 Temporal,Airflow 只负责发起与等待。

17. 迁移与共存策略

实际项目里很少从零选型,更多是「已有自研状态表,想迁移」。建议采用绞杀者模式:新流程走引擎,老流程保持不动,用防腐层把老流程的完成事件转成引擎的启动信号。这样迁移风险被限制在单个流程内。

引擎之间迁移则更难,尤其是 BPMN 到 Temporal 这类跨范式迁移。可行做法是把 BPMN 里的 ServiceTask 逐个翻译成 Activity,把网关翻译成 if-else,把边界定时器翻译成 Workflow.await 加超时。人工任务是最难翻译的部分,通常保留在 BPMN 引擎里,用 Signal 从 Temporal 侧触发。

无论哪种迁移,都要保证「同一业务单号在迁移期只会被一个引擎处理」,通常用一个路由表加灰度开关实现:

CREATE TABLE workflow_router (
  biz_type   VARCHAR(32) PRIMARY KEY,
  engine     VARCHAR(16) NOT NULL,   -- 'camunda' | 'temporal' | 'legacy'
  gray_pct   INT         NOT NULL DEFAULT 0,
  updated_at TIMESTAMP   NOT NULL DEFAULT CURRENT_TIMESTAMP
);

迁移期还要准备「双读」能力:运维查实例时先查新引擎,查不到再查老系统。这个查询入口应该收敛到一个内部页面上,而不是让每个人记住两套查询命令。

18. 团队与运维成本

选型时要算人力账。BPMN 引擎需要有人维护流程版本与模型规范,Camunda 7 升级到 8 是一次架构级改造;Temporal 需要有人维护 Cassandra/PostgreSQL 集群与 Worker 容量规划;Airflow 需要有人盯着调度延迟与 DAG 解析耗时。

一个粗略的经验值:单引擎的专职运维人力大约 0.5 人年起。如果团队规模小于 5 个后端,建议优先选云托管或单机可跑的方案,把精力留给业务。如果流程是公司的核心资产(比如保险理赔规则),那么投入专职平台人力是值得的,因为流程变更速度直接决定业务响应速度。

除了运维人力,还有「学习成本」。BPMN 的建模培训通常需要 2 到 3 天;Temporal 的确定性约束需要 1 天左右能理解,但踩坑要一到两个迭代;Airflow 上手最快,但写出「不重复、可回填、幂等」的 DAG 需要有人带。这些成本应该在项目排期里显式体现,而不是默认「工程师自己会」。

19. 成本模型与容量规划

自建引擎的成本主要在状态存储。以 Camunda 7 为例,每个流程实例的运行时数据大约几 KB,历史数据(audit 级别)大约 10 到 30 KB,变量多的话会成倍增长。按每天 10 万实例、保留 90 天计算,历史表大约 90 GB 到 270 GB,需要单独的清理策略与分区表。

Temporal 的成本模型不同:持久化层(Cassandra/PostgreSQL)承担事件历史,可见性层(Elasticsearch)承担查询。事件历史的大小与 Workflow 执行步数成正比,一个执行 1 万步的长流程可能产生几十 MB 的历史。所以 Temporal 的最佳实践是「长流程用 ContinueAsNew 截断历史」,把历史控制在几千个事件以内。

云托管的成本最直观也最容易失控:Step Functions 按状态转换计费,一个 20 步的流程跑 100 万次就是 2000 万次转换;Temporal Cloud 按 Action 计费,Activity 调用与 Signal 都算 Action。做容量规划时要把「每笔业务产生多少次状态转换」算清楚,再乘以业务量。

20. 落地路线图

一个可执行的四周落地计划:

  • 第 1 周:选定一个「步骤 5 到 8 步、含一次外部调用、含一次等待」的真实流程作为试点,不选最复杂的,也不选最没价值的。
  • 第 2 周:搭建引擎环境(开发 + 测试),把流程定义跑通,验证崩溃恢复、重试、超时三个行为。
  • 第 3 周:接入观测(实例查询页、关键指标、告警),把「卡住怎么发现」这件事做完。参考 工作流可观测与调试 。
  • 第 4 周:做一次故障演练,人为杀掉 Worker、断开数据库、注入下游超时,验证恢复行为符合预期。

四周之后再决定是否推广到其他流程。如果试点阶段就发现「引擎的行为与预期不符且改不动」,那是换引擎的信号;如果发现「流程建模本身很别扭」,那可能是流程该拆了,而不是引擎选错了。

21. 权衡取舍

你的处境推荐选择理由
业务方要改流程图,有大量人工审批Camunda 7 或 8BPMN 与人工任务是原生能力
服务编排复杂、工程师主导、需要长期运行Temporal代码即流程,重试超时内建
每天批量处理数据、需要补数回填Airflow 或 Dagster调度与分区是核心能力
已经在 AWS 上、流程简单Step Functions零运维,集成原生
状态少、无等待、无审批自研状态表引入引擎的收益小于成本
需要数据血缘与资产视图Dagster软件定义资产模型
需要毫秒级调度、动态生成任务Prefect 或 Temporal运行时动态编排
流程跑在 K8s、每步需要独立镜像Argo WorkflowsPod 级隔离与资源配额

22. 常见坑清单

  1. 用 Airflow 做需要「等人审批」的流程,结果只能用传感器轮询数据库,延迟高且状态不可见。
  2. 在 Temporal Workflow 里调用 System.currentTimeMillis(),本地正常、重放时因为时间不一致导致分支漂移。
  3. 把 BPMN 当流程图工具用,画了 60 个节点,最后没人敢改,退化成新的遗留系统。
  4. 自研状态表但没做状态机校验,非法流转(比如「已取消」到「已发货」)悄悄写进库。
  5. 认为引擎自带「恰好一次」,在 Activity 里重复扣款,实际只保证至少一次投递。
  6. 流程定义变更后直接改线上实例,老实例重放时找不到新节点而卡死,缺少版本兼容策略。
  7. 用 Step Functions Express 模式做长流程,超过 5 分钟被强制终止。
  8. 所有流程共用一个任务队列,长任务阻塞短任务,缺少队列隔离与优先级。
  9. 没有为流程实例设置超时与告警,实例静默堆积几万条才被发现。
  10. 选型时只做功能对比不做压测,上线后才发现引擎的调度粒度或写入吞吐不达标。
  11. 用引擎的变量存储当业务数据存储,把整个订单对象塞进流程变量,导致状态库膨胀十倍。
  12. 忽略了「退出成本」,两年后发现流程定义无法导出,迁移等于重写。

23. 小结

工作流引擎的选型本质是选择「把复杂度放在哪里」:放在建模语言上(BPMN)、放在确定性重放上(Temporal)、放在调度器上(Airflow)、还是放在状态设计上(自研状态机)。没有普适最优解,只有与团队结构、流程性质、运维能力匹配的解。

建议的下一步阅读顺序是:先读本专题里与你场景最接近的一篇(BPMN、Temporal、Airflow 三选一),把该引擎的核心语义吃透;再读 重试幂等与补偿设计 ,因为无论选哪个引擎,幂等和补偿都是绕不过去的工程底线;最后读 工作流可观测与调试 ,把「实例卡住怎么查」这件事在项目初期就设计好。

选型决策一旦做出,迁移成本远高于当初多花两天做评估。宁可先做一个小型概念验证,用真实流程跑通一次崩溃恢复和一次人工审批,再决定全量投入。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「工作流引擎」更多文章

  1. 工作流成本优化
  2. 执行器与资源隔离
  3. 调度、回填与补数