Skip to content
清晨的一缕阳光
返回

无服务器架构实战:Serverless 应用设计与落地

引言

“Serverless 不是没有服务器,而是不用你管理服务器。”

Serverless(无服务器)架构让开发者专注于业务逻辑,无需关心服务器运维。

Serverless 优势

适用场景

场景适合度说明
API 后端⭐⭐⭐⭐⭐RESTful API、Webhook
事件处理⭐⭐⭐⭐⭐文件处理、消息队列
定时任务⭐⭐⭐⭐⭐Cron Job、数据同步
实时处理⭐⭐⭐⭐流数据处理
长连接服务⭐⭐WebSocket(部分支持)
长时间运行受限于执行时间

一、Serverless 核心概念

架构组成

graph TB
    Client[客户端] --> APIGW[API 网关]
    APIGW --> Func1[函数 1]
    APIGW --> Func2[函数 2]
    APIGW --> Func3[函数 3]
    
    Func1 --> DB[(数据库)]
    Func1 --> Cache[(缓存)]
    
    Func2 --> Queue[消息队列]
    Func2 --> Storage[对象存储]
    
    Func3 --> Event[事件源]
    Func3 --> Stream[数据流]
    
    Monitor[监控告警] --> Func1
    Monitor --> Func2
    Monitor --> Func3

核心组件

组件作用示例
FaaS函数即服务AWS Lambda、阿里云函数计算
API GatewayAPI 管理AWS API Gateway、APISIX
BaaS后端即服务Firebase、Auth0
事件源触发函数S3、Kafka、定时器等

二、AWS Lambda 实战

1. 函数代码(Java)

// RequestHandler.java
package com.example.handler;

import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent;
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent;
import com.fasterxml.jackson.databind.ObjectMapper;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
import software.amazon.awssdk.services.dynamodb.model.*;

import java.util.HashMap;
import java.util.Map;

public class CreateUserHandler implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> {
    
    private final ObjectMapper objectMapper = new ObjectMapper();
    private final DynamoDbClient dynamoDb = DynamoDbClient.create();
    private final String tableName = System.getenv("USER_TABLE_NAME");
    
    @Override
    public APIGatewayProxyResponseEvent handleRequest(APIGatewayProxyRequestEvent event, Context context) {
        
        context.getLogger().log("Received event: " + event.getBody());
        
        try {
            // 解析请求
            Map<String, String> body = objectMapper.readValue(
                event.getBody(), 
                Map.class
            );
            
            String username = body.get("username");
            String email = body.get("email");
            
            // 验证参数
            if (username == null || email == null) {
                return response(400, Map.of("error", "Missing required fields"));
            }
            
            // 生成 ID
            String userId = java.util.UUID.randomUUID().toString();
            String createdAt = java.time.Instant.now().toString();
            
            // 保存到 DynamoDB
            Map<String, AttributeValue> item = new HashMap<>();
            item.put("userId", AttributeValue.builder().s(userId).build());
            item.put("username", AttributeValue.builder().s(username).build());
            item.put("email", AttributeValue.builder().s(email).build());
            item.put("createdAt", AttributeValue.builder().s(createdAt).build());
            
            dynamoDb.putItem(PutItemRequest.builder()
                .tableName(tableName)
                .item(item)
                .build());
            
            // 返回响应
            Map<String, Object> responseBody = Map.of(
                "userId", userId,
                "username", username,
                "email", email,
                "createdAt", createdAt
            );
            
            return response(201, responseBody);
            
        } catch (Exception e) {
            context.getLogger().log("Error: " + e.getMessage());
            return response(500, Map.of("error", "Internal server error"));
        }
    }
    
    private APIGatewayProxyResponseEvent response(int statusCode, Map<String, Object> body) {
        APIGatewayProxyResponseEvent response = new APIGatewayProxyResponseEvent();
        response.setStatusCode(statusCode);
        response.setHeaders(Map.of(
            "Content-Type", "application/json",
            "Access-Control-Allow-Origin", "*"
        ));
        try {
            response.setBody(objectMapper.writeValueAsString(body));
        } catch (Exception e) {
            response.setBody("{\"error\":\"Serialization error\"}");
        }
        return response;
    }
}

2. Maven 配置

