Serverless:无状态化的收益与代价

Serverless 不是没有服务器,而是服务器不再是你需要关心的事。你关心的最小单元,变成了产品本身。

一、引言:把”服务器”从开发者的词汇表里删掉

这个系列一直在做同一件事:把操作性关切从开发者身上一层层抽离

容器抽走了运行时环境——你不再关心”在我机器上能跑,在你机器上跑不了”;Kubernetes 抽走了调度和自愈——你不再关心进程崩溃后谁来重启它;GitOps 抽走了部署决策——你不再关心哪个人在什么时间执行了什么命令。

Serverless 把这个抽象推到了极致:连”我需要一台服务器”这件事本身都抽走了

你不再需要选实例规格,不再需要配置 nginx,不再需要写 Dockerfile,不再需要规划容量。你需要关心的,只有产品逻辑本身——当一个 HTTP 请求到来,我的代码应该做什么?

这个转变听起来只是运维便利,实际上是思维模式的翻转:从”我有一台服务器,我在上面部署代码”,到”我有一段逻辑,平台负责在需要时执行它”。它把开发者的注意力从基础设施拉回到产品,这才是 Serverless 最被低估的价值。

二、为什么需要 Serverless

常驻服务的两个浪费

传统的服务部署模型是”常驻进程”:你启动一个服务,它一直运行,等待请求到来。这个模型有两个隐性浪费:

计算浪费:大多数服务的流量不是均匀的。白天高峰、深夜低谷,周末波动、节假日突增。但你的服务器是按月租用的——流量低谷时,你在为空转的 CPU 付钱。对于中小流量的服务,这个浪费可能超过 70%。

认知浪费:你需要在部署前回答一堆与业务无关的问题。需要几台机器?每台多大内存?流量突增时能撑住吗?需要自动扩容吗?扩容需要多久?这些问题消耗的时间和精力,本可以用在产品功能上。

事件驱动场景的天然契合

有一类场景天然符合”触发 - 执行 - 结束”的模式:用户上传图片后生成缩略图、订单支付后发送邮件通知、定时任务每小时抓取数据、Webhook 回调处理。

这些场景不需要常驻进程。没有触发时,它们什么都不做,不需要任何计算资源。有触发时,它们执行几百毫秒到几秒,然后结束。常驻一个进程等待这类事件,是典型的用大炮打蚊子。

Serverless 对这类场景是完美匹配:触发时启动,执行完毕立刻释放资源。

Scale to Zero:比 HPA 更彻底的弹性

Kubernetes 的 HPA(Horizontal Pod Autoscaler)可以根据 CPU 或请求量自动扩缩容——流量高时增加 Pod,流量低时减少 Pod。但它有一个下界:最少保留 1 个 Pod,因为需要一直有实例等待请求,冷启动代价太高。

Serverless 的 scale-to-zero 打破了这个下界:完全没有流量时,实例数可以降到 0。这不只是省钱,它改变了部署的基本假设——你可以部署数百个函数,大部分时间都不运行,只有被触发时才消耗资源。这对长尾功能(偶尔用到的 API、低频的后台任务)尤其有价值。

三、怎么设计:FaaS 的执行模型

函数生命周期

以 AWS Lambda 为代表的 FaaS 平台,函数的生命周期分为四个阶段:

flowchart LR
    CS["冷启动\n① 下载代码包\n② 启动运行时\n③ 函数初始化"] -->|初始化完成| EX["执行\nhandler 处理请求"]
    EX -->|请求完毕| FR["冻结\n实例保留待复用"]
    FR -->|新请求到来| EX
    FR -->|空闲超时| RC["回收\n释放资源"]
    RC -->|下次调用| CS

复用是性能优化的关键:函数初始化阶段建立的数据库连接、加载的配置文件,在执行环境被复用时不需要重新初始化。这就是为什么要把数据库连接写在 handler 函数外层——它只在冷启动时执行一次。

// 正确:数据库连接在函数初始化阶段建立,被复用
const db = createDatabaseConnection(process.env.DB_URL);
 
export const handler = async (event) => {
  const result = await db.query("SELECT ...");
  return { statusCode: 200, body: JSON.stringify(result) };
};

隔离单位的演进

