Serverless 函数

Serverless 函数让前端团队可以在不直接运维服务器的前提下交付 API、Webhook、鉴权和编排逻辑,但仍然需要认真处理运行时、状态、延迟和成本。

Serverless 函数

为什么前端工程师需要认真理解它

很多资料会把 Serverless 讲成一个云厂商计费模型。

这种说法不算错,但对前端工程师不够有用。

更实用的理解是:

Serverless 是一种执行模型。

平台负责服务器生命周期、扩缩容和一部分运行时管理。

团队负责交付一小段一小段的后端逻辑。

这也是为什么它会持续进入前端工程师的工作范围。

今天的前端项目很少只负责渲染页面。

团队通常还要处理:

  • 表单提交
  • 鉴权校验
  • Webhook 接收
  • 短期令牌交换
  • 缓存失效
  • 内容聚合
  • 轻量 BFF
  • 定时任务

在 Next.js App Router 里,这些需求常常落在 route.ts

在 Vercel、AWS、Cloudflare、Netlify 这些平台里,它们又会以不同的 Serverless 运行时出现。

如果你只记住一句“无服务器”,很容易误判它适不适合自己的场景。

如果你把它看成“由平台托管、按事件触发、以小执行单元为边界的后端模型”,很多工程判断就会清楚得多。

一个更准确的定义

Serverless 不是没有服务器。

服务器仍然存在。

只是它们不再由你的团队直接配置、扩容、打补丁和维护。

这个区分很重要。

因为 Serverless 并没有消灭后端问题。

它只是把问题的重心从“机器管理”转成了“执行模型设计”。

你仍然要关心:

  • 延迟
  • 并发
  • 超时
  • 重试
  • 幂等
  • 数据边界
  • 权限控制
  • 监控与告警
  • 成本

平台通常会替你处理:

  • 机器准备
  • 基础扩缩容
  • 部分请求路由
  • 运行环境启动
  • 部署承载

但团队仍然要自己负责:

  • 代码逻辑
  • 运行时选择
  • 数据访问方式
  • 依赖体积
  • 超时策略
  • 安全边界
  • 可观测性

所以真正的问题不是“要不要服务器”。

真正的问题是:

这个需求是否适合一个由平台管理、按请求或事件触发、倾向无状态的小执行单元。

执行模型:它到底是怎么工作的

可以先用一个简化流程理解:

  1. 请求或事件到达平台。
  2. 平台找到一个可用执行环境,或者新建一个执行环境。
  3. 函数代码开始执行。
  4. 返回响应,或者完成事件处理。
  5. 执行环境可能被复用,也可能被销毁。

这个流程看起来简单,但会直接影响工程设计。

无状态是默认前提

很多人第一次接触 Serverless,会把“无状态”理解成“每次执行后什么都不剩”。

这并不准确。

像 AWS Lambda 这样的服务,明确支持执行环境复用。

这意味着:

  • 函数外层初始化的对象,后续调用可能继续复用
  • 某些连接可能继续存活
  • 某些临时文件可能暂时还在

这对性能是好事。

但它只是优化手段。

不是持久化承诺。

你不能假设下一次请求一定命中同一个执行环境。

所以持久状态仍然应该放在外部系统里,例如:

  • 数据库
  • 对象存储
  • 队列
  • 缓存
  • KV 存储

冷启动是执行模型的一部分

当平台需要为一个请求准备新的执行环境时,就会发生冷启动。

冷启动成本通常来自:

  • 运行时准备
  • 代码装载
  • 依赖初始化
  • 网络连接建立

所以在 Serverless 里,包体积和初始化逻辑会比传统常驻服务更敏感。

不是因为平台“慢”。

而是因为它的执行边界更短、更频繁。

复用执行环境有价值,但不能当成契约

如果执行环境被复用,性能可能会更好。

常见收益包括:

  • SDK client 不需要重复初始化
  • 数据库连接可以复用
  • 配置解析只做一次
  • 短时缓存可能还在

但正确性不能建立在这些条件上。

系统要先能在“完全重新启动”的情况下正常工作。

然后再利用复用带来的性能收益。

前端团队最容易混淆的点:Node Serverless 和 Edge Runtime 不是一回事

很多文章会把它们混在一起讲。

实际做项目时,最好把它们明确分开。

Node 型 Serverless 函数

常见代表:

  • AWS Lambda 的 Node.js 运行时
  • Vercel Functions 的 Node.js runtime
  • Netlify Functions

这一类运行时的特点通常是:

  • Node.js 兼容性更完整
  • npm 生态兼容性更强
  • 更适合依赖较重的后端逻辑
  • 冷启动成本通常高于基于 isolate 的 edge 运行时

适合的场景包括:

  • 依赖复杂 SDK
  • 需要完整 Node API
  • 数据库客户端依赖 Node 能力
  • PDF 生成
  • 内容转换
  • 逻辑较重的 API

Edge Runtime

常见代表:

  • Cloudflare Workers
  • Vercel Edge Functions
  • Next.js 中显式选择 edge runtime 的路由

