AWS Lambda 的冷启动问题真的能让人抓狂。上周刚上线了一个新版本的 API 网关函数,结果一压测,冷启动时间直奔 6 秒,部分请求直接抛出 Gateway Timeout。去 AWS CloudWatch 日志里排查,发现系统加载依赖的 Init Duration 占了整整 5.5 秒。登入 CI 机器上一看,好家伙,由于开发同学直接把 node_modules 里的全部依赖包括 AWS SDK、大量的 lodash 辅助函数,甚至一些本地开发测试工具都一股脑压缩进 zip 包,整个部署包体积达到了惊人的 85MB。
要知道,AWS Lambda 初始化时需要下载这个 zip 包并解压。包体积越大,拉取和解压消耗的时间就呈线性增长。更别提由于 node.js 的 require 机制是同步磁盘 I/O,加载数千个小文件直接把 CPU 跑满,导致冷启动慢得像蜗牛。对于敏感的 C 端接口来说,这绝对是灾难。
为了解决这个问题,我们必须给部署包瘦身,定下了一个“Lambda 打包体积预算(Bundle Budget)”目标:解压前 zip 包控制在 5MB 以内。我们废弃了原先无脑 zip 的打包方案,改用 Esbuild 进行单文件打包和 Treeshaking 树摇。
这里直接贴出我们最终上线并稳定运行的 esbuild.config.js 自动化打包脚本。它不仅能自动排除 AWS SDK(因为 Lambda 运行时环境自带),还能自动压缩混淆,精简最终输出。
import esbuild from 'esbuild';
import { builtinModules } from 'module';
const nativeModules = [
'@aws-sdk/client-s3',
'@aws-sdk/client-dynamodb',
'aws-sdk', // 兼容老版本 sdk
...builtinModules
];
async function runBuild() {
console.time('Build completed in');
try {
const result = await esbuild.build({
entryPoints: ['src/handlers/api.ts'],
bundle: true,
minify: true,
sourcemap: true,
platform: 'node',
target: 'node18',
format: 'esm', // 采用 ESM 格式输出,提升加载速度
outfile: 'dist/index.mjs',
external: nativeModules, // 关键:排除运行时已有的原生模块和 AWS SDK
treeShaking: true, // 开启树摇,剔除未使用的辅助函数
metafile: true, // 生成分析文件,用于可视化包体积
define: {
'process.env.NODE_ENV': '"production"',
},
banner: {
// 解决 ESM 下 __dirname 和 __filename 找不到的报错问题
js: `
import { createRequire } from 'module';
const require = createRequire(import.meta.url);
`,
},
});
console.log('⚡ Build succeeded.');
console.timeEnd('Build completed in');
// 打印打包分析摘要
if (result.metafile) {
const text = await esbuild.analyzeMetafile(result.metafile);
console.log(text);
}
} catch (error) {
console.error('❌ Build failed:', error);
process.exit(1);
}
}
runBuild();
通过这套脚本,我们把原本 85MB 的依赖怪物,直接浓缩成了一个只有 820KB 的单个 index.mjs 文件。
上线后的效果非常显著:Init Duration 从 5.5 秒暴跌到 280 毫秒,整体冷启动时间稳定在 400 毫秒以内,用户端再也没有遇到超时报错。而且,精简后的包也加快了我们的 CI/CD 部署速度,从原本打包上传到 AWS 耗时 2 分钟缩短到了 10 秒以内,开发体验也得到了极大提升。
所以,做 Serverless 架构设计时,千万别拿传统主机的依赖思路去写 Lambda。对于这种瞬时启动、按需计算的场景,每一个 KB 的依赖都是实打实的成本和延迟。
你们在写 Lambda 或者 Serverless 函数时,是用什么构建工具做压缩的?有遇到过哪些因为第三方依赖引入的莫名其妙的冷启动大坑吗?