不同平台用不同的技术实现函数间的隔离,这个选择直接决定了冷启动时间和资源密度:

隔离方式代表平台启动时间隔离强度每台主机密度
容器(MicroVM)AWS Lambda(Firecracker)100ms–500ms强(内核级)
V8 IsolateCloudflare Workers< 5ms中(进程内)极高(数千/主机)
WASMFastly Compute、WasmEdge< 1ms强(沙箱验证)

AWS Lambda 用 Firecracker 微虚拟机实现容器级隔离,安全性极高,但启动需要初始化完整的操作系统环境。Cloudflare Workers 用 V8 的 Isolate 机制——同一个 V8 进程里运行数千个相互隔离的执行上下文,启动一个新 Isolate 的开销和创建一个 JavaScript 对象差不多。WASM 则通过沙箱字节码的静态验证实现隔离,兼顾安全性和启动速度,是下一代 Serverless 隔离的方向。

触发器类型

函数不只响应 HTTP 请求,触发源多样:

触发器类型典型使用场景
HTTP / API GatewayWeb API、Webhook 回调
消息队列(SQS、Kafka)异步任务处理、事件消费
对象存储事件(S3、R2)上传后处理(图片压缩、格式转换)
定时器(Cron)定期数据抓取、报告生成
数据流(Kinesis、DynamoDB Streams)实时数据处理、变更捕获
另一个函数调用函数编排、微服务内部调用

无状态约束与 BaaS

函数执行环境可以随时被销毁和重建,这意味着函数不能把状态存在本地——本地文件系统是临时的,内存里的全局变量在执行环境回收后消失。

所有需要持久化的状态都必须外置到 BaaS(Backend as a Service):

  • 键值存储:Cloudflare KV、AWS DynamoDB、Redis — 简单的映射关系
  • 关系数据库:Cloudflare D1(边缘 SQLite)、Supabase、PlanetScale — 结构化数据
  • 对象存储:Cloudflare R2、AWS S3 — 文件、大型二进制数据
  • 缓存:Upstash(无服务器 Redis)— 热数据加速

这个约束实际上是良性的——它强制推动无状态设计,让函数天然可以水平扩展,不存在”只有这台机器上才有这个会话”的问题。

四、冷启动:Serverless 最重要的工程问题

冷启动的三层开销

冷启动不是单一事件,而是三层叠加:

flowchart TD
    subgraph L1["第一层:基础设施初始化(平台控制,开发者无法优化)"]
        A1[分配主机资源] --> A2[拉取代码包 / 镜像] --> A3[启动隔离环境\n容器 / MicroVM]
    end
    subgraph L2["第二层:运行时初始化(部分可优化)"]
        B1[启动语言运行时\nNode.js / Python / JVM] --> B2[加载标准库和依赖]
    end
    subgraph L3["第三层:函数初始化(开发者控制,优化空间最大)"]
        C1[建立数据库连接] & C2[加载配置文件] & C3[初始化 SDK 客户端]
    end
    L1 --> L2 --> L3 --> Exec[执行 handler]

运行时选型对冷启动的影响

语言运行时的启动时间差异巨大,是函数选型时经常被忽略的因素:

运行时典型冷启动时间主要开销
Python 3.x100–300ms解释器 + 依赖导入
Node.js100–300msV8 启动 + 模块加载
Go50–150ms静态二进制,启动快
Rust30–100ms静态二进制,近乎最优
Java(JVM)500ms–3sJVM 启动 + 类加载
Java(GraalVM Native Image)50–200ms预编译为原生二进制

Java 的 JVM 冷启动问题曾是 Lambda 的严重痛点,AWS 为此专门推出了 SnapStart:在发布新版本时提前执行初始化并拍快照,调用时从快照恢复而不是从零初始化,Java 冷启动时间可以降低 90%。

V8 Isolate 为什么近乎零冷启动

Cloudflare Workers 的冷启动之所以可以做到 5ms 以内,是因为它根本没有第一层和第二层的开销:

  • 没有容器初始化:Workers 运行在 Cloudflare 全球 300+ PoP 的固定 V8 进程里,进程常驻
  • 没有运行时启动:V8 引擎一直在运行
  • 只有 Isolate 创建:在已有的 V8 进程里新建一个执行上下文,开销与创建对象同量级

