写在前面

每次跟技术同行聊到"我们的流程引擎是自研的"这个话题,对方的第一反应几乎都一样:“为什么不用开源的?Activiti/Camunda不香吗?”

说实话,我们内部当年做这个决策的时候,也纠结了很久。自研意味着更大的投入、更长的周期、更多的坑要自己填。但五年多走下来,回过头看,这是数环通iPaaS平台做得最正确的技术决策之一。

这篇文章想把这个决策过程拆开来讲:当初看了哪些方案,为什么不合适,自研的引擎做成了什么样,踩了哪些坑,最终的收益到底体现在哪里。


市面上的开源流程引擎,各有各的"但是"

Activiti / Camunda — BPMN阵营

这两个可能是Java生态里知名度最高的流程引擎了。基于BPMN 2.0规范,有成熟的流程设计器,社区生态丰富。

优点

  • BPMN标准规范,流程定义可移植
  • 有完整的人工审批、会签、加签等能力
  • 丰富的事件(信号事件、消息事件、定时事件)
  • 商业版Camunda有不错的监控后台

但是

这两个引擎的设计初衷是企业内部审批流程——人工任务在流程中占主导,节点之间可能间隔数小时甚至数天。所以它们的执行模型是"事件驱动 + 数据库持久化每一步状态"。

对iPaaS场景来说,问题在于:

  1. 性能模型不匹配。iPaaS流程是"API调用链",一个流程可能调用5-20个外部API,全程几秒到几十秒搞定。每一步都做数据库持久化?在日均百万执行量下,这个IO开销是致命的。
  2. 流程定义过重。BPMN XML冗长复杂,一个简单的"触发器→数据转换→调API"就要写几十行XML。而iPaaS的流程本质上就是一条数据管道,不需要泳道、不需要人工节点。
  3. 多租户能力弱。Activiti的多租户只是在数据层面隔离,并发控制、资源限制、流量管控全部需要自己做。
  4. 扩展成本高。想加一个"连接器调用"的节点类型?得深入理解它的ServiceTask和JavaDelegate机制,跟自有的连接器体系做适配。改造成本不亚于自研。

Temporal / Cadence — 工作流编排阵营

Temporal(原Uber的Cadence项目衍生)是这几年的新秀,在微服务编排领域口碑很好。

优点

  • 天然分布式,支持集群部署
  • Activity执行自动重试和超时处理
  • 支持长时运行工作流(几天甚至几个月)
  • 工作流的持久化和恢复机制很优秀
  • 有信号和查询能力

但是

  1. 技术栈依赖重。需要部署独立的Temporal Server集群(依赖Cassandra或MySQL + ElasticSearch),运维成本不低。对于一个本身就是SaaS产品的平台来说,多一套基础设施就多一分出问题的概率。
  2. 编程模型限制。Temporal要求每个Activity是一个独立函数,工作流通过代码编排。而iPaaS的流程是用户在前端可视化拖拽出来的,是JSON配置驱动的。两种范式之间的转换层会非常厚。
  3. 国内生态弱。文档、社区支持主要面向英文用户,国内SaaS特有的签名算法、认证方式等适配全靠自己。
  4. 计费困难。SaaS产品需要按执行次数、步骤数计费。Temporal本身不提供这种粒度的度量,需要额外开发。

Apache Airflow — 数据管道阵营

Airflow在数据工程领域是标配,DAG调度能力很强。

优点

  • DAG定义直观,Python代码定义流程
  • 调度能力强(Cron、依赖调度)
  • 有成熟的任务重试和告警机制
  • 社区庞大,Operator丰富

但是

  1. Python生态。数环通的技术栈是Java,引入Airflow意味着混合语言部署,增加复杂度。
  2. 批处理导向。Airflow是为ETL批处理设计的——T+1跑、按天调度。iPaaS需要的是实时事件驱动,一个钉钉审批通过,2秒内触发后续流程。
  3. 不适合高并发短任务。Airflow一个Worker进程能处理的并发DAG数很有限,远达不到iPaaS单节点数千并发的需求。
  4. 没有可视化编排能力。面向开发者,不面向业务用户。

Node-RED / n8n — 轻量集成阵营

这类工具跟iPaaS最像,都是可视化流程编排 + 连接器生态。

