凌晨两点被电话叫醒。监控说某个接口 p99 飙到 4 秒。不是流量洪峰。是函数实例全在冷启动。你看一眼调用链,第一段空白,然后才进业务逻辑。这种场景,做 Serverless 的人都懂。
Serverless冷启动优化从来不是调一个参数的事。它更像拆账。代码包、运行时、依赖、VPC、日志初始化、框架扫描、安全代理,每一笔都可能吃掉几百毫秒。想省时间,先得知道时间花在哪。
先看火焰图里那几毫秒到底花在哪
很多人第一反应是加内存。内存给大点,CPU 配额跟着涨,初始化确实会快。但麻烦在于,内存加倍,费用也接近线性涨。你调大一次,账单会替你记住。
更值得看的是初始化火焰图。函数入口之前,有很多看不见的活。比如 Java 的类加载、Spring 上下文扫描、Python 的 import、Node 的 require 缓存。它们不在你的业务代码里,却在冷启动路径上。有一次翻一个 Java 函数,光 Spring 初始化就占了 1.8 秒。业务逻辑本身才 80 毫秒。挺荒诞的。

冷启动不是启动慢四个字能概括的。它分好几段:平台调度、实例创建、运行时拉起、代码下载、初始化、然后才执行 handler。每段的优化手段不一样,有的能靠配置,有的只能靠改代码。
平台调度和实例创建基本不可控。你只能接受。运行时拉起和代码下载,跟包大小、层数量、镜像体积有关。初始化阶段最值得动手。因为这里是你自己的代码和依赖。
预置并发不是万能药 它的账要算清楚
预置并发很香。热门函数配几个预热实例,请求过来直接命中。没有冷启动。但说白了,它等于你提前买了几台小车停在门口。不管有没有单,都要付停车费。
麻烦在于容量规划。配少了,突发流量照样冷启动。配多了,费用肉疼。更尴尬的是,预置并发和版本绑定。你发一个新版本,预热实例要重新准备。自动扩缩也有延迟。所以它适合可预测的稳定流量,不适合那种半夜突然被爬虫扫的场景。

还有个坑:预置并发不是无限扩容。区域配额、账户配额、函数并发上限,都会卡你。有时候你配了 100,实际能用的只有 60。这不是平台坑你,是配额就这么多。得提前申请。
如果只是偶尔有高峰,可以把预置并发和按量并发混用。关键函数留几个预热实例兜底,其余交给按量。别指望一套配置解决所有时段。流量曲线本来就不是一条直线,对吧。
快照恢复和运行时裁剪能救急但别指望根治
快照恢复是这几年比较务实的方向。思路不复杂:把初始化完成的进程内存快照下来,下次冷启动直接恢复,跳过初始化。对 Java、Python 这种初始化重的运行时,收益明显。
但快照有前提。你的初始化必须能安全快照。打开的网络连接、临时文件、随机数种子、外部依赖状态,都可能让快照恢复后出问题。有的 SDK 不喜欢被快照。恢复后连接池是坏的,重连耗时反而更长。

运行时裁剪更朴素。能把包做小就做小。能去掉的依赖就去掉。Java 可以试试 GraalVM native image,或者换轻量框架。Node 别把整个 lodash 塞进去。Python 少用重型 ORM 在初始化时连库。这些都是老办法,但老办法经常最有效。
还要注意层。Layer 多了,挂载和下载也慢。有些团队为了复用,搞了七八层,结果冷启动比不用层还长。复用和启动速度之间,得有个平衡。
真正省下来的时间往往来自架构上的让步

最狠的优化不是把冷启动从 3 秒降到 800 毫秒。而是让这条路径根本不需要冷启动。比如把同步调用改成异步消息。请求进来先写队列,立刻返回。后端函数慢慢消费。用户感知不到冷启动,因为等待被挪走了。
再比如边缘函数。把鉴权、重定向、AB 测试放到边缘。那里实例密度高,冷启动概率低。源站函数只处理真正的业务。这样拆分之后,核心函数的调用量下降,冷启动自然少。
但这有代价。架构变复杂了。异步意味着最终一致,边缘意味着状态难管。你省了延迟,可能欠下可观测性和调试的债。这事没那么玄,就是取舍。
我见过一个团队,为了压冷启动,把一个大函数拆成三个小函数。结果调用链变长,网络跳数增加,p99 反而更差。后来他们又合回去两个。折腾一圈,唯一确定的是:没有银弹。
所以 Serverless冷启动优化别只看冷启动。得看端到端延迟。看成本。看运维复杂度。看团队能不能hold住。如果函数一天只跑几十次,冷启动两秒也无所谓。如果 QPS 很高,那每一毫秒都值钱。
最后一句实在话:先量,再改。不要凭感觉调内存,也不要上来就预置一百个并发。火焰图、调用链、账单,三个放一起看。很多问题不是冷启动,是代码写得太重。你信不信,把初始化里的那个同步 HTTP 调用挪走,比换运行时管用。