代价是隔离强度:V8 Isolate 是进程内隔离,不是内核级隔离。Cloudflare 通过严格的 API 限制和代码分析来弥补这个安全边界——Workers 只能访问有限的 Web API 子集,无法做文件系统操作或任意系统调用。

缓解冷启动的手段

当冷启动确实是问题时:

预置并发(Provisioned Concurrency):告诉 Lambda 预先初始化 N 个执行环境,保持热状态。这些环境按小时计费,即使没有请求也付钱——本质上是把 scale-to-zero 的好处换回来,但换来了可预测的低延迟。适合对 P99 有严格要求的场景。

轻量运行时:如果能换到 Go 或 Rust,冷启动时间直接降一个数量级。如果必须用 Java,评估 GraalVM Native Image 或 SnapStart。

缩小部署包:依赖越少,下载和加载越快。用 tree-shaking、只打包必要的模块。

函数初始化优化:把不必要的初始化推迟到第一次使用时(懒加载),减少冷启动时的工作量。

五、三种形态选型:Lambda / Knative / Edge Functions

AWS Lambda:FaaS 托管的标准形态

Lambda 是 Serverless 概念的定义者,2014 年发布,至今仍是生产环境最成熟的选择。

适合:后端 API、事件处理、异步任务、与 AWS 生态(S3、SQS、DynamoDB)深度集成的场景。

主要限制

  • 最长执行时间:15 分钟(超过必须拆分或用 Step Functions 编排)
  • 最大内存:10GB
  • 部署包大小:50MB(直接上传)/ 250MB(解压后)
  • 并发有软限制:默认 1000,需申请提升

锁定程度:高。Lambda 的触发器、IAM 权限模型、VPC 集成都是 AWS 专有,迁移成本高。

Knative:Kubernetes 原生的 Serverless

Knative 是 Google 主导的开源项目,把 scale-to-zero 和事件驱动能力带到 Kubernetes 上,适合私有云或不愿意绑定公有云的场景。

Knative Serving:在 Kubernetes 上实现 scale-to-zero。你部署的不是 Deployment,而是 Knative Service,平台自动管理版本、流量分割和缩容:

apiVersion: serving.knative.dev/v1
kind: Service
metadata:
  name: url-shortener
spec:
  template:
    metadata:
      annotations:
        autoscaling.knative.dev/minScale: "0"   # 允许缩容到 0
        autoscaling.knative.dev/maxScale: "10"
    spec:
      containers:
        - image: ghcr.io/myorg/url-shortener:latest
          resources:
            requests: {memory: 64Mi, cpu: 100m}

Knative Eventing:基于 CloudEvents 规范(CNCF 标准化的事件格式)构建事件驱动管道,解耦事件生产者和消费者。

适合:已有 Kubernetes 基础设施、需要 Serverless 但不能用公有云 FaaS 的场景。代价:你仍然需要运维 Kubernetes 集群,Knative 本身也有一定运维复杂度——这不是”真正的”无服务器,只是”无状态弹性服务”。

Edge Functions:代码跑在离用户最近的地方

Edge Functions 代表了 Serverless 的另一个维度:不只是弹性,还有地理分布。代码不运行在某个数据中心,而是运行在全球分布的 PoP(Point of Presence)节点上,用户的请求就近处理。

Cloudflare Workers:300+ PoP,V8 Isolate 隔离,近零冷启动,绑定 KV / D1 / R2 / Queues / Durable Objects。限制:CPU 时间 50ms(付费可到 30s),无文件系统,API 为 Web 标准子集。

Vercel Edge Functions:基于 Cloudflare Workers,集成在 Vercel 部署流程中,适合 Next.js 的中间件场景(A/B 测试、认证重定向、个性化响应)。

Deno Deploy:基于 Deno 运行时,支持完整的 Web API,TypeScript 原生,比 Workers 的 API 限制少,但 PoP 数量和生态不如 Cloudflare。

选型判断矩阵

维度AWS LambdaKnativeEdge Functions
冷启动100ms–500ms取决于镜像大小< 5ms
执行时长上限15 分钟无限制30 秒(付费)
数据主权选 Region完全自控数据在 Edge,合规风险
厂商锁定高(AWS 生态)低(Kubernetes 标准)中(平台 API 差异)
运维负担极低中(需运维 K8s)极低
适合场景后端逻辑、AWS 集成私有云弹性低延迟、全球分发

