冷启动是Serverless架构绕不开的话题。当一个函数长时间没有流量,平台会回收它的运行实例;下一次请求到来时,平台必须重新分配容器、下载代码、启动运行时、执行初始化逻辑,整个过程用户请求一直处于等待状态。如果函数依赖了较重的框架或需要建立数据库连接池,冷启动时间轻松突破两三秒,对延迟敏感的在线业务来说基本不可接受。要解决这个问题,业内主要依靠预留实例和预热两种手段,下面分别展开分析。

冷启动到底慢在哪里
一次完整的冷启动大致包含四个阶段:调度资源、下载代码与依赖、启动运行时容器、执行用户初始化代码。前三个阶段由平台控制,通常耗时100到500毫秒;真正拖慢冷启动的往往是第四个阶段,也就是函数处理函数之外编写的初始化逻辑。
比如一个Python函数如果在文件顶部加载了一个较大的机器学习模型,或者在初始化时建立了几十个数据库连接,这些工作全部要算进冷启动时间。再比如Java函数,JVM本身的启动加上Spring框架的类加载,冷启动时间经常超过5秒。理解这一点很重要,因为优化方向完全不同:平台侧的耗时你控制不了,但初始化代码的耗时是可以通过设计来压缩的。
排查冷启动耗时有一个简单办法:在初始化代码和入口函数里分别打上时间日志,多次触发后对比差值。差值大的部分就是你可以优化的空间,差值小的部分只能靠预留实例或预热来规避。
预留实例:花钱买确定性
预留实例的思路很直接:向平台申请固定数量的常驻实例,这些实例不参与缩容回收,请求一到就直接复用,冷启动延迟直接归零。以阿里云函数计算为例,可以为函数配置预留实例个数,配置后这些实例持续存活,无论有没有流量。
配置方式上,控制台在函数的弹性伸缩页面可以直接设置预留实例数,也可以通过SDK或Terraform声明式管理。下面是一个用SDK修改预留配置的示例:
import fc2
client = fc2.Client(
endpoint='https://123458.cn-hangzhou.fc.aliyuncs.com',
accessKeyID='your_ak',
accessKeySecret='your_sk'
)
# 为函数别名 prod 设置 5 个预留实例
client.put_provision_config(
'my-service', 'my-function-alias',
config={'target': 5}
)
预留实例的代价是费用模型的变化。正常按量付费的函数只在执行时计费,而预留实例即使空闲也在持续计费,本质上相当于把Serverless用回了传统常驻服务。因此在配置数量时要谨慎评估:配多了浪费钱,配少了高峰期仍然会出现冷启动。一个实用的策略是结合定时伸缩,在业务高峰时段调大预留数量,深夜低峰调小甚至归零,兼顾性能和成本。
适合使用预留实例的场景包括:对延迟极度敏感的在线接口、初始化耗时特别重的函数(如加载大模型)、以及流量相对平稳可以准确预估的业务。如果流量波动剧烈且难以预测,单纯依赖固定预留数量会比较吃力,需要配合指标伸缩一起使用。
请求预热:定时唤醒实例
预热的原理是利用定时触发器周期性地调用函数,让平台认为这个函数有持续流量,从而保住已启动的实例不被回收。它的优势是几乎零成本,定时调用本身耗时极短,计费金额可以忽略不计。
实现预热需要注意两个关键点。第一,预热请求要和真实业务请求区分开,在入口函数里判断请求特征,直接返回,不执行任何业务逻辑。第二,预热频率要高于平台的实例回收周期,一般建议间隔设置在1到3分钟。下面是一个简单的预热处理示例:
const KEEP_WARM_INTERVAL = 120; // 秒
exports.handler = async (event, context) => {
const payload = JSON.parse(event.toString() || '{}');
// 识别预热请求,直接返回,不执行业务
if (payload.warmup === true) {
return { status: 'warmed' };
}
// 正常业务逻辑
return handleBusiness(payload);
};
定时触发可以直接使用云厂商提供的定时触发器,也可以用外部系统的定时任务发HTTP请求。前者配置简单,在函数触发器列表里添加一个每两分钟执行一次的触发器即可;后者更灵活,可以把多个函数的预热集中在任务调度系统里统一管理。
预热方案也有明显的局限。首先它保活的实例数量不可控,只能保证至少有一个实例热着,无法应对突然并发的扩容需求,新增实例照样要经历冷启动。其次,如果平台调整了回收策略或者触发器执行有抖动,预热可能失效,稳定性不如预留实例。所以预热更适合低成本保底,配合弹性伸缩使用,而不是作为唯一的冷启动解决方案。
压缩冷启动本身的几个技巧
除了外部手段,从函数自身入手压缩冷启动时间同样重要,往往是性价比最高的做法。代码包体积直接影响下载和解压耗时,删除无用依赖、按需安装包、压缩代码包都能带来收益。一个常见错误是把整个开发环境的依赖目录直接打包上传,实际运行时只用到其中一小部分。
初始化逻辑方面,遵循懒加载原则:能在请求处理阶段延迟初始化的资源就不要放到模块顶层。数据库连接不必在启动时建立一整个连接池,可以在首个请求到达时按需创建并缓存。一些平台还提供了初始化回调接口,把重初始化放到这个阶段执行,可以让首个请求更快返回。
语言选择上也有差异。Go编译后的二进制文件启动极快,冷启动通常在百毫秒以内;Node.js和Python表现中等;Java由于JVM的原因最慢,但如果必须使用Java,可以选用厂商提供的自定义运行时并精简框架,或者考虑GraalVM原生镜像,能大幅缩短启动时间。
如何选择组合方案
实际生产中很少单独使用一种手段,更常见的组合是:核心链路函数配置少量预留实例做保底,非核心函数用定时预热维持基本热度,同时所有函数做好代码瘦身和懒加载优化。这样即使遇到流量突发,冷启动的范围和时长也被控制在可接受的范围内。
判断标准可以简单归纳为:看业务能不能接受最坏情况下的秒级延迟。能接受的场景(如后台任务、异步处理)完全不需要预留实例,省下这笔钱;不能接受的场景(如面向用户的API、实时推荐)就必须用预留实例兜底,预热只能作为补充。上线后建议持续监控冷启动相关指标,包括实例初始化耗时和P99延迟,根据真实数据动态调整预留数量和预热频率,让性能和成本始终处于合理的平衡点。
冷启动预留实例Serverless预热修改时间:2026-09-05 08:10:33