无服务器应用实战 #

一、架构概述 #

1.1 无服务器架构 #

text
无服务器应用架构:
┌─────────────────────────────────────────────────────────────┐
│                          用户                                │
└───────────────────────────┬─────────────────────────────────┘
                            │
                            ▼
┌─────────────────────────────────────────────────────────────┐
│                      Route53 DNS                             │
└───────────────────────────┬─────────────────────────────────┘
                            │
                            ▼
┌─────────────────────────────────────────────────────────────┐
│                    CloudFront CDN                            │
└───────────────────────────┬─────────────────────────────────┘
                            │
              ┌─────────────┴─────────────┐
              ▼                           ▼
┌───────────────────────┐   ┌───────────────────────┐
│    静态资源(S3)        │   │   API Gateway         │
│    Web前端            │   │   REST API            │
└───────────────────────┘   └───────────┬───────────┘
                                        │
                                        ▼
                            ┌───────────────────────┐
                            │      Lambda函数       │
                            │    业务逻辑处理       │
                            └───────────┬───────────┘
                                        │
              ┌─────────────────────────┼─────────────────────────┐
              ▼                         ▼                         ▼
┌───────────────────────┐   ┌───────────────────────┐   ┌───────────────────────┐
│      DynamoDB         │   │        S3             │   │    其他AWS服务        │
│      数据存储         │   │      文件存储         │   │    SES/SNS/SQS       │
└───────────────────────┘   └───────────────────────┘   └───────────────────────┘

1.2 架构优势 #

text
无服务器优势:
├── 无需管理服务器
├── 自动扩展
├── 按需付费
├── 高可用
├── 快速部署
└── 降低运维成本

二、项目结构 #

2.1 目录结构 #

text
serverless-app/
├── frontend/
│   ├── index.html
│   ├── css/
│   └── js/
├── backend/
│   ├── src/
│   │   ├── handlers/
│   │   │   ├── create-item.js
│   │   │   ├── get-item.js
│   │   │   ├── list-items.js
│   │   │   └── delete-item.js
│   │   └── utils/
│   │       └── response.js
│   └── package.json
├── infrastructure/
│   └── template.yaml
└── deploy.sh

三、DynamoDB设计 #

3.1 表设计 #

javascript
const params = {
    TableName: 'Items',
    KeySchema: [
        { AttributeName: 'PK', KeyType: 'HASH' },
        { AttributeName: 'SK', KeyType: 'RANGE' }
    ],
    AttributeDefinitions: [
        { AttributeName: 'PK', AttributeType: 'S' },
        { AttributeName: 'SK', AttributeType: 'S' },
        { AttributeName: 'GSI1PK', AttributeType: 'S' },
        { AttributeName: 'GSI1SK', AttributeType: 'S' }
    ],
    GlobalSecondaryIndexes: [{
        IndexName: 'GSI1',
        KeySchema: [
            { AttributeName: 'GSI1PK', KeyType: 'HASH' },
            { AttributeName: 'GSI1SK', KeyType: 'RANGE' }
        ],
        Projection: { ProjectionType: 'ALL' }
    }],
    BillingMode: 'PAY_PER_REQUEST'
};

3.2 数据模型 #

javascript
const item = {
    PK: 'ITEM#item-123',
    SK: 'METADATA',
    GSI1PK: 'USER#user-456',
    GSI1SK: 'ITEM#2024-01-01T00:00:00Z',
    itemId: 'item-123',
    userId: 'user-456',
    title: 'My Item',
    description: 'Item description',
    status: 'active',
    createdAt: '2024-01-01T00:00:00Z',
    updatedAt: '2024-01-01T00:00:00Z'
};

四、Lambda函数 #

4.1 创建项目 #

javascript
const dynamodb = new (require('aws-sdk')).DynamoDB.DocumentClient();
const tableName = process.env.TABLE_NAME;

export const createItem = async (event) => {
    const body = JSON.parse(event.body);
    const itemId = generateId();
    const timestamp = new Date().toISOString();
    
    const item = {
        PK: `ITEM#${itemId}`,
        SK: 'METADATA',
        GSI1PK: `USER#${body.userId}`,
        GSI1SK: `ITEM#${timestamp}`,
        itemId,
        userId: body.userId,
        title: body.title,
        description: body.description,
        status: 'active',
        createdAt: timestamp,
        updatedAt: timestamp
    };
    
    await dynamodb.put({
        TableName: tableName,
        Item: item
    }).promise();
    
    return {
        statusCode: 201,
        body: JSON.stringify(item)
    };
};

