为什么我们没用Activiti?数环通iPaaS自研流程引擎的决策复盘
写在前面每次跟技术同行聊到我们的流程引擎是自研的这个话题对方的第一反应几乎都一样“为什么不用开源的Activiti/Camunda不香吗”说实话我们内部当年做这个决策的时候也纠结了很久。自研意味着更大的投入、更长的周期、更多的坑要自己填。但五年多走下来回过头看这是数环通iPaaS平台做得最正确的技术决策之一。这篇文章想把这个决策过程拆开来讲当初看了哪些方案为什么不合适自研的引擎做成了什么样踩了哪些坑最终的收益到底体现在哪里。市面上的开源流程引擎各有各的但是Activiti / Camunda — BPMN阵营这两个可能是Java生态里知名度最高的流程引擎了。基于BPMN 2.0规范有成熟的流程设计器社区生态丰富。优点BPMN标准规范流程定义可移植有完整的人工审批、会签、加签等能力丰富的事件信号事件、消息事件、定时事件商业版Camunda有不错的监控后台但是这两个引擎的设计初衷是企业内部审批流程——人工任务在流程中占主导节点之间可能间隔数小时甚至数天。所以它们的执行模型是事件驱动 数据库持久化每一步状态。对iPaaS场景来说问题在于性能模型不匹配。iPaaS流程是API调用链一个流程可能调用5-20个外部API全程几秒到几十秒搞定。每一步都做数据库持久化在日均百万执行量下这个IO开销是致命的。流程定义过重。BPMN XML冗长复杂一个简单的触发器→数据转换→调API就要写几十行XML。而iPaaS的流程本质上就是一条数据管道不需要泳道、不需要人工节点。多租户能力弱。Activiti的多租户只是在数据层面隔离并发控制、资源限制、流量管控全部需要自己做。扩展成本高。想加一个连接器调用的节点类型得深入理解它的ServiceTask和JavaDelegate机制跟自有的连接器体系做适配。改造成本不亚于自研。Temporal / Cadence — 工作流编排阵营Temporal原Uber的Cadence项目衍生是这几年的新秀在微服务编排领域口碑很好。优点天然分布式支持集群部署Activity执行自动重试和超时处理支持长时运行工作流几天甚至几个月工作流的持久化和恢复机制很优秀有信号和查询能力但是技术栈依赖重。需要部署独立的Temporal Server集群依赖Cassandra或MySQL ElasticSearch运维成本不低。对于一个本身就是SaaS产品的平台来说多一套基础设施就多一分出问题的概率。编程模型限制。Temporal要求每个Activity是一个独立函数工作流通过代码编排。而iPaaS的流程是用户在前端可视化拖拽出来的是JSON配置驱动的。两种范式之间的转换层会非常厚。国内生态弱。文档、社区支持主要面向英文用户国内SaaS特有的签名算法、认证方式等适配全靠自己。计费困难。SaaS产品需要按执行次数、步骤数计费。Temporal本身不提供这种粒度的度量需要额外开发。Apache Airflow — 数据管道阵营Airflow在数据工程领域是标配DAG调度能力很强。优点DAG定义直观Python代码定义流程调度能力强Cron、依赖调度有成熟的任务重试和告警机制社区庞大Operator丰富但是Python生态。数环通的技术栈是Java引入Airflow意味着混合语言部署增加复杂度。批处理导向。Airflow是为ETL批处理设计的——T1跑、按天调度。iPaaS需要的是实时事件驱动一个钉钉审批通过2秒内触发后续流程。不适合高并发短任务。Airflow一个Worker进程能处理的并发DAG数很有限远达不到iPaaS单节点数千并发的需求。没有可视化编排能力。面向开发者不面向业务用户。Node-RED / n8n — 轻量集成阵营这类工具跟iPaaS最像都是可视化流程编排 连接器生态。优点低代码可视化编排连接器生态丰富n8n 400节点部署简单前端交互设计优秀但是单机模型。Node-RED和n8n都是单进程设计没有集群调度能力。当执行量上去后只能堆机器没有统一的队列和调度。没有租户隔离。作为自部署工具很合适作为多租户SaaS平台的底座完全不行。执行模型太简单。没有暂停恢复、没有并发控制、没有优雅停机。一个流程卡住可能影响整个进程。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.子流程/恢复执行 → 直接放行skipQueue2.流程维度并发数超限 → 快速失败或排队3.低于安全水位 → 直接执行4.队列中有排队 → 直接排队保证公平性5.达到警告水位 → 快速失败或排队6.组织维度并发超限 → 快速失败或排队底层用Java Semaphore控制许可数publicclassConcurrencyControl{privatefinalSemaphoresemaphore;publicvoidacquire(Executionec){semaphore.acquire();// 获取许可满了就等}publicvoidrelease(Executionec){semaphore.release();// 执行完释放}publicdoublepermitsUsageRate(IntegerpermitsLimits){// (总量-余量)/总量 使用率return(permitsLimits-availablePermits())/permitsLimits;}}更巧妙的是许可数支持通过Nacos动态调整。线上出现性能问题时可以热调整并发上限而不用重启publicvoidchangePermits(intoriginal,inttarget){if(originaltarget){increase(target-original);// 增加许可}else{decrease(original-target);// 逐个回收许可}}排队机制不能排的快速失败能排的入队等待并非所有流程都适合排队。同步API调用用户在等响应的不能排——排10秒用户早超时了。异步的事件触发流程可以排——反正多等几秒用户感知不到。privatebooleanunableEnqueue(Executionec){// 试运行不排队if(ec.getFlow().isTest())returntrue;// 同步API触发不排队if(TriggerTypeEnum.API_MANAGE_TRIGGER.equals(triggerType)!isAsync(ec))returntrue;returnfalse;}排队的流程会做一次快照持久化等资源空闲后由调度器取出恢复执行。这让引擎在高峰期不会崩溃只是响应变慢了一点——降级而不是崩溃。Fork/Join并行不只是多线程那么简单iPaaS流程中经常需要并行执行多个分支——比如同时向钉钉和飞书发通知、“把一批数据拆成多个批次并行推送”。我们实现了Fork/Join模型protectedListExecutionrunForkContext(Executionec,ExecutionForkContextforkContext){for(ExecutionForkRecordrecord:forkContext.getRecords()){ExecutionforkedEcec.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(intcurExecCountstart;curExecCountstepRetryCount;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);// 等待后重试}关键设计点只对外部连接器调用做重试内部逻辑节点不重试。因为内部节点的失败通常是逻辑错误比如字段映射配错重试也不会好。而外部调用的失败大多是瞬时的重试有意义。中断信号机制无侵入的流程控制引擎支持多种中断类型——手动暂停、部署暂停、手动停止、执行失败、排队中断publicenumInterruptTypeEnum{MANUAL_PAUSE,// 用户手动暂停MANUAL_STOP,// 用户手动停止DEPLOY_PAUSE,// 优雅停机暂停EXECUTE_FAILED,// 执行失败QUEUE// 排队等待}中断信号存在本地 Cache中性能考虑执行器在每个步骤间隙检测一次。检测到信号后流程在当前步骤完成后停下来做快照持久化。这套机制的精巧在于——对Handler实现完全透明。写一个新的连接器Handler不需要关心中断逻辑引擎在外层统一处理。这大大降低了开发新节点类型的心智负担。超时监控不让僵尸流程占着资源每300毫秒巡检一次所有运行中的流程超过配置时间默认6小时的直接终结privateJobResultmonitorTimeoutExecutions(){for(Map.EntryString,Executione:executions.entrySet()){if(ec.getRuntime().getDuration()executionTimeout){timeouts.add(ec);}}for(Executionec:timeouts){ec.setError(newIpaasException(ResultCode.EXECUTION_TIMEOUT));complete(ec);// 释放资源}}看似简单但如果用Activiti做这个事情你需要额外部署一个定时任务去查数据库里的运行中流程然后发信号去中断——链路长、延迟高。我们的实现是纯内存操作毫秒级响应。多租户感知每一层都有隔离从并发控制到队列排队引擎的每一层都是租户感知的并发许可全局信号量控制总并发避免单租户打爆整台机器组织级并发限制groupConcurrencyLimit限制单个组织的最大并发数排队公平性队列按组织隔离一个组织排队不影响其他组织计费统计按组织流程维度统计步骤数、连接器调用数这些能力在开源引擎上几乎不可能开箱获得都是自研才能做到这种粒度的嵌入。自研的真实难度说了这么多好处也得诚实地讲讲自研的代价。第一年最痛苦。从执行模型设计、Handler抽象、到线程模型调优前半年基本在反复推翻重来。特别是并发模型——一开始用的是线程池直接submit结果高并发下线程数爆炸。后来改成Semaphore许可模型才稳定下来。边界场景多。Fork/Join看似简单但嵌套ForkFork里面再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 #自研引擎 #并发控制 #微服务编排 #数环通 #工作流引擎 #企业集成 #低代码平台
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2595302.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!