优化微信小程序云函数冷启动性能
一句话定义:针对微信小程序后端使用的 Cloudbase 云函数,用初始化代码外移、裁依赖、调内存规格、开请求多并发四个手段降低冷启动延迟,附完整的 before/after 代码。
预计耗时:30 分钟 | 难度:进阶
适用场景
- 适用:小程序首屏或登录链路上的云函数,冷启动延迟影响用户进入体感
- 不适用:批处理或定时任务,那类场景优化吞吐比优化延迟更有价值
云函数冷启动分两层——容器拉起(Cloudbase 平台负责,开发者控制不了)和用户代码初始化(执行 require + 模块级代码,这是开发者可以优化的部分)。这篇 recipe 的四个手段都集中在用户代码层。
环境要求
| 依赖 | 版本 |
|---|---|
@cloudbase/node-sdk | 3.18.1 |
@cloudbase/cli | 3.0.4 |
| Node.js | ≥ 16.13 |
需要一个已经跑起来的云函数。建议用 add-auth-wechat-miniprogram 里的 getLoginTicket 做优化对象。
优化 1:把 SDK 初始化提到模块顶层
原理:Cloudbase 云函数运行在 Node.js 进程里。容器首次拉起时,Node.js 加载整个模块(执行所有顶层代码)。后续同一容器实例处理新请求时,模块级代码不会再次执行——这是 Node.js 的模块缓存机制。把 require 和 init 放在模块顶层,就能让容器复用时跳过这部分开销。
优化前:
// ❌ 每次请求都重新 require + init
exports.main = async (event) => {
const cloudbase = require('@cloudbase/node-sdk');
const app = cloudbase.init({
env: process.env.TCB_ENV,
credentials: require('./tcb_custom_login.json'),
});
const ticket = app.auth().createTicket(event.openid);
return { ticket };
};
优化后:
// ✅ require 和 init 放在模块顶层
const cloudbase = require('@cloudbase/node-sdk');
const app = cloudbase.init({
env: process.env.TCB_ENV,
credentials: require('./tcb_custom_login.json'),
});
const auth = app.auth();
exports.main = async (event) => {
const ticket = auth.createTicket(event.openid);
return { ticket };
};
注意:只有纯内存操作(require、对象初始化)适合放模块级。如果 init 过程涉及网络 IO(比如建立数据库连接池),放模块级反而会拖慢冷启动——因为容器第一次拉起时 IO 和代码加载是串行的。
优化 2:裁剪 node_modules,减少 require 文件量
原理:冷启动时 Node.js 的 require() 需要从磁盘读取并解析 JS 文件。@cloudbase/node-sdk 完整包含数据库、存储、AI 等所有模块,解包后 20+ MB。如果你的函数只用了 auth 模块,其余的加载全是浪费。
检查当前依赖体积:
cd cloudfunctions/getLoginTicket
du -sh node_modules/ | head -1 # 总体积
du -sh node_modules/*/ | sort -h | tail -10 # 前 10 大依赖
裁剪原则:
- 只装
dependencies,不装devDependencies。npm install --production或确保package.json里 devDependencies 不混入 dependencies - 用原生模块替代大库。
getLoginTicket里调微信jscode2session接口只需要一个 HTTP GET,用 Node.js 内置的https模块就够了,不需要装axios(3MB+) npm ls --prod --depth=1列出运行时实际依赖,逐个评估能否去掉
优化 3:调大内存规格
原理:Cloudbase 云函数的 CPU 按内存比例分配(官方文档原文:"根据指定的内存分配函数运行时可用的计算资源,CPU 按跟随内存的大小,按比例自动分配")。冷启动阶段 Node.js 解析 JS 文件是 CPU 密集操作,分到更多 CPU 这一步自然会更快。
配置方式:控制台 → 云函数 → 函数详情 → 基础配置 → 内存。默认 256 MB,可以调到 512 MB 或更高(具体可选范围以控制台实际选项为准)。
成本权衡:内存翻倍,单次调用单价也大致翻倍。对登录、鉴权这类「每日调用量不高但延迟敏感」的函数,涨价的绝对金额很小,换来的体感改善是值得的。对高 QPS 的批处理函数,保持默认即可。
优化 4:开启请求多并发(HTTP 云函数)
原理:默认每个云函数实例同一时刻只处理一个请求。突发流量来了 10 个并发请求,平台会拉起 10 个新实例——每个都要经历冷启动。开启「请求多并发」后,一个实例可以同时处理多个请求,突发流量下需要拉起的新实例数量大幅减少, 冷启动发生的概率也随之降低。
配置方式:控制台 → 云函数 → 函数详情 → 基础配置 → 请求多并发 → 开启,设置最大并发数(具体范围以控制台实际选项为准)。
注意:该配置是否仅限 HTTP 云函数,请以控制台实际展示为准。不同函数类型(HTTP 触发 vs 事件触发)的可配置项可能不同。
副作用:开启多并发后,模块顶层的变量会被多个请求共享。如果代码里有「以为每次调用都是全新进程」的假设(比如用模块级变量缓存某个用户的状态),并发场景下会出现数据串扰。做法是:不可变常量可以放模块级,请求相关的状态必须在 handler 函数内部定义。
// ❌ 模块级变量在并发下会串
let currentUserId = null;
exports.main = async (event) => {
currentUserId = event.uid; // 并发时 A 请求设置,B 请求覆盖
// ...
};
// ✅ 请求相关状态放 handler 内
exports.main = async (event) => {
const currentUserId = event.uid; // 每个请求独立
// ...
};
运行验证
四个优化做完后,用这个埋点脚本确认效果:
const COLD_START_AT = Date.now();
let isCold = true;
exports.main = async (event, context) => {
const latency = Date.now() - COLD_START_AT;
console.log(JSON.stringify({
coldStart: isCold,
latencyMs: isCold ? latency : 0,
}));
isCold = false;
// ... 业务逻辑
};
部署后用 CLI 触发几十次采样,从日志里筛 coldStart: true 的记录看延迟分布。建议每做一项优化就跑一遍,单项对比才知道哪个手段贡献最大。
常见错误
| 现象 | 原因 | 修复 |
|---|---|---|
| 模块级 init 导致部署成功但所有调用失败 | 模块级代码里 throw 了(比如 require('./tcb_custom_login.json') 文件不存在),容器起不来 | 把可能 throw 的 require 改成 try/catch,错误延迟到 handler 里返回 |
| 开多并发后出现数据串扰 | 模块顶层变量在多个请求间被共享 | 审计所有模块级状态,请求相关的改到 handler 内 |
| 调大内存后冷启动没变快 | 依赖很小或业务是 IO 密集(CPU 本来不是瓶颈) | 用埋点脚本看 latencyMs 里有多少是 Node.js 加载 + 解析的时间,如果本来就很短,说明瓶颈在别处 |
裁依赖后本地跑通,云端报 Cannot find module | 依赖被放进了 devDependencies,tcb fn deploy 不打包 dev 依赖 | 检查 package.json,运行时用到的全部放 dependencies |
| 调大内存后成本明显上涨 | 内存翻倍单价翻倍,高 QPS 函数绝对金额会大 | 只对延迟敏感且 QPS 低的函数调大内存,批处理函数保持默认 |
相关文档
- 云函数基础配置 — 内存、超时、多并发等
- Node SDK 初始化
- 云函数日志查询
下一步
- 登录报错逐条修复:
fix-auth-wechat-miniprogram - 对接企业微信群机器人:
connect-wecom-webhook-cloud-function