六、抽象的代价

厂商锁定的极致

容器已经有相当高的可移植性——一个 Docker 镜像在 AWS、GCP、Azure、私有云都能运行。Serverless 的可移植性则差得多。

Lambda 函数依赖 Lambda 的触发器格式(event 对象结构)、IAM 权限模型、VPC 配置方式,与 Google Cloud Functions 的函数签名就不兼容。Cloudflare Workers 的 KV/D1/Durable Objects 是专有 API,迁移到 AWS Lambda 需要重写数据访问层。

抽象越高,平台提供的”便利”越多,也意味着你与平台的耦合越深。使用 Serverless 前,必须有意识地评估这个锁定是否可接受——是否有明确的迁出方案,还是接受”迁不走”的现实并选择一个可以长期信任的平台?

可观测性缺失

在传统服务里,你可以 SSH 进去看进程状态、查日志文件、用 top 看 CPU——这些都消失了。

冷启动引入延迟噪声:你的 P99 延迟里混入了冷启动时间,这和正常的”业务逻辑慢”是两件事,但在同一个延迟指标里很难区分。

分布式链路变长:一个用户请求可能经过 API Gateway → Lambda A → Lambda B → SQS → Lambda C,每个节点都是独立的执行环境,没有完整的分布式追踪你根本看不清链路。

平台日志结构差异大:AWS CloudWatch Logs、Cloudflare Workers Logs、Vercel Function Logs 格式各异,查询方式不同,很难在多平台场景下统一。

调试困难

本地运行 node handler.js 和实际的 Lambda 执行环境之间有很多语义差距:IAM 权限、VPC 网络、环境变量注入方式、执行时长限制……本地测试通过,部署后出问题是常见模式。

AWS SAM Local 和 Serverless Framework 的 offline 插件可以部分模拟执行环境,但永远不是百分之百还原——特别是涉及 AWS 服务集成(S3 事件、SQS 触发)时,本地模拟的保真度是有限的。

适用边界

Serverless 不适合的场景:

  • 长时间任务:视频转码、大数据处理,执行时间可能超过平台上限(Lambda 15 分钟)
  • 有状态工作负载:WebSocket 长连接、有内存状态的游戏服务器,需要持久连接的场景
  • 可预测的高流量:如果流量稳定且高,常驻实例(甚至预留实例)的单位成本远低于按次计费
  • 极低延迟要求:如果你的 P99 SLO 是 10ms,冷启动(哪怕是 V8 Isolate)也可能是隐患

Serverless 的核心价值在于不可预测流量的弹性事件驱动场景的简洁,超出这两个范围时,用错了工具。

七、直面产品:用 Serverless 构建一个微产品

思维转变

在学完容器和 Kubernetes 之后,你对”部署一个服务”有了固定的心智模型:写 Dockerfile、推镜像、写 Deployment YAML、配 Service、配 Ingress……

Serverless 要求你扔掉这个模型。没有 Dockerfile,没有 nginx,没有 Deployment YAML,没有容量规划。你需要思考的只有一件事:当这个请求到来时,我的代码做什么?

这不是简化——这是把注意力从基础设施拉回到产品。

技术选型:Hono + Cloudflare Workers + KV

社区目前最主流的 Edge Serverless 开发范式是 Hono 框架。Hono 是一个极轻量的 Web 框架(核心 < 12KB),原生支持所有主流 Serverless 平台:

同一份代码,多平台运行:
  Cloudflare Workers  →  wrangler deploy
  Deno Deploy         →  deployctl deploy
  AWS Lambda          →  配合 @hono/node-server
  Vercel Edge         →  配合 @hono/vercel
  Bun / Node.js       →  直接运行

这解决了 Serverless 可移植性的核心问题——业务逻辑不依赖平台,只有部署配置不同。

选择 Cloudflare Workers + KV 作为演示平台,原因是:近零冷启动、免费额度慷慨(每天 10 万请求免费)、KV 存储开箱即用、wrangler CLI 体验流畅。

示例:一个可用的 URL 短链服务

这不是 Hello World,这是一个可以直接投入使用的产品:用户提交长链接,得到一个短链;访问短链,自动跳转。