<!-- pom.xml -->
<project>
    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
    </properties>
    
    <dependencies>
        <!-- AWS Lambda Core -->
        <dependency>
            <groupId>com.amazonaws</groupId>
            <artifactId>aws-lambda-java-core</artifactId>
            <version>1.2.3</version>
        </dependency>
        
        <!-- AWS Lambda Events -->
        <dependency>
            <groupId>com.amazonaws</groupId>
            <artifactId>aws-lambda-java-events</artifactId>
            <version>3.11.3</version>
        </dependency>
        
        <!-- AWS SDK v2 -->
        <dependency>
            <groupId>software.amazon.awssdk</groupId>
            <artifactId>dynamodb</artifactId>
            <version>2.20.100</version>
        </dependency>
        
        <!-- Jackson -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.15.2</version>
        </dependency>
        
        <!-- SLF4J -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>2.0.9</version>
        </dependency>
    </dependencies>
    
    <build>
        <plugins>
            <!-- Shade Plugin 打包 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>3.5.0</version>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

3. SAM 模板

# template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31

Globals:
  Function:
    Timeout: 30
    MemorySize: 512
    Runtime: java17
    Environment:
      Variables:
        USER_TABLE_NAME: !Ref UserTable

Resources:
  # Lambda 函数
  CreateUserFunction:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: create-user-function
      CodeUri: target/lambda-create-user.jar
      Handler: com.example.handler.CreateUserHandler::handleRequest
      Description: Create user API
      Environment:
        Variables:
          USER_TABLE_NAME: !Ref UserTable
      Events:
        CreateUserApi:
          Type: Api
          Properties:
            Path: /users
            Method: POST
            RestApiId: !Ref ServerlessRestApi
      Policies:
        - DynamoDBCrudPolicy:
            TableName: !Ref UserTable
        - AWSLambdaBasicExecutionRole
    
    # 预留并发
    ProvisionedConcurrencyConfig:
      ProvisionedConcurrentExecutions: 5

  # DynamoDB 表
  UserTable:
    Type: AWS::DynamoDB::Table
    Properties:
      TableName: users
      AttributeDefinitions:
        - AttributeName: userId
          AttributeType: S
        - AttributeName: username
          AttributeType: S
      KeySchema:
        - AttributeName: userId
          KeyType: HASH
      GlobalSecondaryIndexes:
        - IndexName: username-index
          KeySchema:
            - AttributeName: username
              KeyType: HASH
          Projection:
            ProjectionType: ALL
          ProvisionedThroughput:
            ReadCapacityUnits: 5
            WriteCapacityUnits: 5
      BillingMode: PAY_PER_REQUEST
      SSESpecification:
        SSEEnabled: true

Outputs:
  ApiUrl:
    Description: API Gateway endpoint URL
    Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/users"
  
  CreateUserFunctionArn:
    Description: Create User Function ARN
    Value: !GetAtt CreateUserFunction.Arn

4. 部署命令

# 打包
mvn clean package

# 部署
sam build
sam deploy --guided

# 或使用 AWS CLI
aws cloudformation deploy \
  --template-file template.yaml \
  --stack-name user-api \
  --capabilities CAPABILITY_IAM

三、阿里云函数计算实战

1. 函数代码

// FunctionComputterHandler.java
package com.example.fc;

import com.aliyun.fc.runtime.Context;
import com.aliyun.fc.runtime.StreamRequestHandler;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.rds.model.v20140815.DescribeDBInstancesRequest;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.Map;

public class GetUserHandler implements StreamRequestHandler {
    
    private final ObjectMapper objectMapper = new ObjectMapper();
    
    @Override
    public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context) throws IOException {
        
        // 读取请求
        byte[] bytes = inputStream.readAllBytes();
        Map<String, Object> event = objectMapper.readValue(bytes, Map.class);
        
        String userId = (String) event.get("userId");
        
        context.getLogger().println("Processing user: " + userId);
        
        try {
            // 业务逻辑
            Map<String, Object> user = getUserById(userId);
            
            // 返回响应
            Map<String, Object> response = Map.of(
                "statusCode", 200,
                "headers", Map.of("Content-Type", "application/json"),
                "body", user
            );
            
            outputStream.write(objectMapper.writeValueAsBytes(response));
            
        } catch (Exception e) {
            context.getLogger().println("Error: " + e.getMessage());
            
            Map<String, Object> errorResponse = Map.of(
                "statusCode", 500,
                "body", Map.of("error", e.getMessage())
            );
            
            outputStream.write(objectMapper.writeValueAsBytes(errorResponse));
        }
    }
    
    private Map<String, Object> getUserById(String userId) {
        // 实现业务逻辑
        return Map.of(
            "userId", userId,
            "username", "testuser",
            "email", "test@example.com"
        );
    }
}