export const getItem = async (event) => {
    const itemId = event.pathParameters.itemId;
    
    const result = await dynamodb.get({
        TableName: tableName,
        Key: {
            PK: `ITEM#${itemId}`,
            SK: 'METADATA'
        }
    }).promise();
    
    if (!result.Item) {
        return {
            statusCode: 404,
            body: JSON.stringify({ message: 'Item not found' })
        };
    }
    
    return {
        statusCode: 200,
        body: JSON.stringify(result.Item)
    };
};

export const listItems = async (event) => {
    const userId = event.queryStringParameters?.userId;
    
    const result = await dynamodb.query({
        TableName: tableName,
        IndexName: 'GSI1',
        KeyConditionExpression: 'GSI1PK = :pk',
        ExpressionAttributeValues: {
            ':pk': `USER#${userId}`
        }
    }).promise();
    
    return {
        statusCode: 200,
        body: JSON.stringify(result.Items)
    };
};

export const deleteItem = async (event) => {
    const itemId = event.pathParameters.itemId;
    
    await dynamodb.delete({
        TableName: tableName,
        Key: {
            PK: `ITEM#${itemId}`,
            SK: 'METADATA'
        }
    }).promise();
    
    return {
        statusCode: 204,
        body: ''
    };
};

五、API Gateway配置 #

5.1 SAM模板 #

yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: Serverless API

Parameters:
  TableName:
    Type: String
    Default: Items

Globals:
  Function:
    Runtime: nodejs20.x
    Handler: index.handler
    Environment:
      Variables:
        TABLE_NAME: !Ref TableName
    IAMRoleStatements:
      - Effect: Allow
        Action:
          - dynamodb:PutItem
          - dynamodb:GetItem
          - dynamodb:Query
          - dynamodb:DeleteItem
        Resource: !Sub 'arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${TableName}'

Resources:
  ItemsTable:
    Type: AWS::DynamoDB::Table
    Properties:
      TableName: !Ref TableName
      AttributeDefinitions:
        - AttributeName: PK
          AttributeType: S
        - AttributeName: SK
          AttributeType: S
        - AttributeName: GSI1PK
          AttributeType: S
        - AttributeName: GSI1SK
          AttributeType: S
      KeySchema:
        - AttributeName: PK
          KeyType: HASH
        - AttributeName: SK
          KeyType: RANGE
      GlobalSecondaryIndexes:
        - IndexName: GSI1
          KeySchema:
            - AttributeName: GSI1PK
              KeyType: HASH
            - AttributeName: GSI1SK
              KeyType: RANGE
          Projection:
            ProjectionType: ALL
      BillingMode: PAY_PER_REQUEST

  CreateItemFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: backend/src/handlers/
      Handler: create-item.createItem
      Events:
        CreateItem:
          Type: Api
          Properties:
            Path: /items
            Method: post

  GetItemFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: backend/src/handlers/
      Handler: get-item.getItem
      Events:
        GetItem:
          Type: Api
          Properties:
            Path: /items/{itemId}
            Method: get

  ListItemsFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: backend/src/handlers/
      Handler: list-items.listItems
      Events:
        ListItems:
          Type: Api
          Properties:
            Path: /items
            Method: get

  DeleteItemFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: backend/src/handlers/
      Handler: delete-item.deleteItem
      Events:
        DeleteItem:
          Type: Api
          Properties:
            Path: /items/{itemId}
            Method: delete

Outputs:
  ApiEndpoint:
    Description: API Gateway endpoint URL
    Value: !Sub 'https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/'

六、前端配置 #

6.1 S3静态网站 #

html
<!DOCTYPE html>
<html>
<head>
    <title>Serverless App</title>
    <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
