5月27日 16:25

Serverless 微服务设计原则有哪些?

单一职责原则

每个 Serverless 函数只承担一项职责,是微服务拆分的基本粒度准则。

  • 函数粒度:一个函数只做一件事,避免"万能函数"。例如用户注册场景,拆分为"验证参数""写入数据库""发送通知"三个独立函数,而非一个大函数包揽全部
  • 业务边界:按业务领域(Domain)划分函数边界,同一领域的函数组成一个微服务。订单域的函数不应混入支付域的逻辑
  • 可复用性:通用逻辑(鉴权、日志、参数校验)抽取为共享层或独立函数,供多个业务函数调用,避免重复实现

实际项目中,过度拆分会导致函数数量爆炸、调用链过长;拆分不足则失去 Serverless 弹性伸缩的优势。合理的判断标准:一个函数的执行时间应在秒级,职责描述能用一句话说清。

无状态设计

Serverless 函数天然是无状态的,每次调用都在全新环境中执行。设计时必须顺应这一特性。

  • 状态外置:将状态存储在外部服务中,如 DynamoDB、Redis、S3。函数本身不保存任何跨调用的状态信息
  • 幂等性:同一请求多次执行结果一致。这对于消息队列的 at-least-once 投递语义至关重要——消费者重试时不会产生副作用
  • 无副作用:函数不依赖本地文件系统、全局变量等不可靠的状态载体。如果需要临时存储,使用 /tmp 目录(AWS Lambda 提供 512MB-10GB)并假设它随时可能丢失

幂等设计的常用手段:用请求 ID 去重、用乐观锁控制并发写入、用事务保证原子操作。

事件驱动架构

Serverless 架构下,服务间通信的首选模式是事件驱动,而非同步调用。

  • 异步通信:使用消息队列(SQS、Kafka、EventBridge)实现服务间解耦。生产者只管发事件,不需要等消费者处理完成
  • 事件溯源:所有状态变更以事件形式记录,形成不可变的事件流。需要重建状态时,回放事件即可。这在审计和调试场景中极为有用
  • 发布订阅:通过事件总线(如 AWS EventBridge)实现松耦合。订单服务发布"订单已创建"事件,库存服务、通知服务各自订阅处理,互不感知

事件驱动 vs 同步调用的核心取舍:事件驱动牺牲了实时性和调试便利性,换来了更高的系统弹性和容错能力。

服务通信模式

同步通信

通过 API Gateway 调用其他函数,请求-响应模式。

  • 典型方式:HTTP/HTTPS 调用,API Gateway 充当入口
  • 适用场景:需要立即返回结果的查询类操作,如获取用户信息
  • 关键风险:冷启动延迟可能导致 P99 超时;级联调用会放大延迟;不适合高并发写入场景

异步通信

通过消息队列传递数据,生产者和消费者解耦。

  • 典型方式:SQS、Kafka、SNS 等消息中间件
  • 适用场景:长时间运行的任务(视频转码、报表生成)、高并发写入(订单入库)、需要重试保障的操作
  • 优势:服务间完全解耦,消费者可独立扩缩容,系统弹性大幅提升

编排模式

使用状态机(如 AWS Step Functions)编排多个函数的执行顺序和分支逻辑。

  • 适用场景:包含条件分支、并行执行、人工审批等复杂流程
  • 优势:执行流程可视化、内置错误处理和重试机制、每步状态可追踪
  • 注意:Step Functions 本身有状态管理开销,简单场景用事件驱动更轻量

三种模式不是互斥的,实际架构中通常组合使用:API Gateway 接收请求 → Step Functions 编排流程 → 消息队列传递中间结果。

冷启动优化

冷启动是 Serverless 架构的核心性能挑战,理解并优化它是设计原则落地的关键。

冷启动原因:函数首次调用或长时间空闲后,平台需要分配运行环境、加载代码和依赖。Java/C# 等运行时冷启动可达数秒,Python/Node.js 通常在百毫秒级。

优化策略:

  • 精简函数体积:只引入必要依赖,避免打入了完整的 SDK。Node.js 使用 webpack/tree-shaking,Python 使用 Lambda Layer 按需加载
  • 预热机制:通过定时触发器(CloudWatch Events)周期性调用函数,保持实例活跃。需权衡额外成本
  • 连接复用:在 handler 外部初始化数据库连接、HTTP 客户端等,利用运行时复用。同一容器内的后续调用无需重新建连
  • 选择轻量运行时:对延迟敏感的场景优先选 Python、Node.js 或 Go,而非 Java/C#
  • Provisioned Concurrency:AWS 提供预置并发,为关键函数保持固定数量的就绪实例,彻底消除冷启动(但会产生额外费用)

数据一致性

微服务拆分后,每个服务拥有独立数据存储,跨服务一致性成为难点。

  • 最终一致性:Serverless 架构默认采用最终一致性模型。通过 Saga 模式协调跨服务事务——每个服务执行本地事务并发布事件,任一步失败触发补偿操作
  • CQRS(命令查询职责分离):将写入和读取分离到不同的数据模型。写入走规范化模型保证一致性,读取走反规范化模型优化查询性能。在 Serverless 中,写入函数和读取函数可独立扩缩容
  • 分布式事务替代方案:避免跨服务分布式锁和两阶段提交。用事件溯源 + 幂等消费实现"准事务"语义

可观测性与监控

Serverless 架构下,传统服务器监控手段失效,需要新的可观测性策略。

  • 分布式追踪:每个请求在服务间传递 Trace ID(如 AWS X-Ray、Jaeger),串联完整调用链。没有追踪,排查跨函数问题如同盲人摸象
  • 结构化日志:所有函数输出 JSON 格式日志,包含请求 ID、函数名、时间戳、关键参数。便于 CloudWatch Logs Insights 或 ELK 检索分析
  • 指标告警:监控函数执行时长、错误率、并发数、冷启动频率。设置阈值告警,而非事后排查
  • 仪表盘:为每个微服务建立独立的 CloudWatch Dashboard,聚合关键指标

最佳实践总结

  1. 合理拆分服务:按业务领域拆分,函数粒度在"一句话职责"和"秒级执行时间"之间取平衡,避免过度拆分导致调用链爆炸
  2. API 设计:保持接口简洁,遵循 RESTful 规范,API Gateway 层统一处理鉴权和限流
  3. 错误处理与重试:实现完善的错误分类(可重试 vs 不可重试)、指数退避重试、死信队列兜底。Step Functions 内置了 catch/retry 语法
  4. 监控先行:在开发阶段就嵌入 Trace ID 传递和结构化日志,不要等到上线再补
  5. 安全最小权限:每个函数的 IAM 角色只授予必要权限,避免使用通配符权限

面试中回答此问题时,除了阐述上述原则,应结合自身项目经验说明取舍过程——如为何选择异步而非同步、冷启动如何优化、最终一致性如何保证,体现对架构决策背后原因的理解。

标签:Serverless