Lambda 的真正瓶颈不是冷启动,是「一请求一实例」的并发模型——每个请求独占一个执行环境,哪怕 90% 的时间都在等 I/O。Node.js 的事件循环本就能并发处理几十个请求,Fluid Compute 没有发明新技术,只是把这个三十年前就存在的并发模式带回了 Serverless 世界。
上一篇:现代计算的基础设施(一):隔离的代价讨论了隔离方案的设计空间。这篇聚焦一个更具体的问题:Serverless 架构的核心瓶颈到底是什么,业界为什么把它诊断成「冷启动」,正确的诊断应该是什么。
Serverless/FaaS 的核心承诺是:只写函数逻辑,不管服务器。平台负责弹性伸缩、故障恢复、容量规划。计费按实际执行时间,而不是预留实例。
这个模型对架构师有真实的吸引力:
代价是执行模型的约束——函数实例是无状态的,随时可能被销毁,也随时可能从零启动。这个启动过程就是「冷启动」。
一次 Lambda 函数调用,如果没有可用的热实例,需要经历以下阶段:
真实的冷启动时间分布很宽,从几百毫秒到几秒,主要取决于:
node_modules 越大,下载和挂载越慢。多年来,围绕「消灭冷启动」有大量工程实践:减小 bundle 大小、懒加载依赖、预留并发(Provisioned Concurrency)……这些都是有效的,但都在治标。
真正的问题不是冷启动,而是为什么冷启动这么频繁。
Lambda 的并发模型可以用一句话描述:一个执行环境同一时间只处理一个请求。
这意味着,如果你的函数同时收到 100 个请求,Lambda 会启动(或复用)100 个独立的执行环境。
对于一个处理数据库查询的 API 函数,执行时间线大约是这样的:
这个函数的总执行时间 225ms,但 CPU 实际工作时间不到 25ms。剩余 200ms 全部在等待 I/O(建连接、等查询结果)。
在这 200ms 里,执行环境的 CPU 是完全空闲的。但 Lambda 的并发模型不允许这个空闲的执行环境处理其他请求——它在等,其他请求只能另开新环境。
这就是真正的浪费。 不是冷启动,是实例利用率。
理解 Fluid Compute 的解法,需要先理解 Node.js 的并发模型。
Node.js 是单线程的,但它能并发处理 I/O——这不是矛盾,而是事件循环(Event Loop)的设计。
当 Node.js 执行 await db.query(sql) 时:
这意味着,一个 Node.js 进程在等待数据库响应期间,完全可以开始处理另一个请求的逻辑——只要那个请求也很快进入 I/O 等待状态,CPU 就不会成为瓶颈。
对于典型的 web API(大量时间在等数据库、等外部服务),一个 Node.js 进程并发处理几十甚至上百个请求是完全可行的,CPU 利用率依然很低。
这不是新发现。这是 Node.js 从 2009 年就在做的事(Chen 注:Ryan Dahl 于 2009 年 11 月在首届欧洲 JSConf 上发布了 Node.js,核心动机正是解决「一请求一线程」模型在高并发下的资源浪费问题。他在演讲中以 Apache 为反例,展示了事件循环如何用单线程处理大量并发 I/O。[→ Wikipedia](https://en.wikipedia.org/wiki/Node.js#History)),也是 Nginx 相对于 Apache 的核心优势(Chen 注:Nginx 由 Igor Sysoev 于 2004 年发布,明确以解决 [C10K 问题](https://kegel.com/c10k.html)(单机同时处理一万个连接)为设计目标。其事件驱动架构使单个 worker 进程能处理数万并发连接,而 Apache 的 prefork/worker MPM 在同等并发下内存占用高出数十倍。[→ Inside NGINX](https://blog.nginx.org/blog/inside-nginx-how-we-designed-for-performance-scale))所在。Serverless 的「一请求一实例」模型,反而是一种倒退。
Vercel 的 Fluid Compute 本质上是一个并发模型的修正:允许一个执行环境并发处理多个请求。
同样是 100 个并发请求,Lambda 需要 100 个执行环境,Fluid 可能只需要 10 个——因为每个环境并发处理 10 个请求,而这些请求大部分时间都在等 I/O。
实现这个模型,控制平面需要解决一个调度问题:如何把请求路由到有剩余并发容量的实例?
每个执行环境持续向注册表上报当前并发数和最大容量。调度器维护一个实时的容量视图,优先把请求投递给容量充足的实例,只在所有实例都满载时触发冷启动。
每个执行环境的最大并发数(max_concurrency)是一个需要调优的参数:
对于典型的 web API(I/O 密集),并发数可以设置较高(几十到上百)。对于 CPU 密集型任务(图像处理、加密运算),并发数应设为 1——退化回 Lambda 的经典模型。
Vercel 目前对 Fluid Compute 的默认最大并发数是根据函数配置的内存大小动态计算的。
Fluid 的实例不是用完即销毁的。一个实例处理完请求后会保持存活,等待下一个请求——这就是「热实例复用」。
但热实例的保活是有成本的(即使没有请求也占用内存),所以平台需要一个回收策略:
空闲超时后实例被销毁,下次请求触发新的冷启动。这就是为什么低流量函数依然会遇到冷启——不是并发模型的问题,是实例保活策略的问题。
Lambda 的 Provisioned Concurrency(预留并发)解决的是这个问题:花钱让指定数量的实例永远不被销毁,保证零冷启,但需要持续付费。
Fluid Compute 解决了实例利用率的问题,但 Serverless 模型还有一些场景不适合:
长连接服务:WebSocket、Server-Sent Events、gRPC streaming——这些需要持久的有状态连接,Serverless 的无状态实例模型从根本上不匹配。
CPU 密集型批处理:视频转码、大规模数据聚合——这类任务需要稳定的 CPU 资源,Serverless 的弹性启停带来的调度开销反而是负担。
极低延迟要求:即使热实例复用,Serverless 的调度路径(API 网关 → 调度器 → 实例)比直接命中一个始终在线的进程多几毫秒。对于 P99 延迟要求在个位数毫秒的场景,这不可接受。
有状态计算:需要在内存中维护大型数据结构(如内存数据库、机器学习模型缓存)的服务,每次冷启都需要重新加载状态,成本无法接受。
| 适合 Serverless | 不适合 Serverless |
|---|---|
| HTTP API(REST / GraphQL) | WebSocket / 长连接 |
| 事件处理(Webhook / 消息队列消费) | CPU 密集批处理 |
| 定时任务(Cron Job) | 内存状态服务 |
| 边缘函数(请求改写、鉴权) | 极低延迟(P99 < 5ms) |
理解差异的直觉模型:
| 传统长连接(Railway/Render(Chen 注:Railway 和 Render 是两个典型的「始终在线」PaaS 平台,部署后进程持续运行,无冷启动,适合需要长连接或内存状态的服务。计费按实际运行时长,即使没有流量也持续计费。)) | Lambda 经典模型 | Fluid Compute | |
|---|---|---|---|
| 实例生命周期 | 永远在线 | 按请求启停 | 按需启动,空闲保活 |
| 并发模型 | 进程内多线程/事件循环 | 一请求一实例 | 一实例多请求 |
| 冷启动 | 无(始终在线) | 有(每次启动新实例) | 低(热实例复用) |
| 闲时成本 | 高(始终付费) | 无 | 低(短暂保活后销毁) |
| 峰值扩容 | 手动或慢速弹性 | 毫秒级弹性 | 毫秒级弹性 |
| 状态 | 可以有内存状态 | 无状态 | 无状态 |
Fluid Compute 是在「完全无状态的弹性」和「始终在线的长连接」之间的一个工程折中——它把事件循环的并发优势带回 Serverless,同时保留了弹性伸缩和按需付费。
一些API(评论读写、阅读量更新、内容审核)用 Fluid Compute 是合适的:
唯一不适合的部分是 OpenAI Moderation API 的调用——这个也是 I/O 等待为主(等 OpenAI 响应),仍然适合 Fluid。
如果评论系统有实时通知(「有人回复了你」的 WebSocket 推送),就需要一个单独的长连接服务,不能跑在 Fluid 上。
把这个并发模型映射到专线 SaaS 场景,问题变成:如何为有限的、可预测的 B 端并发流量设计计算层?
公有云 Serverless 面对的是不可预测的互联网流量,弹性伸缩是核心需求。专线 SaaS 的流量特征截然不同:
架构建议:不要把 Fluid/Serverless 当成替代所有常驻服务的银弹。在专线 SaaS 里,延迟敏感的核心交易路径应该是常驻服务;Fluid 模式适合报表、导出、通知等 I/O 密集的辅助功能。这样既利用了弹性模型降低非核心路径的成本,又保证了核心路径的延迟稳定性。
容量规划:专线 SaaS 的优势是流量可预测。可以基于历史数据做确定性的容量规划(而不是依赖弹性伸缩),为每个客户分配固定的计算配额,配合 Fluid 的并发复用,在保证 SLA 的前提下最大化机器利用率。