优点

  • 低代码可视化编排
  • 连接器生态丰富(n8n 400+节点)
  • 部署简单
  • 前端交互设计优秀

但是

  1. 单机模型。Node-RED和n8n都是单进程设计,没有集群调度能力。当执行量上去后,只能堆机器,没有统一的队列和调度。
  2. 没有租户隔离。作为自部署工具很合适,作为多租户SaaS平台的底座完全不行。
  3. 执行模型太简单。没有暂停恢复、没有并发控制、没有优雅停机。一个流程卡住可能影响整个进程。
  4. Node.js性能天花板。在大数据量同步场景下(比如一次拉取10万条订单做ETL),受限于V8内存和单线程模型。

我们需要的引擎,长什么样?

把上面的分析综合下来,数环通iPaaS的流程引擎需要满足这些核心诉求:

需求维度 具体要求
执行模型 内存态运行,只在必要时持久化(而非每步持久化)
并发能力 单节点支撑数千流程并发执行
多租户 租户级并发隔离、资源限制、公平调度
驱动方式 JSON配置驱动,支持前端可视化编排
扩展能力 Handler插件化,新增节点类型不改引擎核心
运维能力 暂停恢复、优雅停机、超时管控
计费支持 步骤级计数、连接器维度统计
重试机制 节点级自动重试,支持固定间隔和动态间隔
并行分支 Fork/Join并行执行

没有一个开源方案能同时满足上面这些要求。与其在别人的框架上缝缝补补,不如从零设计一个专为iPaaS场景优化的轻量执行引擎


自研引擎的核心架构

执行模型:Handler Chain + Step推进

引擎的核心执行模型非常简洁。一个流程在运行时由三个核心对象组成:

  • FlowInstance:流程定义,包含一组Handler和它们的连接关系
  • Execution:一次流程执行的上下文,承载所有运行时数据
  • ExecutionHandler:每个节点的执行逻辑,通过nextHandler串联成链

执行过程就是沿着Handler Chain逐个推进:

TriggerHandler → ConnectorHandler → DataTransformHandler → ConnectorHandler → EndEvent

每一步(Step)执行完成后,Runner检查是否有中断信号,没有就推进到下一个Handler:

// 核心执行循环
handler.execute(ec, callback);
// 步骤完成后检查中断
if (interruptControl.executeInterrupt(ec)) {
    return;  // 响应中断信号
}
// 推进下一步
ec.newStep(currentStep.getNextHandler(), ...);

为什么这么设计而不是状态机?

状态机的问题是每次状态变迁都要做持久化(否则无法恢复),这在高频执行场景下是瓶颈。我们的做法是:执行过程全部在内存中完成,只在中断时才做快照持久化。99%的流程一气呵成跑完,不需要任何IO开销。那1%被中断的流程,通过Snapshot恢复到其他节点继续执行。

并发控制:Semaphore + 多级水位

单机数千并发流程执行,最怕的是雪崩。我们设计了一套多级水位的并发控制机制:

// 6级执行决策
1. 子流程/恢复执行 → 直接放行(skipQueue)
2. 流程维度并发数超限 → 快速失败或排队
3. 低于安全水位 → 直接执行
4. 队列中有排队 → 直接排队(保证公平性)
5. 达到警告水位 → 快速失败或排队
6. 组织维度并发超限 → 快速失败或排队

底层用Java Semaphore控制许可数:

public class ConcurrencyControl {
    private final Semaphore semaphore;
    
    public void acquire(Execution ec) {
        semaphore.acquire();  // 获取许可,满了就等
    }
    
    public void release(Execution ec) {
        semaphore.release();  // 执行完释放
    }
    
    public double permitsUsageRate(Integer permitsLimits) {
        // (总量-余量)/总量 = 使用率
        return (permitsLimits - availablePermits()) / permitsLimits;
    }
}

更巧妙的是,许可数支持通过Nacos动态调整。线上出现性能问题时,可以热调整并发上限而不用重启:

public void changePermits(int original, int target) {
    if (original < target) {
        increase(target - original);  // 增加许可
    } else {
        decrease(original - target);  // 逐个回收许可
    }
}

排队机制:不能排的快速失败,能排的入队等待