初始化项目

npm create cloudflare@latest url-shortener -- --template hono
cd url-shortener

src/index.ts(完整实现,不到 50 行):

import { Hono } from "hono";
import { nanoid } from "nanoid";
 
type Bindings = {
  LINKS: KVNamespace;  // Cloudflare KV 绑定
};
 
const app = new Hono<{ Bindings: Bindings }>();
 
// POST /shorten — 创建短链
app.post("/shorten", async (c) => {
  const { url } = await c.req.json<{ url: string }>();
 
  if (!url || !URL.canParse(url)) {
    return c.json({ error: "invalid url" }, 400);
  }
 
  const key = nanoid(8);                          // 生成 8 位随机 key
  await c.env.LINKS.put(key, url, {
    expirationTtl: 60 * 60 * 24 * 30,            // 30 天后过期
  });
 
  const shortUrl = `${new URL(c.req.url).origin}/${key}`;
  return c.json({ short: shortUrl, original: url });
});
 
// GET /:key — 跳转
app.get("/:key", async (c) => {
  const key = c.req.param("key");
  const url = await c.env.LINKS.get(key);
 
  if (!url) return c.notFound();
  return c.redirect(url, 301);
});
 
export default app;

wrangler.toml(声明 KV 绑定):

name = "url-shortener"
main = "src/index.ts"
compatibility_date = "2024-01-01"
 
[[kv_namespaces]]
binding = "LINKS"
id = "your-kv-namespace-id"  # wrangler kv namespace create LINKS 后填入

部署

# 创建 KV namespace
wrangler kv namespace create LINKS
 
# 把上面输出的 id 填入 wrangler.toml
 
# 部署(自动构建 + 推送到 Cloudflare 边缘网络)
wrangler deploy
 
# 输出:
# Published url-shortener (1.23 sec)
# https://url-shortener.your-subdomain.workers.dev

验证

# 创建短链
curl -X POST https://url-shortener.your-subdomain.workers.dev/shorten \
  -H "Content-Type: application/json" \
  -d '{"url": "https://example.com/very/long/path"}'
# {"short":"https://url-shortener.your-subdomain.workers.dev/Ab3xYz89","original":"https://..."}
 
# 跳转测试(-L 跟随重定向)
curl -L https://url-shortener.your-subdomain.workers.dev/Ab3xYz89

整个过程:没有 Dockerfile,没有 nginx,没有 Kubernetes YAML,没有容量规划。你写的每一行代码都是产品逻辑,没有一行是基础设施配置。部署完立刻拿到一个公网可访问的域名。

延伸:同一份代码迁移到其他平台

如果未来需要迁出 Cloudflare,Hono 的路由和中间件代码完全不需要修改,只需要:

# 迁移到 Deno Deploy
# 1. 把 KV 替换为 Deno KV(API 几乎相同)
# 2. deployctl deploy --project=url-shortener src/index.ts
 
# 迁移到 Node.js 自托管
# npm install @hono/node-server
# 把 KV 替换为 Redis 或 PostgreSQL

业务逻辑的可移植性,是选择 Hono 这类框架的核心原因。

关键感受

完成这个微产品之后,你可能会注意到一件事:整个过程中,你从未想过”我的服务器够不够用”这个问题。流量是 1 个用户还是 10 万个用户,不是你需要考虑的——平台负责。这就是 Serverless 对开发者注意力的重新分配。

八、故障剧场

剧场一:冷启动雪崩,P99 延迟飙升

场景:营销活动突然带来大量流量,Lambda 函数从 0 并发瞬间扩展到几百个并发,大量执行环境同时冷启动。

现象:P50 延迟正常(复用热环境),P99 延迟突然跳到几秒(新冷启动环境),前端出现超时报错,但整体错误率不高,令人困惑。

定位

# CloudWatch Metrics 查看 Init Duration(冷启动时间)
aws cloudwatch get-metric-statistics \
  --namespace AWS/Lambda \
  --metric-name InitDuration \
  --dimensions Name=FunctionName,Value=my-function \
  --statistics Maximum,Average \
  --period 60 --start-time ... --end-time ...
 
# 或在 CloudWatch Logs Insights 里查询
fields @timestamp, @initDuration, @duration
| filter @initDuration > 0
| sort @timestamp desc

