一句话总结
Serverless 不是"没有服务器", 而是"不用再管服务器": 平台按需拉起, 用完销毁, 闲时缩容到零, 只为真正执行的那段时间付费. 它把"服务器"这一层抽象掉了, 代价是冷启动, 调试变难和厂商绑定--用对场景是利器, 用错场景是负担.
前置回顾
第 01 篇的抽象阶梯里, Serverless 处在"集群"之上的一格--它抽象掉了"服务器"和"扩缩容". 本篇就来看清这一格: 它到底让我们不用操心什么, 又把复杂度转移到了哪(第 01 篇的核心追问). 第 03 篇再讲它在 Kubernetes 上怎么落地(Knative).
从一个浪费说起: 凌晨三点, 没人用, 但在付钱
先看传统部署的一个尴尬: 假设有一个"生成月度报表"的服务, 每月就月初被调用几次, 每次跑几分钟, 但为了那几次, 得 7x24 跑着一台(或几台)服务器. 凌晨三点, 月中, 没人用的时候--服务器照样开着, 照样计费.
更普遍的浪费: 为应对流量峰值, 按峰值容量备机器, 但大部分时间流量远低于峰值--多备的容量全在空转烧钱.
问题的本质: 为"容量"付费(机器开着就计费), 而不是为"实际使用"付费. 容量和使用之间的差, 就是被浪费的钱和资源. Serverless 要消灭的正是这个差.
传统服务器像包月租了一整间办公室: 不管用不用, 用多少, 月租照付.
Serverless 像按分钟计费的共享会议室: 要用时随时有, 进去开会才计费, 走了立刻不收费.
对那种"偶尔才开个会"的需求, 后者省得多.
Serverless 到底是什么: 三个核心特征
"Serverless(无服务器)"是个有点误导的名字--服务器当然还在, 只是不再需要感知和管理它们. 判断一个东西是不是真 Serverless, 看它是否同时具备三个特征:
| 特征 | 含义 | 对比传统 |
|---|---|---|
| 事件驱动 | 代码由"事件"触发执行(HTTP 请求, 消息到达, 文件上传, 定时器) | 传统服务常驻进程, 一直监听 |
| 缩容到零 | 没有请求时, 实例数可以降到 0--完全不占资源 | 传统服务至少留 1 个实例常驻 |
| 按用量付费 | 只为代码实际执行的时间付费(常精确到毫秒), 不执行不花钱 | 传统按"机器开着的时长"付费 |
这三点环环相扣: 因为事件驱动, 平台知道"现在没事干"→ 于是缩容到零 → 于是不执行就不计费. 三者合起来, 才实现了"为使用而非为容量付费".
两种形态: FaaS 与 BaaS
Serverless 通常以两种形态出现, 分工不同:
| FaaS(函数即服务) | BaaS(后端即服务) | |
|---|---|---|
| 全称 | Function as a Service | Backend as a Service |
| 提供什么 | 一段函数代码(处理某类事件) | 什么都不写, 直接用现成的后端能力 |
| 例子 | AWS Lambda, 阿里云函数计算, Google Cloud Functions | 托管数据库, 认证服务(Auth0), 对象存储(S3), 消息队列 |
| 一句话 | "把代码跑成 Serverless" | "把整个后端组件外包成 Serverless" |
FaaS 是 Serverless 最具代表性的形态--也是本篇和第 03 篇 Knative 的重点. 它的模型极简: 写一个函数, 声明它被什么事件触发, 平台负责其余一切(拉起, 扩缩, 销毁, 计费).
|
绕不开的代价: 冷启动
"缩容到零"听起来很美, 但它带来 Serverless 最著名的痛点--冷启动(Cold Start). 逻辑很直接: 缩容到零意味着实例数 = 0, 这时来了一个请求, 平台得现场拉起一个新实例, 加载运行时, 加载代码, 初始化(连数据库, 读配置), 然后才能开始处理请求. 这段"从零准备好"的时间, 就是冷启动延迟(几百毫秒到几秒). 如果实例已经是热的(刚处理过请求, 还没被销毁), 直接处理 = 热启动, 很快.
这是个无法回避的根本权衡: 想要"闲时零成本", 就得接受"被唤醒时有延迟". 对延迟不敏感的场景(异步处理, 定时任务, 低频接口), 冷启动无所谓; 但对要求稳定低延迟的在线核心链路(比如用户下单的主接口), 偶尔几秒的冷启动可能不可接受.
缓解手段有: 保留少量"预热实例"(但这就牺牲了一部分"缩容到零"的省钱), 用更轻量的运行时, 减小代码体积和初始化开销. 缓解冷启动往往意味着放弃一部分省钱--又是复杂度/成本的权衡(第 01 篇).
适用边界: 什么场景适合, 什么场景别碰
这是本篇最该记住的部分. Serverless 不是万能的, 它有清晰的"甜区"和"雷区":
| 适合 Serverless 的场景 | 不适合的场景 |
|---|---|
| 流量波动大 / 不可预测(自动扩缩省心) | 稳定高并发(常驻服务器单位成本更低) |
| 事件驱动的零散任务(图片处理, Webhook, 定时 ETL) | 对延迟极敏感的核心链路(冷启动受不了) |
| 低频但需要随时可用(月度报表那种) | 长时间运行的任务(FaaS 通常有执行时长上限) |
| 快速试验 / MVP(不想先搭一套基础设施) | 需要持久连接 / 大量本地状态(函数无状态, 易销毁) |
成本陷阱: 高频稳定负载用 Serverless 可能更贵
一个常见误区是"Serverless 总是更便宜". 按用量付费的单价, 通常比常驻服务器的单价高--它贵在"灵活". 算笔账: 如果服务几乎一直在满负荷跑, 那"按执行时间 x 较高单价"很可能超过"包月一台机器".
Serverless 省钱的前提是负载有明显的波峰波谷或低频, 让"缩容到零"真正省下大量闲置成本. 负载越平稳越饱和, Serverless 的成本优势越小甚至倒挂. 选型前务必按真实流量曲线算账, 别想当然.
其他要权衡的代价
除了冷启动和成本, Serverless 还转移来几项复杂度(回应第 01 篇"复杂度守恒"):
- 调试和可观测性变难: 函数转瞬即逝, 无固定实例, 传统"登录服务器看日志"行不通, 更依赖可观测性系列那套指标/日志/追踪--而且追踪一条跨多个函数的请求尤其有挑战.
- 厂商绑定(Vendor Lock-in): 各家 FaaS 的函数签名, 事件格式, 配套服务都不同, 深度用了某家就很难迁走. 这正是第 03 篇 Knative 想缓解的--把 Serverless 建在开放的 K8s 上.
- 无状态约束: 函数随时被销毁, 不能依赖本地内存/磁盘保存状态, 状态必须外置(数据库, 缓存).
- 执行时长 / 资源上限: FaaS 平台通常限制单次执行时长和内存, 跑不了重型长任务.
快速回顾
- Serverless 不等于没有服务器, 而是"不用管服务器"; 要消灭的是"为容量付费而非为使用付费"的浪费
- 三大特征: 事件驱动, 缩容到零, 按用量付费--环环相扣, 共同实现"为使用付费"
- 两种形态: FaaS(把函数跑成 Serverless, 如 Lambda), BaaS(把后端组件外包成 Serverless, 如托管数据库/认证)
- 冷启动是"缩容到零"的代价: 实例从 0 拉起需时间; 延迟敏感的核心链路要谨慎, 缓解手段往往以牺牲省钱为代价
- 甜区: 流量波动大/事件驱动零散任务/低频/快速试验; 雷区: 稳定高并发(可能更贵), 延迟敏感, 长任务, 强状态
- 其他代价: 调试与可观测变难, 厂商绑定, 无状态约束, 执行时长上限--复杂度守恒
Serverless 和容器, Kubernetes 是替代关系吗?
不是替代, 是不同抽象层次的选择, 而且常常叠加. Kubernetes 管理容器化的常驻服务, Serverless 运行事件驱动的临时函数--很多系统两者都用: 核心常驻服务跑在 K8s 上, 边缘的零散任务(图片处理, Webhook, 定时清理)用 FaaS.
更妙的是, Serverless 本身可以建在 Kubernetes 之上(第 03 篇 Knative 就是), 底层还是容器, 只是上面套了一层"自动扩缩到零 + 事件驱动"的抽象. 回到第 01 篇: 它们是抽象阶梯上的不同格子, 不是二选一.
函数这么小, 又频繁冷启动, 会不会比单体还难维护?
有这个风险, 是真实存在的反模式. 如果把一个系统拆成几百个互相调用的微小函数, 会得到一个"分布式单体"的更碎版本: 调用关系复杂, 端到端追踪困难, 冷启动叠加, 认知负荷爆炸.
所以 FaaS 不是"拆得越碎越好"--它适合边界清晰, 相对独立, 事件触发的任务单元. 对于内聚的核心业务, 常驻服务(K8s 上的微服务)往往是更好的选择. 粒度的把握, 本身就是 Serverless 用得好不好的关键.
动手练习
- 创建最简 FaaS 函数: 在任意一家云上创建一个最简单的 FaaS 函数(如"返回当前时间"), 用 HTTP 触发它, 体验"没写任何服务器代码就有了一个可访问的接口".
- 感受冷启动: 让函数闲置一段时间后再调用, 留意第一次明显比后续慢--亲身感受冷启动.
- 成本测算: 给一个真实场景算账: 假设某服务日均流量曲线已知, 粗略对比"常驻一台小机器"和"按 FaaS 用量付费"的月成本, 找出二者的盈亏平衡点.
- 甜区/雷区判断: 列出工作中 2-3 个任务, 用本篇的"甜区/雷区"表判断它们适不适合 Serverless, 并说明理由.
- 复杂度转移分析: 思考一个了解的 Serverless 应用, 它把哪些复杂度转移给了平台? 又留下了哪些新麻烦(调试, 绑定等)?