并非所有流程都适合排队。同步API调用(用户在等响应的)不能排——排10秒用户早超时了。异步的事件触发流程可以排——反正多等几秒用户感知不到。

private boolean unableEnqueue(Execution ec) {
    // 试运行不排队
    if (ec.getFlow().isTest()) return true;
    // 同步API触发不排队
    if (TriggerTypeEnum.API_MANAGE_TRIGGER.equals(triggerType) && !isAsync(ec))
        return true;
    return false;
}

排队的流程会做一次快照持久化,等资源空闲后由调度器取出恢复执行。这让引擎在高峰期不会崩溃,只是响应变慢了一点——降级而不是崩溃。

Fork/Join并行:不只是多线程那么简单

iPaaS流程中经常需要并行执行多个分支——比如"同时向钉钉和飞书发通知"、“把一批数据拆成多个批次并行推送”。

我们实现了Fork/Join模型:

protected List<Execution> runForkContext(Execution ec, ExecutionForkContext forkContext) {
    for (ExecutionForkRecord record : forkContext.getRecords()) {
        Execution forkedEc = ec.fork();  // 派生子执行上下文
        forkedEc.forkStep(record.getCurrentStep().getCurrentHandler(), ...);
        
        if (isSync) {
            runner.runSync(forkedEc);   // 单分支直接同步执行
        } else {
            runner.run(forkedEc);       // 多分支并行执行
        }
    }
    join(ec.getCurrentStep().getStepId());  // 等待所有分支完成
}

注意这里的优化:如果只有一个分支(实际不需要并行),直接同步执行,避免线程切换开销。这是我们在线上发现的——大量"并行网关"实际只有一条分支(另一条被条件过滤掉了),如果还做线程派发,纯浪费。

Fork等待用Semaphore实现:Fork时acquire,所有分支完成后在JoinGateway中release。简洁且线程安全。

步骤级重试:失败了不用从头来

外部API调用是不稳定的——网络抖动、限流、超时都可能发生。引擎内置了步骤级重试机制:

// 支持两种重试策略
// 1. 固定间隔:每隔N秒重试一次
// 2. 动态间隔:[1s, 3s, 10s] 逐次递增

for (int curExecCount = start; curExecCount <= stepRetryCount; curExecCount++) {
    // 恢复本步骤的上下文
    ec.putParameter(currentHandler.getName(), parameter);
    ec.getCurrentStep().setOutput(output);
    ec.setNextHandler(nextHandler);
    ec.setError(error);
    
    // 重新执行
    doExecuteHandler(currentHandler, ec, callback);
    
    if (ec.getError() == null) break;  // 成功了就跳出
    Thread.sleep(interval);  // 等待后重试
}

关键设计点:只对外部连接器调用做重试,内部逻辑节点不重试。因为内部节点的失败通常是逻辑错误(比如字段映射配错),重试也不会好。而外部调用的失败大多是瞬时的,重试有意义。

中断信号机制:无侵入的流程控制

引擎支持多种中断类型——手动暂停、部署暂停、手动停止、执行失败、排队中断:

public enum InterruptTypeEnum {
    MANUAL_PAUSE,      // 用户手动暂停
    MANUAL_STOP,       // 用户手动停止
    DEPLOY_PAUSE,      // 优雅停机暂停
    EXECUTE_FAILED,    // 执行失败
    QUEUE              // 排队等待
}

中断信号存在本地 Cache中(性能考虑),执行器在每个步骤间隙检测一次。检测到信号后,流程在当前步骤完成后停下来,做快照持久化。

这套机制的精巧在于——对Handler实现完全透明。写一个新的连接器Handler,不需要关心中断逻辑,引擎在外层统一处理。这大大降低了开发新节点类型的心智负担。

超时监控:不让僵尸流程占着资源

每300毫秒巡检一次所有运行中的流程,超过配置时间(默认6小时)的直接终结:

private JobResult monitorTimeoutExecutions() {
    for (Map.Entry<String, Execution> e : executions.entrySet()) {
        if (ec.getRuntime().getDuration() > executionTimeout) {
            timeouts.add(ec);
        }
    }
    for (Execution ec : timeouts) {
        ec.setError(new IpaasException(ResultCode.EXECUTION_TIMEOUT));
        complete(ec);  // 释放资源
    }
}

