函数计算的宣传语通常是“不用管服务器、按量付费、弹性无限”,但真正决定项目成败的,往往不是这些优点,而是它的两个硬约束:有状态的部分放不下,延迟敏感的部分扛不住。搞清楚边界在哪里,比知道它能做什么更重要。

一、用三个问题判断适不适合
面对一个服务,可以依次问三个问题:
- 有没有常驻状态?函数实例随时可能被回收,本地内存、本地文件、长连接都不能作为状态载体。所有状态必须外置到对象存储、数据库或缓存。
- 流量是稀疏的还是持续的?函数计算的成本优势来自“空闲时不收费”。如果服务本身是 7×24 高负载,常驻容器在单位算力上反而更便宜。
- 时延预算是多少?如果端到端要求 P99 低于 100ms,必须把冷启动概率压到极低,否则需要额外的保活成本。
三个问题里有一个答“不满足”,就要慎重;有两个以上不满足,通常说明这个服务更适合容器。
二、冷启动不是“第一次慢”那么简单
冷启动的耗时可以拆成四段,定位问题时最好分开量:
| 阶段 | 典型耗时 | 影响因素 |
|---|---|---|
| 调度与分配实例 | 10–100ms | 平台侧资源池、并发突增幅度 |
| 运行时与依赖加载 | 50ms–2s | 运行时类型、依赖包体积 |
| 初始化代码执行 | 10ms–3s | 建连、读配置、加载模型 |
| 网络就绪(如挂 VPC) | 50ms–1s | 弹性网卡分配、安全组生效 |
Java 与 .NET 的冷启动通常最重,Node.js、Go、Python 较轻。但真正的大头经常是“初始化代码”:在函数的初始化逻辑里去连数据库、拉配置、加载模型,这些操作被叠加到每一次冷启动上,很容易把 200ms 放大到两三秒。
三、四类实际有效的优化手段
1. 把初始化工作从调用路径里挪出去
利用平台提供的初始化回调(如初始化钩子),把客户端创建、配置拉取、模型加载放在实例初始化阶段执行一次,并在全局变量里复用。这一项改动通常收益最大,且不需要额外成本。
2. 精简依赖与运行时
打包体积直接决定加载时间。可行做法包括:剥离仅构建期需要的依赖、避免把整个 SDK 打进去而只引必要模块、用体积更小的基础镜像。一个常被忽略的点:依赖里有大量原生扩展会显著拖慢加载,在 Node.js 里尤其是这样。
3. 按需使用预置并发
预置并发(Provisioned Concurrency)让实例常驻,彻底消除冷启动,但会按常驻量计费。务实的用法是只对延迟敏感的核心路径开出最低保底量,其余流量仍走按需实例。
4. 让流量来得更平滑
突发并发是冷启动的主要触发条件。在网关层做请求排队与限流,或在客户端加重试加抖动退避,都能把并发曲线抹平。定时任务尽量错峰,避免整点集中触发。
四、这几类场景不建议用
长连接服务。WebSocket、gRPC 流式、MQTT 长连接都依赖连接与实例的绑定关系,函数模型的实例回收会直接切断连接。
高频低延迟的内部调用。服务间每秒数千次调用、每次几十毫秒,单次调用开销与冷启动波动都会成为瓶颈,常驻服务更合适。
重计算与长时间任务。各平台都有单次执行时长上限(从几分钟到十几分钟不等),超过上限的批处理任务需要改写成分片触发,改造量往往被低估。
强顺序与强事务的场景。并发实例之间没有执行顺序保证,需要分布式锁、消息队列或幂等设计来兜底。
五、上线后该盯的几个指标
函数计算的可观测性与常驻服务差别很大,至少需要补齐:冷启动次数占比与冷启动耗时分布、并发实例数的峰值与均值比、初始化阶段的耗时、以及按函数维度的成本归因。其中“冷启动次数占比”是最该放进看板的单一指标,它同时反映流量形态与优化效果。
函数计算带来的效率提升是真实的,但它是一种形态匹配度要求很高的技术。把它用在事件驱动的、流量稀疏的、可以接受百毫秒级波动的场景上,收益非常明显;硬塞进延迟敏感的长连接服务里,则只会把复杂度搬到监控和重试逻辑里。