2. serverless.yml 配置

# serverless.yml
component: function
name: user-api

inputs:
  name: user-api-function
  namespace: default
  runtime: custom-java17
  handler: com.example.fc.GetUserHandler::handleRequest
  code: ./code
  
  # 环境变量
  environment:
    variables:
      DB_HOST: ${DB_HOST}
      DB_NAME: user_db
  
  # 触发器
  triggers:
    - name: httpTrigger
      type: http
      config:
        authType: anonymous
        methods:
          - GET
          - POST
  
  # 层
  layers:
    - name: common-layer
      version: 1
  
  # 日志
  logConfig:
    logProject: user-api-logs
    logStore: function-logs
  
  # VPC 配置
  vpcConfig:
    vpcId: vpc-xxx
    vSwitchIds:
      - vsw-xxx
    securityGroupId: sg-xxx
  
  # 性能配置
  memorySize: 512
  timeout: 60
  instanceConcurrency: 10
  
  # 自定义运行时
  customRuntimeConfig:
    command:
      - java
      - -jar
      - /opt/code/function.jar

3. 部署命令

# 安装 Serverless Framework
npm install -g serverless

# 部署
serverless deploy

# 查看日志
serverless logs --function user-api-function

# 调用函数
serverless invoke --function user-api-function \
  --data '{"userId": "123"}'

四、事件驱动架构

1. S3 事件触发

// S3EventHandler.java
public class S3ImageProcessor implements RequestHandler<S3Event, Void> {
    
    private final AmazonS3 s3Client = AmazonS3ClientBuilder.defaultClient();
    private final AmazonRekognition rekognitionClient = AmazonRekognitionClientBuilder.defaultClient();
    
    @Override
    public Void handleRequest(S3Event event, Context context) {
        
        S3EventNotificationRecord record = event.getRecords().get(0);
        String bucket = record.getS3().getBucket().getName();
        String key = record.getS3().getObject().getKey();
        
        context.getLogger().log("Processing image: " + bucket + "/" + key);
        
        try {
            // 图片内容审核
            DetectModerationLabelsRequest moderationRequest = new DetectModerationLabelsRequest()
                .withImage(new Image().withS3Object(new S3Object().withBucket(bucket).withName(key)));
            
            DetectModerationLabelsResult moderationResult = rekognitionClient.detectModerationLabels(moderationRequest);
            
            if (!moderationResult.getModerationLabels().isEmpty()) {
                // 移动到低俗图片桶
                s3Client.copyObject(bucket, key, "flagged-bucket", key);
                s3Client.deleteObject(bucket, key);
                context.getLogger().log("Image flagged as inappropriate");
            }
            
        } catch (Exception e) {
            context.getLogger().log("Error processing image: " + e.getMessage());
            throw new RuntimeException(e);
        }
        
        return null;
    }
}

2. 定时器触发

// ScheduledEventHandler.java
public class DataSyncHandler implements RequestHandler<ScheduledEvent, Void> {
    
    @Override
    public Void handleRequest(ScheduledEvent event, Context context) {
        
        context.getLogger().log("Starting data sync job");
        
        try {
            // 数据同步逻辑
            syncUserData();
            syncOrderData();
            syncProductData();
            
            context.getLogger().log("Data sync completed successfully");
            
        } catch (Exception e) {
            context.getLogger().log("Data sync failed: " + e.getMessage());
            throw new RuntimeException(e);
        }
        
        return null;
    }
    
    private void syncUserData() {
        // 实现同步逻辑
    }
}
# SAM 模板 - 定时器
DataSyncFunction:
  Type: AWS::Serverless::Function
  Properties:
    FunctionName: data-sync-function
    CodeUri: target/data-sync.jar
    Handler: com.example.handler.DataSyncHandler::handleRequest
    Timeout: 300
    Events:
      DailySync:
        Type: Schedule
        Properties:
          Schedule: rate(1 day)
          Enabled: true
          Name: DailyDataSync
          Description: Daily data synchronization job