看似简单,但如果用Activiti做这个事情,你需要额外部署一个定时任务去查数据库里的运行中流程,然后发信号去中断——链路长、延迟高。我们的实现是纯内存操作,毫秒级响应。

多租户感知:每一层都有隔离

从并发控制到队列排队,引擎的每一层都是租户感知的:

  • 并发许可:全局信号量控制总并发,避免单租户打爆整台机器
  • 组织级并发限制groupConcurrencyLimit限制单个组织的最大并发数
  • 排队公平性:队列按组织隔离,一个组织排队不影响其他组织
  • 计费统计:按组织+流程维度统计步骤数、连接器调用数

这些能力在开源引擎上几乎不可能开箱获得,都是自研才能做到这种粒度的嵌入。


自研的真实难度

说了这么多好处,也得诚实地讲讲自研的代价。

第一年最痛苦。从执行模型设计、Handler抽象、到线程模型调优,前半年基本在反复推翻重来。特别是并发模型——一开始用的是线程池直接submit,结果高并发下线程数爆炸。后来改成Semaphore许可模型才稳定下来。

边界场景多。Fork/Join看似简单,但嵌套Fork(Fork里面再Fork)、Fork某个分支失败另一个还在跑、Fork和中断信号的交互……这些边界场景的组合爆炸,每一个都是生产事故的潜在来源。

没有参考。用开源方案至少StackOverflow上能搜到别人遇到的问题。自研引擎的坑,全世界只有你自己踩过,只能靠线上案例一个一个打磨。

测试难度大。流程引擎的测试不是简单的单元测试能覆盖的。并发竞态、超时边界、中断时机……这些需要大量的集成测试和混沌工程手段。

我们团队前后投入了3人×12个月才把核心引擎打磨到生产级稳定。这不是一个"随便试试"的投入量。


但回报也是实实在在的

性能方面:单节点稳定支撑2000+并发流程执行。如果用Activiti + MySQL这套方案,同等并发量可能需要3-5倍的机器资源。

灵活性方面:新增一种节点类型(比如CDC触发器、OPC UA协议节点),只需要实现ExecutionHandler接口,不需要动引擎核心代码。过去三年我们加了十几种新节点类型,引擎主循环的代码几乎没改过。

运维方面:优雅停机、暂停恢复、动态调参、超时管控——这些能力是生产环境的刚需,自研可以做到丝滑集成。如果是基于开源方案改造,每一个都是大动作。

产品差异化:排队机制、多级水位降级、租户并发隔离——这些精细化的资源管控能力,直接转化为了SaaS产品的卖点(“不限量执行” vs “保证SLA”)。

发版速度:遇到线上问题,我们能在小时内定位到引擎层面的原因并修复。如果是开源方案,得先判断是框架Bug还是自己的使用问题,光排查就可能花几天。


总结:什么时候该自研,什么时候不该

说到底,是否自研流程引擎,取决于你的业务场景和引擎的贴合度

建议用开源方案的场景

  • 企业内部审批流程(Activiti/Camunda很合适)
  • 数据ETL调度(Airflow是最佳选择)
  • 微服务编排且团队熟悉Temporal(上Temporal)
  • 个人或小团队做集成工具(n8n开箱即用)

建议自研的场景

  • 做SaaS平台,需要多租户资源隔离
  • 高并发短任务(秒级执行、万级QPS)
  • 需要深度定制执行模型(配置驱动、非代码驱动)
  • 流程执行是核心竞争力,不是辅助功能
  • 团队有足够的工程投入能力

对数环通来说,流程引擎是整个产品的心脏。它的性能、稳定性、灵活性直接决定了客户体验。这种核心中的核心,交给自己掌控是值得的投入。

五年打磨下来,这个引擎每天稳定执行着千百万条自动化流程,支撑着上万家企业的集成需求。它不完美——代码里还有不少待优化的地方——但它是真正为iPaaS场景量身打造的。

这,就是自研的意义。


数环通iPaaS——自研流程引擎驱动的企业级集成平台,1000+应用连接器,亿万级流程执行。了解更多:https://www.solinkup.com

标签:#流程引擎 #iPaaS #Activiti #Camunda #Temporal #自研引擎 #并发控制 #微服务编排 #数环通 #工作流引擎 #企业集成 #低代码平台

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