解决:对延迟敏感的函数启用预置并发;如果是 Java,评估 SnapStart;如果是 Node.js / Python,检查是否可以减少依赖包大小。

剧场二:函数超时,任务中断

场景:一个数据处理 Lambda 函数,平时能在 2 分钟内完成,某天数据量翻倍,执行时间超过 3 分钟(默认超时)被强制终止。

现象:任务无错误日志,但结果不完整;CloudWatch 里看到 Task timed out after 180.00 seconds

定位与处理

# 检查执行时长分布
aws logs filter-log-events \
  --log-group-name /aws/lambda/my-function \
  --filter-pattern "Task timed out"
 
# 短期处理:提高超时限制(最大 15 分钟)
aws lambda update-function-configuration \
  --function-name my-function \
  --timeout 900
 
# 长期处理:如果任务经常超过几分钟,应该用 Step Functions 拆分
# 或者把任务写入 SQS,改为异步处理

教训:Lambda 的 15 分钟上限是设计边界,不是可以无限突破的参数。超过这个范围的任务,本质上不适合 FaaS,应该用 ECS Task、Batch Job 或 Step Functions 编排。

剧场三:并发上限被打满,请求被 Throttle

场景:账户级别的 Lambda 并发上限(默认 1000)被某个高流量函数占满,其他函数的调用开始返回 429 TooManyRequests。

现象:多个函数同时出现错误,但各自的代码没问题,CloudWatch 里看到 Throttles 指标突增。

定位

# 查看各函数的并发使用情况
aws cloudwatch get-metric-statistics \
  --namespace AWS/Lambda \
  --metric-name ConcurrentExecutions \
  --dimensions Name=FunctionName,Value=high-traffic-function \
  --statistics Maximum --period 60 ...
 
# 短期处理:给高流量函数设置预留并发上限,防止它独占账户配额
aws lambda put-function-concurrency \
  --function-name high-traffic-function \
  --reserved-concurrent-executions 500
 
# 长期处理:申请提升账户并发限制,或做函数拆分

剧场四:依赖包过大,冷启动失控

场景:Python Lambda 函数打包了完整的 pandasnumpy,部署包解压后超过 200MB,冷启动时间达到 8 秒。

定位

# 检查部署包大小
aws lambda get-function-configuration --function-name my-function \
  | jq '.CodeSize'
 
# 分析依赖大小
pip show pandas numpy scipy  # 查看各包大小

