引言
“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 Gateway | API 管理 | 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.00000171 | CPU 密集型 |
| 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. 函数设计原则
- ✅ 单一职责:一个函数只做一件事
- ✅ 无状态:状态存储到外部
- ✅ 幂等性:支持重复调用
- ✅ 快速启动:减少初始化时间
- ✅ 合理超时:设置合适的 timeout
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 适合场景
- ✅ 突发流量:自动扩缩容
- ✅ 事件驱动:文件处理、消息队列
- ✅ 微服务:独立部署、快速迭代
- ✅ 成本敏感:按量付费、零闲置成本
不适合场景
- ❌ 长连接:WebSocket 支持有限
- ❌ 长时间运行:受限于超时时间
- ❌ 高性能计算:冷启动影响
- ❌ 强一致性:分布式系统限制
成本对比(100 万请求/天)
| 方案 | 月成本 | 运维成本 |
|---|
| Serverless | $250 | 低 |
| ECS/EC2 | $500+ | 中 |
| Kubernetes | $800+ | 高 |