华为云 FunctionGraph 冷启动优化实战:把函数首响从 3 秒压到 300 毫秒
一次真实的服务端优化复盘:把函数首响从 3 秒压到 300 毫秒,冷启动到底能不能"治"?
一、便宜是真便宜,慢也是真慢
用函数计算(FunctionGraph)做后端,按量付费、免运维,账单一度低到让人怀疑人生。但当它第一次被调用、或者高峰期突然来一波并发时,接口首响直接飙到 2–3 秒,用户体验断崖式下跌。这就是人人喊打的冷启动。
先把原理讲透:函数执行分两个阶段——初始化(Init)和调用(Invoke)。初始化负责拉起运行时、加载代码、执行全局逻辑;调用才是跑你的 handler。实例闲置一段时间会被回收,下次请求就得重走一遍初始化,这段耗时就是冷启动。触发条件主要有三类:实例闲置回收、突发并发、以及代码或配置变更后的首次调用。
二、五招把冷启动打下去
1. 初始化逻辑挪到 handler 外。 这是收益最大、成本最低的一招。函数实例在生命周期内会被复用,放在全局作用域的代码只在初始化时执行一次,后续调用直接复用。
import pymysql
# 全局作用域:实例复用时仅执行一次
_conn = None
def _get_conn():
global _conn
if _conn is None or not _conn.open:
_conn = pymysql.connect(host="...", user="...", password="...", database="...")
return _conn
def handler(event, context):
conn = _get_conn()
with conn.cursor() as cur:
cur.execute("SELECT 1")
return "ok"
注意最后那段重连判断:实例可能被回收导致连接失效,ping 或判空重连是必须的保险。
2. 精简依赖 + 用层(Layer)共享。 包体越大,下载解压越慢。把公共依赖抽成 Layer,多个函数共享,避免每次打包都塞一遍 numpy 这种大块头。
3. 适当调大内存。 这点很多人不知道:FunctionGraph 的内存规格直接决定 CPU 配额,内存越大算力越强,初始化自然更快。别为了省钱把内存卡到最低,可能反而更贵、更慢。
4. 预留实例(Provisioned Concurrency)。 对延迟敏感的核心接口,直接预留实例,让函数"常驻",从根本上消除冷启动。代价是要为预留时长付费,适合流量稳定的高频接口。
5. 非必要不绑 VPC。 函数一旦绑定 VPC,初始化时还要多走一步网络配置,冷启动明显变长。如果只是访问公网 API,能不绑就不绑。
三、怎么定位真正的耗时
优化不能拍脑袋,得看数据。在函数日志里对比初始化耗时与执行耗时,如果 Init 远大于 Invoke,说明瓶颈在冷启动;反之则要查业务代码。再配合压测看并发场景下的 P95 首响,才能判断优化是否真的生效。此外可以结合 AOM(应用运维管理)把调用次数、错误率、耗时分位值都纳入监控,将冷启动相关指标固化为告警,避免优化效果在一段时间后悄悄回退。
四、小结
冷启动的本质是"初始化成本被分摊到某个倒霉的请求上"。优化思路也很清晰:能省则省(精简依赖)、能复用则复用(初始化外置)、能换算力则换(调内存)、实在不行就买确定性(预留实例)。按这个顺序做下来,把首响压进几百毫秒并不难。函数计算不是不能用,而是要用对姿势。
- 点赞
- 收藏
- 关注作者
评论(0)