</head>
<body>
    <div id="app">
        <h1>Items List</h1>
        <form id="createForm">
            <input type="text" id="title" placeholder="Title" required>
            <textarea id="description" placeholder="Description"></textarea>
            <button type="submit">Create Item</button>
        </form>
        <ul id="itemsList"></ul>
    </div>
    <script>
        const API_URL = 'https://your-api-id.execute-api.region.amazonaws.com/Prod';
        
        async function loadItems() {
            const response = await axios.get(`${API_URL}/items?userId=user-123`);
            const items = response.data;
            const list = document.getElementById('itemsList');
            list.innerHTML = items.map(item => `
                <li>
                    <h3>${item.title}</h3>
                    <p>${item.description}</p>
                    <button onclick="deleteItem('${item.itemId}')">Delete</button>
                </li>
            `).join('');
        }
        
        document.getElementById('createForm').addEventListener('submit', async (e) => {
            e.preventDefault();
            const title = document.getElementById('title').value;
            const description = document.getElementById('description').value;
            
            await axios.post(`${API_URL}/items`, {
                userId: 'user-123',
                title,
                description
            });
            
            loadItems();
        });
        
        async function deleteItem(itemId) {
            await axios.delete(`${API_URL}/items/${itemId}`);
            loadItems();
        }
        
        loadItems();
    </script>
</body>
</html>

七、部署流程 #

7.1 构建和部署 #

bash
sam build

sam deploy --guided \
    --stack-name serverless-app \
    --capabilities CAPABILITY_IAM

7.2 部署前端 #

bash
aws s3 sync frontend/ s3://your-bucket-name/ --delete

aws s3 website s3://your-bucket-name/ \
    --index-document index.html \
    --error-document error.html

八、监控配置 #

8.1 CloudWatch告警 #

yaml
HighErrorRate:
  Type: AWS::CloudWatch::Alarm
  Properties:
    AlarmDescription: Lambda error rate high
    MetricName: Errors
    Namespace: AWS/Lambda
    Statistic: Sum
    Period: 300
    EvaluationPeriods: 1
    Threshold: 5
    ComparisonOperator: GreaterThanThreshold

8.2 X-Ray追踪 #

yaml
Globals:
  Function:
    Tracing: Active

九、安全配置 #

9.1 API Gateway认证 #

yaml
ApiGatewayAuthorizer:
  Type: AWS::ApiGateway::Authorizer
  Properties:
    Name: CognitoAuthorizer
    Type: COGNITO_USER_POOLS
    IdentitySource: method.request.header.Authorization
    RestApiId: !Ref ServerlessRestApi
    ProviderARNs:
      - !GetAtt UserPool.Arn

9.2 CORS配置 #

yaml
ApiGatewayCors:
  Type: AWS::ApiGateway::Method
  Properties:
    RestApiId: !Ref ServerlessRestApi
    ResourceId: !Ref ItemsResource
    HttpMethod: OPTIONS
    AuthorizationType: NONE
    Integration:
      Type: MOCK
      IntegrationResponses:
        - StatusCode: 200
          ResponseParameters:
            method.response.header.Access-Control-Allow-Headers: "'Content-Type,Authorization'"
            method.response.header.Access-Control-Allow-Methods: "'GET,POST,DELETE'"
            method.response.header.Access-Control-Allow-Origin: "'*'"
      RequestTemplates:
        application/json: '{"statusCode": 200}'
    MethodResponses:
      - StatusCode: 200
        ResponseParameters:
          method.response.header.Access-Control-Allow-Headers: true
          method.response.header.Access-Control-Allow-Methods: true
          method.response.header.Access-Control-Allow-Origin: true

十、成本优化 #

10.1 成本监控 #

yaml
Budget:
  Type: AWS::Budgets::Budget
  Properties:
    Budget:
      BudgetName: ServerlessAppBudget
      BudgetLimit:
        Amount: 50
        Unit: USD
      TimeUnit: MONTHLY
      BudgetType: COST
    NotificationsWithSubscribers:
      - Notification:
          NotificationType: ACTUAL
          ComparisonOperator: GREATER_THAN
          Threshold: 80
        Subscribers:
          - SubscriptionType: EMAIL
            Address: admin@example.com

十一、小结 #

本章介绍了无服务器应用实战:

内容 要点
架构设计 API Gateway + Lambda + DynamoDB
DynamoDB 表设计、GSI索引
Lambda 函数编写、权限配置
API Gateway REST API、认证、CORS
部署 SAM模板、自动化部署

下一步学习 #

完成无服务器应用后,接下来可以:

  1. 微服务架构 - 学习微服务部署
  2. 安全最佳实践 - 加强安全配置
  3. DynamoDB - 深入学习DynamoDB
最后更新:2026-03-28