解决方案(按优先级):

  1. 只导入需要的子模块(from pandas import DataFrame 而不是 import pandas
  2. 把大型依赖打成 Lambda Layer,与函数包分离
  3. 评估是否真的需要 pandas——很多场景用标准库 csv 模块就够了
  4. 改用 Lambda Container 镜像方式打包(最大 10GB),冷启动通过 SnapStart 优化

九、日常排障

关键指标四件套

监控 Serverless 函数,重点看这四个指标:

指标含义告警阈值参考
Duration函数执行时间(P50 / P99)P99 > 超时上限 80% 时告警
Errors执行错误数(未捕获异常)错误率 > 1% 告警
Throttles被限流的调用数任何非零值都要关注
ConcurrentExecutions当前并发数接近账户上限 80% 时告警

对于冷启动监控,单独追踪 InitDuration(Lambda 专有指标),区分”代码慢”和”冷启动慢”。

分布式追踪定位冷启动

AWS X-Ray 可以把一次完整请求的链路(包括冷启动阶段)可视化:

# Lambda 函数里启用 X-Ray 追踪
from aws_xray_sdk.core import xray_recorder
from aws_xray_sdk.core import patch_all
 
patch_all()  # 自动追踪 boto3、requests 等调用
 
@xray_recorder.capture("process_request")
def handler(event, context):
    # X-Ray 会记录这个 segment 的执行时间
    return process(event)

在 X-Ray 服务图里,冷启动会被标注为 Initialization 阶段,与实际的 Invocation 阶段分开显示,一眼看出延迟来自哪里。

本地调试工具

# Cloudflare Workers:wrangler dev 启动本地模拟环境
wrangler dev
# 完整模拟 KV、D1、R2 等绑定,本地测试体验接近真实
 
# AWS Lambda:SAM Local 模拟执行环境
sam local invoke MyFunction --event event.json
sam local start-api  # 模拟 API Gateway + Lambda
 
# 通用:用环境变量切换本地和云端数据源
# 本地跑真实代码 + 模拟数据源,是最可靠的调试方式

常见问题速查

现象优先检查常见原因
函数返回 502API Gateway 配置 / 响应格式handler 返回格式不符合 API Gateway 期望
函数正常但数据没写入IAM 权限执行角色缺少目标服务的写权限
本地正常,部署后报错环境变量 / 依赖版本本地有、云端没有的环境变量;依赖平台差异
冷启动特别慢InitDuration 指标依赖包过大 / JVM 运行时 / 网络初始化(VPC 冷启动额外开销)
429 ThrottleConcurrentExecutions 指标账户并发上限被打满
函数无响应超时配置 + VPC 设置Lambda 在 VPC 内但没有 NAT Gateway,无法访问外部服务

十、往哪走

WASM 运行时:下一代隔离边界

WebAssembly 最初是为浏览器设计的,但它的隔离模型(沙箱字节码、静态验证、无文件系统访问)和 Serverless 的需求几乎完全吻合:安全、快速启动、跨语言。

WASI(WebAssembly System Interface)规范正在把 WASM 推向服务端。Fastly Compute 和 WasmEdge 已经把 WASM 作为首选运行时,冷启动在微秒级别。与 V8 Isolate 相比,WASM 的优势是真正的语言无关性——Go、Rust、Python、C++ 编译后都是同一种字节码格式,在同一个 WASM 运行时里执行,没有语言绑定问题。

Cloudflare Workers 已经支持 WASM 模块,可以在 JavaScript 函数里加载 WASM 二进制处理计算密集型任务(图像处理、加密计算)。这个方向会继续演进。

有状态 Serverless:打破无状态约束

无状态约束是 Serverless 最受诟病的设计之一,但业界正在找到解法:

Cloudflare Durable Objects:每个 Durable Object 是一个有状态的 JavaScript 类,有自己的存储,保证单实例执行——解决了分布式锁、WebSocket 状态管理等问题,同时仍然享受 Edge 部署的优势。

AWS Step Functions:通过状态机编排多个 Lambda 函数,每个函数仍然无状态,但整体流程有状态——等待人工审批、并行处理、错误重试和补偿事务都可以声明式定义。

这两个方向走的是不同的路:Durable Objects 是让单个执行单元有状态,Step Functions 是让函数组合出状态。二者都是对”Serverless = 无状态”这个限制的务实回应。

GPU Serverless:AI 推理的新战场

AI 推理是当前 Serverless 最热的增量场景。Modal、Replicate、Baseten 等平台把 GPU 资源也做成了按调用付费的模式——你不需要租一台 A100 服务器,只需要在代码里声明”我需要一个 GPU 运行这个模型”。

# Modal 的 GPU Serverless 示例
import modal
 
@modal.function(gpu="A10G", image=modal.Image.debian_slim().pip_install("torch"))
def run_inference(prompt: str) -> str:
    import torch
    # ... 模型推理代码
    return result
 
# 调用时按 GPU 秒计费,空闲时不付钱

代价:GPU 的冷启动问题比 CPU 严重得多。拉取模型权重(动辄几十 GB)、初始化 CUDA 上下文,GPU 冷启动可能需要 30–60 秒。这个问题目前靠预置并发和模型缓存来缓解,但还没有根本解法。

平台收敛:边界在模糊

AWS Fargate 是容器吗?是——你部署 Docker 镜像。是 Serverless 吗?也是——你不管理服务器,按 vCPU 秒计费。

Google Cloud Run 同理:容器部署,Serverless 计费,scale-to-zero 支持。

这个模糊是有意为之的趋势。容器作为打包格式,Serverless 作为运营模型,两者在收敛——你打包一个容器,平台负责弹性运行,按实际使用付钱。这个形态既保留了容器的可移植性,又获得了 Serverless 的运营便利,是目前增长最快的部署形态。

Serverless 的终点,可能不是”函数取代服务”,而是”所有服务都按 Serverless 模型运营”——你关心的始终只有代码,资源管理永远是平台的事。

关联