正在加载,请稍候…

无服务器架构:模式、陷阱与最佳实践

构建健壮的无服务器应用。学习函数组合、冷启动缓解、无状态设计、成本优化,以及何时选择无服务器架构。

无服务器架构:模式、陷阱与最佳实践

无服务器架构模式

何时使用无服务器

适合的场景:
  - 事件驱动型工作负载(图片处理、通知)
  - 不可预测的流量(负载变化)
  - 批处理作业
  - Webhooks 和集成
  - 低流量 API

不适合的场景:
  - 长时间运行的处理(>15 分钟)
  - 高流量 API(大规模时成本高)
  - 有状态应用
  - 延迟敏感型(冷启动)
  - GPU 工作负载

无服务器架构:模式、陷阱与最佳实践 插图

函数组合模式

事件驱动管道

// S3 -> Lambda -> SQS -> Lambda -> DynamoDB
// 每个函数只做一件事

// 图片上传到 S3
export const onImageUpload: S3Handler = async (event) => {
  for (const record of event.Records) {
    const bucket = record.s3.bucket.name;
    const key = record.s3.object.key;

    // 发送到处理队列(解耦)
    await sqs.sendMessage({
      QueueUrl: process.env.PROCESSING_QUEUE_URL!,
      MessageBody: JSON.stringify({ bucket, key }),
    }).promise();
  }
};

// 从 SQS 处理
export const processImage: SQSHandler = async (event) => {
  for (const record of event.Records) {
    const { bucket, key } = JSON.parse(record.body);
    await resizeAndOptimize(bucket, key);
    await storeMetadata(bucket, key);
  }
};

无服务器架构:模式、陷阱与最佳实践 插图

使用 Step Functions 的 Saga 模式

{
  "Comment": "订单处理 saga",
  "StartAt": "ValidateOrder",
  "States": {
    "ValidateOrder": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:us-east-1:123:function:validateOrder",
      "Next": "ChargePayment",
      "Catch": [{ "ErrorEquals": ["ValidationError"], "Next": "OrderFailed" }]
    },
    "ChargePayment": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:us-east-1:123:function:chargePayment",
      "Next": "ReserveInventory",
      "Catch": [{ "ErrorEquals": ["PaymentFailed"], "Next": "OrderFailed" }]
    },
    "ReserveInventory": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:us-east-1:123:function:reserveInventory",
      "Next": "OrderComplete"
    },
    "OrderComplete": { "Type": "Succeed" },
    "OrderFailed": { "Type": "Fail" }
  }
}

冷启动缓解

// 1. 预置并发(AWS)
// 在 serverless.yml 中:
// provisionedConcurrency: 5  # 始终保持实例温暖

// 2. 定时预热
export const warmup: ScheduledHandler = async () => {
  // 此函数每 5 分钟运行一次以保持 Lambda 温暖
  return { statusCode: 200, body: 'warmed' };
};

// 3. 最小化包大小
// 使用 esbuild 打包,摇树优化未使用的模块
// 避免导入整个 AWS SDK - 使用 v3 模块化客户端:
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
// 不要:import AWS from 'aws-sdk';

// 4. 将初始化放在处理函数外部
const client = new DynamoDBClient({ region: 'us-east-1' }); // 在多次调用间复用
export const handler = async (event: APIGatewayEvent) => { /* ... */ };

无服务器架构:模式、陷阱与最佳实践 插图

无状态设计模式

// 问题:在内存中存储状态(冷启动时丢失)
const cache = new Map<string, User>();  // 不好!在调用间丢失

// 解决方案 1:外部缓存
async function getUser(id: string): Promise<User> {
  return redis.getOrSet(`user:${id}`, () => db.findUser(id), 300);
}

// 解决方案 2:通过事件传递状态
export const handler = async (event: { userId: string; sessionToken: string }) => {
  // 所有需要的状态来自事件
  const user = await validateToken(event.sessionToken);
  return processRequest(user, event);
};

// 解决方案 3:使用 DynamoDB 实现分布式状态
const session = await dynamodb.getItem({ TableName: 'sessions', Key: { id: { S: sessionId } } });

成本优化

// 使用 ARM64(Graviton)可节省约 20% 成本
// serverless.yml: architecture: arm64

// 合理调整内存大小(更多内存 = 更多 CPU + 成本)
// 分析实际内存使用情况:
export const handler = async () => {
  const used = process.memoryUsage().heapUsed / 1024 / 1024;
  console.log(`Memory used: ${Math.round(used)}MB`);
  // 将 Lambda 内存设置为实际使用量的约 1.5 倍
};

// 批量处理 SQS 消息以减少调用次数
// SQS 触发器设置批处理大小为 10:
export const batchHandler: SQSHandler = async (event) => {
  await Promise.all(event.Records.map(r => processRecord(JSON.parse(r.body))));
  // 1 次调用处理 10 条消息,而不是 10 次调用
};

无服务器架构适用于事件驱动、可变工作负载,但需要重新思考传统架构。