Serverless
概念
狭义的 Serverless,就是开发人员的 “懒人部署工具” —— 你只需要把写好的代码(比如一个处理订单的函数、一个图片压缩接口)直接上传到平台,不用管服务器怎么买、怎么配,也不用操心代码跑在哪个容器里、要不要扩容。平台会自动帮你运行代码、应对流量高峰(比如秒杀时自动加资源)、流量低时缩容,而且只在代码真正运行的时候收费,空闲时一分钱不花。
广义的 Serverless,则是一种 “按需用资源” 的架构思想 —— 它不只是部署工具,而是指导你怎么设计整个应用。就像微服务把大应用拆成小服务一样,Serverless 鼓励把应用拆成一个个独立的函数,每个函数只干一件事(比如 “生成订单”“发送短信”“解析日志”);同时把计算和存储彻底分开(函数只管计算,数据存在专门的存储服务里),再用 “事件” 驱动函数运行(比如用户下单这个事件,自动触发 “生成订单” 函数)。整个架构里,你看不到一台需要维护的服务器,只关心 “函数做什么” 和 “什么事件触发它”。
架构
Serverless 的本质是基于 FaaS 运行函数、依托 BaaS 调用能力,无需关心底层服务器。
-
Backend-as-a-Service(BaaS): “后端能力的‘即插即用’”
- BaaS 是云端提供的 “现成后端功能包” —— 它把应用开发中通用的核心能力(比如存数据、用户登录、发消息)做成标准化的 API 服务,你不用自己写数据库逻辑、搭认证系统,直接调用这些云端 API 就能用。这些服务自带 “自动扩容” 和 “免运维” 属性:数据存满了自动加空间,用户登录请求多了自动扛住,你只用管 “怎么调用”,不用管 “服务怎么跑”。
- 简单说:BaaS 是 “别人做好的后端轮子,你直接装到自己车上用”,核心是用云端 API 替代自研后端功能。
-
Functions-as-a-Service(FaaS): “代码的‘按需运行’”
-
FaaS 是 “只跑代码、不管机器” 的事件驱动计算服务 —— 你把业务逻辑写成 “一个函数”(比如 “用户下单后发通知”“上传图片后压缩”),上传到平台;当特定事件发生时(比如用户下了单、有图片上传),平台会自动启动一个 “临时容器” 运行这个函数,跑完就销毁容器。整个过程你不用买服务器、不用配环境,函数要处理的请求多了,平台自动多开容器;没请求时,一个容器都不跑,而且只按函数实际运行的时间 / 次数收费。
-
简单说:FaaS 是 “把代码丢给平台,让它在事件发生时帮你跑,跑完就撤”,核心是用 “函数 + 事件” 替代传统的 “服务 + 服务器”
-
事件源(HTTP/消息队列/...)
↓ 产生事件
触发器
↓ 转发事件
FaaS控制器
↓ 调度函数
函数实例(容器)
↓ 执行handler(event,ctx)
BaaS服务(云存储/数据库/...)
优势
一、 成本优势
- 按需付费,精准控本采用按执行时间 + 调用次数的计费模式,仅在函数实际运行时产生费用,无请求流入时零计费,大幅削减业务低峰期的闲置资源成本。
- 实例零缩容,成本完全归零当函数长期无触发事件时,运行实例会自动缩减至零,彻底杜绝空跑资源的成本消耗,相比传统服务器 / 容器的 “闲置仍计费” 模式,实现极致的成本优化。
二、 弹性扩缩容能力
- 极致弹性,轻松应对潮汐流量支持从 0 到数千并发的极速扩展,可在毫秒级响应流量峰值(如秒杀、活动促销场景);流量回落时自动缩容,确保资源利用率始终处于最优状态。
- 免运维扩缩,无需资源规划扩缩容逻辑由平台完全托管,无需开发 / 运维人员手动配置扩缩容阈值、预留资源容量,彻底摆脱传统架构中 “资源预估不准” 导致的浪费或过载问题。
三、 事件驱动架构优势
- 原生事件集成,无缝对接上下游与云产品生态深度联动,可直接监听对象存储(TOS)、消息队列、数据库变更等多种事件源,无需额外开发适配逻辑,是构建事件驱动架构的原生首选方案。
- 简化开发链路,降低架构复杂度函数由事件直接触发执行,省去传统架构中 “事件监听 - 消息转发 - 服务部署” 的多层适配环节,大幅缩短业务开发链路,提升迭代效率。
劣势
。。。冷启动
产品
开源自部署 FaaS(适合私有化场景)
- OpenFaaS轻量开源 FaaS,支持容器镜像部署,可运行在 Kubernetes 上,兼容多语言,适合中小团队在私有集群中快速搭建 FaaS 能力。
- Knative由谷歌主导的开源 Serverless 框架,基于 Kubernetes,核心包含 “服务部署” 和 “事件触发” 能力,可看作 Kubernetes 原生的 FaaS 扩展,适合已有 K8s 集群的团队。
Faas
控制面
在 FaaS(函数即服务)架构中,控制面(Control Plane) 是 FaaS 平台的大脑中枢,负责管理、调度和协调函数的全生命周期,不直接处理函数的业务请求,只做 “决策和管控”
- Regional Server:单地区控制面入口,管理本地区元数据存储;对接 K8s 集群,负责任务发布及 Deployment 的创建、扩缩容。
- GlobalHub:全局控制面与各地区的流量转发入口;管理函数代码下载、函数实例初始化与状态上报,收集实例日志和监控数据。
- EdgeHub:单地区控制面流量接入层;与 GlobalHub 维持 WebSocket 连接,序列化 / 反序列化 HTTP 请求,转发全局与地区间的消息。
- FaaS Build:函数构建组件;负责代码压缩、打包、缓存预置,编译阶段安装用户依赖。
数据面
与控制面对应的是数据面(Data Plane) ,负责实际运行函数代码、处理用户请求。
存在的问题?
用户请求 → Gateway 组件 → Gateway/Dispatcher 发现:
- ❌ 实例数为 0(流量长期为 0 被缩容);
- ❌ 现有实例并发已满,无法承接新请求;→ 触发冷启动流程:Dispatcher 通知 K8s 集群 → K8s 创建新的函数容器实例 → 实例启动完成 → Gateway 转发请求到新实例 → 执行并返回。👉 时延 = “实例启动耗时 + 请求转发 + 函数执行耗时”,核心额外开销就是 “容器实例启动”。
| 优化手段 | 核心动作 | 优化目标 | 典型耗时优化效果 |
|---|---|---|---|
| 镜像代码分离 | 拆分镜像,缓存基础镜像,仅下载代码 | 砍掉大镜像拉取的耗时 | 300ms → 50ms |
| 函数实例预热 | 提前启动实例,放入预热池 | 彻底消除实例启动耗时 | 500ms → 0ms(无启动) |
| 冷启动实例调度 | 智能选节点,并行调度,预留资源 | 减少调度 / 资源竞争耗时 | 200ms → 50ms |
触发器
FaaS 触发器就是能触发函数运行的各类事件源,包括 HTTP 请求、定时任务、消息队列消息、存储 / 数据库变更等。
HTTP
这里给出HTTP 触发器最核心的极简版实现,只保留「请求接收→参数封装→函数调用→响应返回」的核心链路,剔除所有非必要的细节(如可信 IP、复杂错误处理、多事件类型等)
package main
import (
"context"
"encoding/json"
"net/http"
"time"
)
// 1. 定义核心结构体:HTTP请求参数 + 函数响应
type HTTPRequest struct {
Method string `json:"method"`
Path string `json:"path"`
Query map[string]string `json:"query"`
Body []byte `json:"body"`
Headers map[string]string `json:"headers"`
}
type FunctionResponse struct {
StatusCode int `json:"status_code"`
Headers map[string]string `json:"headers"`
Body []byte `json:"body"`
}
// 2. 定义函数处理器签名(核心:统一入口)
type FunctionHandler func(ctx context.Context, req *HTTPRequest) (*FunctionResponse, error)
// 3. 核心:HTTP触发器实现(仅保留核心链路)
func HTTPTrigger(handler FunctionHandler) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// 核心步骤1:封装HTTP请求为结构化参数
req := &HTTPRequest{
Method: r.Method,
Path: r.URL.Path,
Query: make(map[string]string),
Headers: make(map[string]string),
}
// 简化版:解析Query参数
for k, v := range r.URL.Query() {
if len(v) > 0 {
req.Query[k] = v[0]
}
}
// 简化版:读取Body
body := make([]byte, r.ContentLength)
if r.ContentLength > 0 {
r.Body.Read(body)
}
req.Body = body
// 核心步骤2:调用用户函数(带超时控制)
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
res, err := handler(ctx, req)
// 核心步骤3:封装并返回响应
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// 设置响应Header + 状态码 + Body
for k, v := range res.Headers {
w.Header().Set(k, v)
}
w.WriteHeader(res.StatusCode)
w.Write(res.Body)
}
}
// 4. 示例用户函数(业务逻辑)
func HelloHandler(ctx context.Context, req *HTTPRequest) (*FunctionResponse, error) {
name := req.Query["name"]
if name == "" {
name = "world"
}
respBody, _ := json.Marshal(map[string]string{
"message": "hello " + name,
"path": req.Path,
})
return &FunctionResponse{
StatusCode: http.StatusOK,
Headers: map[string]string{"Content-Type": "application/json"},
Body: respBody,
}, nil
}
// 5. 启动服务(核心入口)
func main() {
// 核心:将用户函数绑定到HTTP路径
http.HandleFunc("/hello", HTTPTrigger(HelloHandler))
// 启动HTTP服务器
http.ListenAndServe(":8080", nil)
}
MQ
MQ 触发器核心版本
package main
import (
"context"
"encoding/json"
"fmt"
"time"
// 模拟MQ客户端(实际用对应MQ的SDK:如Kafka/RocketMQ/Pulsar)
"github.com/segmentio/kafka-go"
)
// 1. 定义核心结构体:MQ消息参数 + 函数响应
type MQMessage struct {
Topic string `json:"topic"` // MQ主题
Partition int `json:"partition"`// 分区
Offset int64 `json:"offset"` // 偏移量
Key string `json:"key"` // 消息Key
Body []byte `json:"body"` // 消息体
Headers map[string]string `json:"headers"` // 消息头
}
// 复用HTTP触发器的响应结构体(核心逻辑通用)
type FunctionResponse struct {
StatusCode int `json:"status_code"` // 仅标记处理结果(成功/失败)
Message string `json:"message"` // 处理结果描述
}
// 2. 定义MQ函数处理器签名(核心:统一入口)
type MQFunctionHandler func(ctx context.Context, msg *MQMessage) (*FunctionResponse, error)
// 3. 核心:MQ触发器实现(仅保留核心链路)
func MQTrigger(handler MQFunctionHandler, brokerAddr, topic string) {
// 核心步骤1:创建MQ消费者(连接MQ)
reader := kafka.NewReader(kafka.ReaderConfig{
Brokers: []string{brokerAddr},
Topic: topic,
GroupID: "faas-mq-group", // FaaS消费组
MinBytes: 10e3, // 10KB
MaxBytes: 10e6, // 10MB
})
defer reader.Close()
fmt.Printf("MQ触发器启动:监听主题 %s\n", topic)
// 核心步骤2:循环消费MQ消息
for {
// 拉取MQ消息
m, err := reader.ReadMessage(context.Background())
if err != nil {
fmt.Printf("拉取MQ消息失败:%v\n", err)
time.Sleep(1 * time.Second)
continue
}
// 封装MQ消息为结构化参数(核心:和HTTP触发器的"解析请求"逻辑等价)
msg := &MQMessage{
Topic: m.Topic,
Partition: m.Partition,
Offset: m.Offset,
Key: string(m.Key),
Body: m.Value,
Headers: make(map[string]string),
}
// 解析消息头
for _, h := range m.Headers {
msg.Headers[h.Key] = string(h.Value)
}
// 核心步骤3:调用用户函数(带超时控制)
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
res, err := handler(ctx, msg)
// 核心步骤4:处理函数执行结果(MQ触发器核心差异:需确认/重试/死信)
if err != nil || (res != nil && res.StatusCode != 200) {
// 处理失败:记录日志 + 重试/发死信队列(简化版仅打印)
errMsg := fmt.Sprintf("处理消息失败:offset=%d, err=%v", m.Offset, err)
fmt.Println(errMsg)
continue
}
// 处理成功:打印结果(实际无需手动提交offset,kafka-go自动提交)
fmt.Printf("处理消息成功:offset=%d, result=%s\n", m.Offset, res.Message)
}
}
// 4. 示例用户函数(MQ业务逻辑)
func MQHelloHandler(ctx context.Context, msg *MQMessage) (*FunctionResponse, error) {
// 解析消息体(业务逻辑)
var data map[string]string
if err := json.Unmarshal(msg.Body, &data); err != nil {
return nil, fmt.Errorf("解析消息体失败:%v", err)
}
name := data["name"]
if name == "" {
name = "world"
}
return &FunctionResponse{
StatusCode: 200,
Message: fmt.Sprintf("处理MQ消息成功:hello %s (topic=%s)", name, msg.Topic),
}, nil
}
// 5. 启动服务(核心入口)
func main() {
// 核心:启动MQ触发器,绑定用户函数
MQTrigger(MQHelloHandler, "localhost:9092", "faas-test-topic")
}
在实际生产环境,需要考虑更多复杂的问题。
| 核心维度 | 具体问题 | 典型风险场景 | 生产级解决方案 |
|---|---|---|---|
| 消息可靠性 | 消息丢失 | 1. 拉取消息后进程崩溃2. 函数执行成功但 offset 未提交3. MQ 副本不足导致消息丢失 | 1. 关闭自动提交 offset,函数执行成功后手动提交2. MQ 层面:Kafka 设 min.insync.replicas≥2+acks=all,RocketMQ 开启同步刷盘3. 触发器层面:处理前记录待办日志,崩溃后可恢复 |
| 重复消费 | 1. offset 提交失败导致消息重推2. 业务无幂等性导致数据错乱(重复下单 / 扣款) | 1. 强制业务幂等:用消息 Key 作为幂等键,Redis/DB 标记处理状态2. 精准提交 offset:按最小成功 offset 批量提交3. 数据库层面:唯一索引 / 乐观锁兜底 | |
| 消息堆积 | 1. 函数执行慢 / 并发不足导致消息积压2. 堆积触发 MQ 消息过期 / 磁盘满 | 1. 动态扩缩容:按堆积量 / 消费延迟自动扩容函数实例(设最大实例数)2. 消费限流:单实例 QPS 限制 + MQ max.poll.records 限制单次拉取数3. 优先级拆分:核心消息走高优先级队列 | |
| 函数执行容错 | 执行失败 | 1. 函数 panic / 业务异常2. 下游服务不可用3. 网络抖动导致临时失败 | 1. 分级重试:本地轻重试(1-2 次,100ms 间隔)+ 延时队列重重试(5s/10s/30s 阶梯)2. 死信队列(DLQ):重试失败消息打入 DLQ,人工兜底3. 失败隔离:单条失败不阻塞批量消费 |
| 执行超时 | 1. 函数超时导致线程阻塞2. 超时后 MQ 重推,加剧堆积 | 1. 严格超时控制:函数上下文设置超时(如 5s),超时立即终止执行2. 超时降级:超时消息直接打入 DLQ,不占用消费资源3. 监控告警:超时率超过阈值立即告警 | |
| 资源过载 | 1. 函数 CPU / 内存占用过高2. 并发消费导致下游数据库压垮 | 1. 资源隔离:函数实例设置 CPU / 内存上限(如 1C2G)2. 并发控制:单实例消费并发数限制(如 10 并发)3. 下游限流:函数调用下游服务时加限流(如令牌桶) | |
| 性能优化 | 消费效率 | 1. 单条消费 QPS 低2. 网络往返次数多 | 1. 批量消费:拉取 + 处理 + 提交均批量(如每次拉取 50 条)2. 异步处理:消息拉取和函数执行异步解耦(生产消费队列)3. 本地缓存:热点数据缓存,减少下游调用 |
| 冷启动延迟 | 1. 函数实例冷启动导致消费延迟2. 低流量时实例缩容,突发流量触发冷启动 | 1. 实例预热:保留最小空闲实例数(如 2 个)2. 流量预测:根据历史峰值提前扩容3. 快照加速:函数运行时内存快照,快速恢复实例 | |
| 可观测性 | 问题排查困难 | 1. 消息消费失败无日志2. 无法定位函数执行耗时 / 错误类型 | 1. 全链路日志:记录消息 Key/offset/ 函数执行耗时 / 错误栈2. 关键指标监控:消费 QPS / 堆积量 / 重试率 / 超时率 / 失败率3. 链路追踪:接入 Tracing(如 Jaeger),串联 MQ - 触发器 - 函数 - 下游 |
| 告警不及时 | 1. 消息堆积到阈值未告警2. 函数执行失败率高未发现 | 1. 多维度告警:堆积量 > 1w / 消费延迟 > 5min / 失败率 > 1% 触发告警2. 告警分级:P0(堆积超阈值)→ 电话,P1(失败率高)→ 短信,P2(超时率高)→ 邮件3. 告警降噪:相同告警 5 分钟聚合一次 |
问题1:faas存在消息丢失问题吗?
- 不会FaaS 中的 MQ 触发器核心采用 “消费处理完再发送 ACK” 的模式(推模式为主) ,而非 “拉到消息就发 ACK”—— 本质是 FaaS 平台作为 MQ 的消费者,先把消息推送给函数实例,等函数处理完成(且无异常)后,才向 MQ 发送 ACK 确认;若函数处理失败,触发器会触发重试,且不会发送 ACK,确保消息不丢失。
问题2:faas存在重复消费问题吗?
- 会的,FaaS 中的 MQ 触发器依然存在重复消费问题,这是由 MQ 的消费机制和 FaaS 的无状态特性共同决定的,并非触发器本身的缺陷,而是分布式系统的共性问题。
- 具体导致重复消费的核心场景有 3 类:
- 函数执行成功但 ACK 发送失败函数处理完消息后,向 MQ 发送 ACK 的过程中出现网络抖动 / 平台故障,MQ 未收到 ACK,会判定消息未消费成功,将消息重新入队并再次推送给 FaaS 函数。
- 函数执行超时触发重试FaaS 函数有执行超时限制(比如 30 秒),若业务处理耗时超过阈值,触发器会判定执行失败,触发重试逻辑,而此时函数可能已经处理完消息,导致重复消费。
- 实例扩缩容导致的重复推送流量突增时 FaaS 会快速扩容多个函数实例,MQ 可能将同一条消息推送给多个实例;或者缩容时,正在处理消息的实例被强制销毁,MQ 会重新推送该消息。
- 具体导致重复消费的核心场景有 3 类:
其他
FaaS 与 IaaS、PaaS、Serverless 的关系及区别
核心结论:Serverless 是一种 “无服务器” 的架构理念,而 FaaS 是 Serverless 理念的核心落地形态;IaaS、PaaS 是传统云服务分层模型,与 FaaS 属于不同维度的云服务分类,且 PaaS 与 Serverless 存在部分重叠。
| 概念 | 全称 | 核心定义 |
|---|---|---|
| IaaS | Infrastructure as a Service(基础设施即服务) | 云厂商提供底层基础设施(服务器、虚拟机、存储、网络),用户自己部署操作系统、中间件、应用程序,需运维底层资源。 |
| PaaS | Platform as a Service(平台即服务) | 云厂商提供应用运行平台(如数据库、应用服务器、开发工具),用户只需上传应用代码,无需关注操作系统和中间件的运维。 |
| FaaS | Function as a Service(函数即服务) | 以函数为最小部署单元,用户上传函数代码,平台负责调度、运行、弹性扩缩容,事件驱动触发执行,按实际运行时长计费。 |
| Serverless | 无服务器架构 | 一种架构理念:用户无需关注服务器 / 基础设施的管理,仅聚焦业务逻辑;核心特征是事件驱动、按需计费、弹性伸缩、无状态,FaaS + BaaS(后端即服务)是其典型落地方式。 |
学习自《Serverless核心技术和大规模实践》