这一类运行时通常:

  • 启动更轻
  • 更接近用户
  • 延迟更低
  • API 面更窄
  • 并不支持完整的 Node.js 能力

适合的场景包括:

  • 鉴权前置判断
  • 重定向
  • 地理位置相关逻辑
  • Header 和 Cookie 改写
  • 简单个性化
  • 轻量 API 组合

这里的工程判断并不复杂:

如果你更在意全量 Node 兼容和复杂逻辑承载,优先考虑 Node 型 Serverless。

如果你更在意低延迟入口处理,而且代码确实能适配更窄的运行时,Edge 才值得优先考虑。

放到 Next.js 语境里怎么理解

这个项目本身就是 Next.js。

所以把 Serverless 放到 Next.js 语境里理解会更实际。

在 App Router 中,Route Handlers 是定义后端请求处理的标准方式。

这让前端团队可以不额外拆出一个服务,就先交付一层轻量后端逻辑。

典型用途有:

  • 表单提交接口
  • Session 辅助接口
  • Webhook 验签
  • 上传签名
  • 内容聚合接口
  • 面向页面的 BFF

这里最常见的误区是:

以为只要放进 Next.js,就天然是一个“适合 Serverless”的逻辑。

其实不是。

你仍然要判断:

  • 这段逻辑是否需要完整 Node 环境
  • 是否对冷启动敏感
  • 是否依赖大体积 SDK
  • 是否可能超时
  • 是否应该拆成后台任务

Node runtime 通常是更稳妥的默认值。

只有当需求真的适合 edge 时,再切换到更轻的运行时。

更适合 edge 的逻辑

  • 简单鉴权判断
  • 区域感知重定向
  • 轻量请求改写
  • 少量数据聚合

更适合 Node Serverless 的逻辑

  • 使用 Node 专属依赖
  • 数据库操作较复杂
  • 逻辑较重的内容处理
  • 需要更强兼容性的第三方 SDK
  • 生成文件或处理较大 payload

前端工程里的典型使用场景

1. BFF 接口

BFF 的核心价值不是“转发一下请求”。

而是把后端数据整理成页面真正需要的形状。

一个好的 Serverless BFF 往往会做这些事:

  • 鉴权
  • 聚合多个后端服务
  • 去掉不该暴露给浏览器的字段
  • 做轻量缓存控制
  • 输出更稳定的前端契约

这样做的结果通常是:

  • 客户端逻辑更简单
  • 页面拿到的数据更干净
  • 后端接口变更对页面的影响更可控

2. 密钥相关操作

浏览器不能直接持有高权限密钥。

这类操作很适合放进 Serverless:

  • 第三方 API 的安全调用
  • 上传签名
  • 短期 Token 交换
  • 带权限校验的窄接口

但这里要避免一个误区:

不是所有请求都值得“再包一层”。

真正应该迁移到 Serverless 的,是那些需要服务端保密信息或安全校验的操作。

3. Webhook 接收

Webhook 天然是事件驱动的。

所以很适合 Serverless。

常见例子:

  • 支付回调
  • CMS 发布事件
  • Git 仓库事件
  • 身份系统通知

这类函数通常应该优先完成:

  • 验签
  • 基本校验
  • 幂等判断

如果后续任务很重,就应该尽快交给队列或后台流程,而不是把整个流程都塞在一次请求里。

4. 定时任务

很多平台都支持 Cron 触发。

这让 Serverless 不只用于用户请求。

它也能承担:

  • 缓存刷新
  • 数据同步
  • 报表生成
  • 清理过期数据

这时要特别注意失败重试和告警。

因为这类任务经常没有用户在前面盯着页面是否正常返回。

性能问题:真正需要关心的不是“快不快”,而是“哪一段在慢”

初始化成本要尽量小

很多冷启动问题,本质上是初始化问题。

比如:

  • 引入了过大的 SDK
  • 一个函数承担了过多职责
  • 在模块加载阶段做了太多工作
  • 每次都建立昂贵连接

常见优化方向:

  • 保持 bundle 小
  • 按需引入依赖
  • 不把所有逻辑塞进一个 handler
  • 把可复用初始化放到函数外层
  • 延迟那些不是每次都需要的初始化

数据库连接是一个高频坑

不少团队第一次做 Serverless API,会在每次调用里新建数据库连接。

请求量一上来,就会出现连接数压力。

更稳妥的方案通常包括:

  • 结合平台特性的连接复用
  • 使用适合 Serverless 的连接池方案
  • 在合适场景下使用 HTTP 型数据库驱动
  • 通过 data proxy 或托管中间层吸收连接压力

这里没有一句通用口号可以解决问题。

必须结合数据库类型、平台能力和流量特征来判断。

把用户请求和重任务拆开

如果用户请求只需要完成输入校验、写入一条记录、触发后续处理,那么就不要在同步请求里把所有工作做完。

更健康的模式通常是:

  1. 校验输入
  2. 写入最小必要数据
  3. 投递队列或触发后台流程
  4. 尽快返回响应

