做自动化引擎的人,第一反应往往是加机器。请求多了就加API服务器,队列堵了就上Kafka。但把5万张发票的活儿摊开算一遍,会发现真正卡住吞吐量的,是那台只能一次开一张发票的老系统。
这个结论来自一次围绕Minicor展开的设计推演。Minicor处理的是遗留软件的自动化问题:业务方提出某个场景的自动化需求,系统理解人是怎么操作这件事的,生成并测试一条工作流,业务方确认后对外暴露一个API,之后每次调用就由引擎去完成那件事。
推演者据此动手做了一个SmartPay:每次API被调用,就创建一张发票。问题随之而来——如果要支撑更多用户、更多遗留系统,同时还要保证可靠,这套东西该怎么设计?
先把请求的路径画出来
在讨论花哨功能之前,得先看清一次请求在系统里怎么流动。这条链路是:业务方演示场景,引擎提出工作流,业务方批准,API对外暴露,业务方调用API,工作流成功、失败,或者停在一个说不清的状态。
最后那种状态最要命。系统挂了,而没人知道发票到底建没建。
由此拆出的功能需求包括:记录场景与工作流;存储工作流定义和参数映射;发布工作流供业务方审核,审核内容包括名称、参数、输出和提交步骤;为每条已发布的工作流暴露API,每个请求都要按该工作流的规则校验;提供监控面板,让业务人员能查看运行记录并在需要时介入。
暂时不做的部分也划了出来:屏幕变化后的AI修复、通过界面编辑工作流步骤、租户与用户管理,以及自动化引擎自身的计费。
两种失败,两种处理
非功能需求要服务于上面这些功能需求。在定义它们之前,先看两个失败场景。
第一个是保存过程中的中断:工作流在写发票时被打断,会发生什么。
第二个是不确定的结果,它有两种形态。业务方系统在发票保存前停了;或者发票已经保存,但引擎还没来得及记录成功。第二种情况下,发票真实存在,系统却不知道。此时盲目重试,就可能重复开票,甚至向客户重复收费。
于是有了这条硬要求:即使执行结果不确定,也必须防止重复创建发票。每张发票的操作需要幂等性,同时要能区分“确认失败”和“结果不明”。结果不明的运行必须先对账,之后才允许再次尝试开票。
换句话说,没收到成功响应不代表可以重发同一个请求。同一个请求要有稳定的幂等键,执行系统必须检查预期效果是否已经发生。工作流定义、参数、映射和规则也要以结构化格式(比如JSON)存下来,这样每个API请求都能对照它调用的那条工作流做校验。
响应时间不等于完成时间
API该多快?这里有个陷阱:自己这一侧可以优化到极致,但目标应用本身慢怎么办。
引擎也许能在500毫秒内确认收到请求,但这不意味着发票会在500毫秒内建好。遗留的计费应用可能要花长得多的时间才能完成操作。所以API响应时间和工作流完成时间必须分开看。
接着是量的问题:收到5万个请求会怎样。推演者为此和Claude讨论了不短的时间。在算成本之前,先立几个基本概念:速率等于请求数除以时间窗口;并发约等于速率乘以平均处理时间;存储等于记录数乘以平均记录大小。还要多问一句——限制性资源是什么,这个限制长在哪里。
假设要在四小时内处理5万张发票。四小时是14400秒,平均到达速率约为每秒3.5个请求。再假设遗留应用创建一张发票要30秒,按利特尔法则,并发约为3.5乘以30,也就是104个并发运行。
104个在跑,从基础设施角度看似乎还能接受。但还有另一重约束。
真正的天花板在老软件那边
假设每个业务方只有一台遗留应用实例,而这台实例一次只能创建一张发票。如果一个业务方要处理500张发票,每张30秒,那就是15000秒,合4小时10分钟,已经超过了四小时的目标。
加API服务器、加worker都解决不了这个问题。目标应用一次只能处理一张发票,引擎没法靠堆基础设施让这台实例变快。真正的容量上限,是每个业务方遗留应用所支持的并发数。要提升它,可能需要额外的会话、应用实例或授权,具体取决于软件本身和业务环境支持什么。
所以关键问题不是“怎么扛住5万个请求”,而是:会有多少请求到达,每条工作流要跑多久,每个目标系统能安全地同时执行多少条工作流。
至于要不要Kafka或SQS,在这个假设负载下,第一天并不一定需要。可以先用PostgreSQL作为持久化任务队列,把执行记录和队列状态放在同一个数据库里,既减少要运维的系统数量,也让执行状态和排队中的工作保持一致。
核心实体的关系大致是:租户之下挂着目标实例(对应目标应用,其下还有代理)、客户端应用(其下是运行记录,指向目标实例)以及工作流(其下是演示记录);工作流与运行记录相连。
这一轮推演停在设计约束的梳理上。队列的具体实现、失败恢复和成本估算都还留着。但已经浮出来的那条经验很朴素:在给自动化引擎扩容之前,先看懂工作流,把请求从头到尾追踪一遍,找出真正限制吞吐量的那个系统。
热门跟贴