五、成本优化

1. 内存优化

内存配置单价($/GB-秒)100ms 成本建议场景
128MB$0.0000166667$0.00000021简单 API
512MB$0.0000166667$0.00000085标准 API
1024MB$0.0000166667$0.00000171CPU 密集型
3008MB$0.0000166667$0.00000501重计算任务

2. 预留并发优化

# 预留并发配置
ProvisionedConcurrencyConfig:
  ProvisionedConcurrentExecutions: 10
  
# 成本对比
# 按需:100 万请求 × 500ms × 512MB = $8.33/天
# 预留:10 个并发 × 24 小时 × 512MB = $17.28/天
# 适合:稳定流量、低延迟要求

3. 冷启动优化

// 优化技巧
public class OptimizedHandler implements RequestHandler<...> {
    
    // 1. 静态初始化客户端(复用连接)
    private static final DynamoDbClient dynamoDb = DynamoDbClient.create();
    private static final ObjectMapper mapper = new ObjectMapper();
    
    // 2. 懒加载重型依赖
    private HeavyService heavyService;
    
    private HeavyService getHeavyService() {
        if (heavyService == null) {
            heavyService = new HeavyService();
        }
        return heavyService;
    }
    
    @Override
    public Response handleRequest(Request event, Context context) {
        // 3. 减少序列化开销
        return getHeavyService().process(event);
    }
}

六、监控与调试

1. CloudWatch 日志

// 结构化日志
Map<String, Object> logEntry = Map.of(
    "timestamp", Instant.now().toString(),
    "level", "INFO",
    "message", "User created",
    "userId", userId,
    "requestId", context.getAwsRequestId()
);

context.getLogger().log(objectMapper.writeValueAsString(logEntry));

2. X-Ray 链路追踪

// 添加 X-Ray 支持
AWSXRay.beginSegment("CreateUser");

try {
    // 业务逻辑
    AWSXRay.beginSubsegment("DynamoDB");
    dynamoDb.putItem(request);
    AWSXRay.endSubsegment();
    
    AWSXRay.endSegment();
    return response;
    
} catch (Exception e) {
    AWSXRay.addError(e);
    AWSXRay.endSegment();
    throw e;
}

3. 本地调试

# SAM Local 本地运行
sam local invoke CreateUserFunction \
  --event events/create-user.json \
  --env-vars env.json

# 本地 API 网关
sam local start-api \
  --port 3000 \
  --env-vars env.json

# 访问
curl http://localhost:3000/users \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{"username":"test","email":"test@example.com"}'

七、最佳实践

1. 函数设计原则

2. 安全最佳实践

# 最小权限原则
Policies:
  - DynamoDBReadPolicy:
      TableName: !Ref UserTable
  - DynamoDBWritePolicy:
      TableName: !Ref OrderTable

# VPC 隔离
VpcConfig:
  SecurityGroupIds:
    - sg-function
  SubnetIds:
    - subnet-private-1
    - subnet-private-2

# 环境变量加密
Environment:
  Variables:
    DB_PASSWORD: !Sub '{{resolve:secretsmanager:${SecretArn}}}'

3. 错误处理

try {
    return handleBusinessLogic(event);
    
} catch (BusinessException e) {
    // 业务异常,返回 4xx
    return errorResponse(400, e.getMessage());
    
} catch (ServiceException e) {
    // 服务异常,可重试
    context.getLogger().log("Retriable error: " + e.getMessage());
    throw e; // Lambda 会自动重试
    
} catch (Exception e) {
    // 未知异常,返回 5xx
    context.getLogger().log("Unexpected error", e);
    return errorResponse(500, "Internal error");
}

八、总结

Serverless 适合场景

不适合场景

成本对比(100 万请求/天)

方案月成本运维成本
Serverless$250
ECS/EC2$500+
Kubernetes$800+

分享这篇文章到:

上一篇文章
服务网格实战:Istio 微服务治理指南
下一篇文章
整洁架构实战:构建可维护的 Java 应用