这样做的好处是:

  • 响应时间更稳定
  • 超时风险更低
  • 失败边界更清晰
  • 后台任务更容易重试

可靠性问题:Serverless 不是“自动扩容”四个字就结束了

幂等要提前设计

真实系统里,重试会反复出现。

重试可能来自:

  • 平台
  • Webhook 发送方
  • 客户端
  • 中间网络

所以只要函数会产生副作用,就应该认真考虑幂等。

常见手段:

  • 用事件 ID 去重
  • 用 upsert 代替盲目 insert
  • 标记已处理记录
  • 让队列消息可重复消费

如果没有幂等设计,Serverless 经常会把“偶发重试”放大成“重复写入、重复扣费、重复发消息”。

部分失败要有模型

函数可能在很多中间状态下失败:

  • 写库前失败
  • 写库后、返回前失败
  • 调用下游服务一半时失败
  • 响应超时但后台逻辑已部分执行

所以工程上不能只有 happy path。

你需要考虑:

  • 事务边界
  • 补偿机制
  • 重试策略
  • 死信处理
  • 关联日志

可观测性必须从第一天开始做

Serverless 的一次执行很短。

分布又比较散。

如果日志和指标做得差,定位问题会非常慢。

至少应该准备:

  • request ID
  • 结构化日志
  • 错误聚合
  • 延迟指标
  • 调用次数指标
  • 下游依赖耗时

否则看起来是“平台自动管理”,实际上会变成“问题很难复盘”。

安全问题:换了运行方式,不代表天然更安全

Serverless 不会自动帮你完成安全设计。

它只是提供了更方便部署服务端代码的方式。

常见安全要点包括:

  • 在边界处做输入校验
  • 验证 Webhook 签名
  • 使用最小权限凭证
  • 把密钥放在平台的 secret 管理里
  • 响应里不要泄露内部错误细节
  • 给容易被滥用的接口做限流

还有一个很现实的问题:

很多前端团队第一次接触服务端敏感逻辑,就是从 Serverless 开始。

这时更需要安全评审,而不是因为“代码量小”就掉以轻心。

成本:按调用付费不等于一定省钱

“按使用付费”是 Serverless 的重要优点。

但这句话不应该被理解成“任何场景都更便宜”。

真实成本通常和这些因素一起决定:

  • 调用次数
  • 执行时长
  • 内存或 CPU 规格
  • 网络出口
  • 子请求数量
  • 绑定的数据库、队列、缓存等服务

它通常更适合:

  • 流量波动明显的业务
  • 突发请求
  • 还不值得养重运维体系的项目
  • 需要快速验证的功能

它未必适合:

  • 长时间常驻的重计算任务
  • 大量持续运行的后台管道
  • 明显更适合容器或专用服务的工作负载

做决策时,最好基于真实调用链和平台账单模型做估算,而不是凭印象选型。

常见误区

误区一:把 Serverless 当成架构替代品

Serverless 可以减少基础设施管理。

但它不能替代系统设计。

请求边界、数据边界、缓存策略、后台任务拆分,这些问题仍然需要认真设计。

误区二:一个函数里塞太多职责

职责过重的函数通常会带来:

  • 体积更大
  • 冷启动更重
  • 测试更困难
  • 失败影响面更大

职责单一、边界清晰的函数通常更容易维护。

误区三:本地能跑通,就默认线上也没问题

线上环境和本地环境经常不一样。

真正影响线上稳定性的,往往是:

  • 超时限制
  • edge 运行时 API 不兼容
  • 包体积限制
  • 连接数压力
  • 区域网络延迟

误区四:把长任务硬塞进同步请求

如果任务时间长、容易失败、需要多步重试,那么它大概率更适合队列、工作流引擎或容器型后台任务,而不是同步 Serverless 请求。

面试表达可以怎么说

一个比较稳妥、专业、但不空泛的回答可以是:

Serverless 函数是一种由平台托管的后端执行单元,适合处理事件驱动或请求驱动的轻量后端逻辑。

对前端团队来说,它常用于 BFF、Webhook、Token 交换、上传签名和轻量编排,因为它能降低基础设施负担,并且对突发流量扩展得比较自然。

但它并不意味着不需要后端设计。

实际工程里仍然要处理冷启动、无状态设计、数据库连接、重试幂等、可观测性,以及 Node runtime 和 Edge runtime 的选择。

如果能把这些点说清楚,说明你理解的是工程边界,而不是宣传口号。

实践检查清单

在决定某个需求是否要用 Serverless 前,可以先问自己:

  • 这是请求驱动还是事件驱动?
  • 这段逻辑能否保持边界清晰、职责单一?
  • 它是否需要完整 Node.js 兼容?
  • 它真的需要 edge 级低延迟吗?
  • 持久状态放在哪里?
  • 失败后如何重试?
  • 是否需要幂等?
  • 日志、指标、追踪怎么做?
  • 目标平台的超时、体积、子请求限制是什么?

如果这些问题没有答案,通常说明函数边界还没设计好。

延展阅读