无服务器计算
无服务器计算(Serverless Computing)是一种云计算执行模型,云服务商动态管理机器资源的分配。开发者无需关心服务器运维,只需专注于编写业务逻辑代码。无服务器并不代表没有服务器,而是指服务器对开发者透明。
无服务器计算概述
核心概念
无服务器计算的核心是函数即服务(FaaS)。开发者将代码组织成函数,上传到云平台,云平台在事件触发时自动执行函数。函数执行完毕后,资源自动释放。
无服务器的特点
无需管理基础设施:开发者不需要配置服务器、管理操作系统、处理扩展等问题。云平台自动处理所有底层工作。
按需付费:只为函数实际执行的时间付费,没有空闲成本。传统服务器即使不处理请求也在产生费用。
自动扩展:函数可以根据请求量自动扩展,从零到成千上万的并发执行。无需手动配置扩展策略。
事件驱动:函数由各种事件触发,如HTTP请求、数据库变更、文件上传、定时任务等。
无服务器 vs 传统架构
| 特性 | 传统架构 | 无服务器架构 |
|---|---|---|
| 服务器管理 | 需要管理 | 不需要 |
| 扩展方式 | 手动或自动配置 | 自动 |
| 计费方式 | 按资源预留付费 | 按执行时间付费 |
| 启动时间 | 持续运行 | 冷启动延迟 |
| 最大执行时间 | 无限制 | 有限制(通常15分钟) |
| 状态管理 | 应用内管理 | 需要外部存储 |
主流无服务器平台
AWS Lambda
AWS Lambda是最早也是最成熟的无服务器平台,支持多种编程语言。
核心特性:
- 支持Node.js、Python、Java、Go、C#、Ruby等
- 最大执行时间15分钟
- 内存配置128MB-10GB
- 支持容器镜像部署
触发器类型:
- API Gateway(HTTP请求)
- S3(文件上传)
- DynamoDB(数据变更)
- Kinesis(数据流)
- SQS(消息队列)
- CloudWatch Events(定时任务)
Azure Functions
Azure Functions是微软的无服务器平台,与Azure生态深度集成。
核心特性:
- 支持C#、JavaScript、Python、Java、PowerShell等
- 消费计划最大执行时间10分钟
- 高级计划支持更长执行时间
- 支持Durable Functions实现有状态工作流
Google Cloud Functions
Google Cloud Functions是GCP的无服务器平台,与Google服务集成。
核心特性:
- 支持Node.js、Python、Java、Go、Ruby、.NET
- 最大执行时间9分钟
- 支持HTTP触发和事件触发
阿里云函数计算
阿里云函数计算是国内主流的无服务器平台。
核心特性:
- 支持Node.js、Python、Java、PHP、Go、.NET等
- 最大执行时间10分钟
- 支持自定义运行时和容器镜像
函数开发实践
函数示例
以AWS Lambda为例,一个简单的Python函数:
import json
def lambda_handler(event, context):
name = event.get('name', 'World')
response = {
'statusCode': 200,
'body': json.dumps({
'message': f'Hello, {name}!'
})
}
return response
这个函数接收一个事件对象,返回一个问候消息。
函数设计原则
单一职责:每个函数只做一件事,保持函数简单和专注。
无状态:函数应该是无状态的,状态存储在外部服务(如数据库、缓存)中。
幂等性:函数应该是幂等的,多次执行产生相同的结果。
快速执行:函数执行时间应该尽量短,避免冷启动影响。
处理冷启动
冷启动是无服务器架构的挑战之一。当函数长时间未执行后首次调用,需要初始化运行环境,导致延迟增加。
缓解策略:
预热函数:定期调用函数保持运行环境"热"。
减少依赖:减少函数的依赖包大小,加快初始化速度。
使用预置并发:预先初始化一定数量的函数实例(AWS Provisioned Concurrency)。
选择合适的语言:编译型语言(如Go)通常比解释型语言冷启动更快。
无服务器应用场景
API后端
无服务器非常适合构建API后端。API Gateway + Lambda的组合可以快速构建RESTful API。
优势:
- 自动扩展应对流量波动
- 按请求量付费,成本可控
- 无需管理服务器
示例架构:
客户端 → API Gateway → Lambda → DynamoDB
数据处理
无服务器适合事件驱动的数据处理场景。
典型场景:
- 图片处理:S3上传触发Lambda进行缩放、压缩
- 日志处理:日志写入触发函数进行分析
- 数据转换:数据库变更触发数据转换
定时任务
无服务器可以替代传统的定时任务服务器。
示例:
# AWS EventBridge规则
{
"ScheduleExpression": "rate(1 day)",
"Targets": [{
"Arn": "arn:aws:lambda:region:account:function:my-function",
"Id": "daily-task"
}]
}
聊天机器人
无服务器适合处理聊天机器人的请求,响应时间短,请求量波动大。
IoT数据处理
IoT设备产生的事件可以触发无服务器函数进行处理,适合事件驱动的场景。
无服务器框架
Serverless Framework
Serverless Framework是最流行的无服务器应用开发框架,支持多个云平台。
配置示例:
service: my-service
provider:
name: aws
runtime: python3.9
region: us-east-1
functions:
hello:
handler: handler.hello
events:
- http:
path: hello
method: get
resources:
Resources:
MyBucket:
Type: AWS::S3::Bucket
部署命令:
serverless deploy
AWS SAM
AWS Serverless Application Model(SAM)是AWS官方的无服务器应用框架。
模板示例:
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Resources:
HelloWorldFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: src/
Handler: app.lambda_handler
Runtime: python3.9
Events:
HelloWorld:
Type: Api
Properties:
Path: /hello
Method: get
Terraform
Terraform是通用的基础设施即代码工具,也支持无服务器资源。
配置示例:
resource "aws_lambda_function" "my_function" {
filename = "function.zip"
function_name = "my_function"
role = aws_iam_role.lambda_role.arn
handler = "index.handler"
runtime = "python3.9"
}
resource "aws_api_gateway_integration" "lambda" {
rest_api_id = aws_api_gateway_rest_api.api.id
resource_id = aws_api_gateway_resource.resource.id
http_method = aws_api_gateway_method.method.http_method
integration_http_method = "POST"
type = "AWS_PROXY"
uri = aws_lambda_function.my_function.invoke_arn
}
无服务器的挑战与解决方案
冷启动问题
挑战:函数长时间未执行后首次调用延迟增加。
解决方案:
- 使用预置并发
- 减少依赖包大小
- 选择冷启动快的语言
执行时间限制
挑战:函数有最大执行时间限制,不适合长时间运行的任务。
解决方案:
- 将长任务拆分为多个短任务
- 使用Step Functions编排工作流
- 对于长时间任务,考虑使用容器服务
本地开发调试
挑战:无服务器应用依赖云服务,本地开发调试困难。
解决方案:
- 使用SAM Local、Serverless Offline等工具在本地模拟
- 使用Docker模拟云服务
- 编写单元测试,减少对云服务的依赖
厂商锁定
挑战:不同云平台的无服务器API不同,迁移成本高。
解决方案:
- 使用跨平台的框架(如Serverless Framework)
- 抽象云服务接口
- 使用容器化部署,提高可移植性
无服务器最佳实践
函数代码最佳实践
利用执行环境复用提升性能:
Lambda 函数的执行环境在处理完请求后不会立即销毁,而是会保留一段时间。后续调用如果被路由到同一个执行环境,可以复用之前初始化的资源。合理利用这一点可以显著提升性能。
import json
import boto3
# 在函数处理器外部初始化——这些会被复用
s3_client = boto3.client('s3')
dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('my-table')
# 缓存静态数据到 /tmp 目录
import os
if not os.path.exists('/tmp/cached_data.json'):
# 首次调用时加载并缓存
with open('/tmp/cached_data.json', 'w') as f:
json.dump(load_config(), f)
def lambda_handler(event, context):
# 每次调用都使用已初始化的客户端
response = table.get_item(Key={'id': event['id']})
return response
关键点:
- SDK 客户端和数据库连接应在处理器外部初始化
- 静态资源可以缓存到
/tmp目录(最大 10GB) - 不要在执行环境中存储用户数据或敏感信息
编写幂等性代码:
幂等性意味着多次执行同一操作产生相同的结果。这对于处理重复事件至关重要,因为 Lambda 可能会重试失败的请求,或者事件源可能发送重复消息。
import hashlib
def lambda_handler(event, context):
# 使用事件中的唯一标识符作为幂等性键
idempotency_key = event['requestId']
# 检查是否已处理过
if already_processed(idempotency_key):
return {'status': 'already_processed'}
try:
# 执行业务逻辑
result = process_event(event)
# 记录处理状态
mark_as_processed(idempotency_key)
return result
except Exception as e:
# 清理部分处理的结果
cleanup(idempotency_key)
raise e
实现幂等性的策略:
- 使用数据库唯一约束防止重复插入
- 在处理前检查处理状态
- 使用分布式锁确保同一时间只有一个实例处理
使用 Keep-Alive 保持连接:
Lambda 会清理空闲连接,在函数调用时重用空闲连接会导致连接错误。
// Node.js 示例
const https = require('https');
// 创建 agent 并启用 keep-alive
const agent = new https.Agent({
keepAlive: true,
maxSockets: 50,
keepAliveMsecs: 120000 // 2 分钟
});
// 使用 agent 创建请求
const options = {
hostname: 'api.example.com',
agent: agent
};
避免递归调用:
函数调用自身或触发会再次调用函数的过程,可能导致无限循环和成本失控。
# 错误示例:递归调用
def lambda_handler(event, context):
result = process_data(event)
if needs_more_processing(result):
# 危险:可能导致无限循环
lambda_client.invoke(
FunctionName=context.function_name,
Payload=json.dumps(result)
)
return result
# 正确示例:使用 Step Functions 或 SQS
def lambda_handler(event, context):
result = process_data(event)
if needs_more_processing(result):
# 将后续处理发送到 SQS
sqs.send_message(
QueueUrl=queue_url,
MessageBody=json.dumps(result)
)
return result
函数配置最佳实践
内存配置优化:
Lambda 的 CPU 性能与内存配置成正比。增加内存不仅增加可用内存,也会提升 CPU 性能。
# 内存与 CPU 的关系
# 128 MB = ~1 vCPU 的 1/12
# 512 MB = ~1 vCPU 的 1/3
# 1024 MB = ~1 vCPU 的 2/3
# 1769 MB = 1 vCPU
# 更高内存 = 更多 vCPU
使用 AWS Lambda Power Tuning 工具找到最佳内存配置:
# 使用 AWS Lambda Power Tuning
sasr -r us-east-1 -n my-function -s 128 -e 10240 -i 50
分析 CloudWatch 日志中的内存使用情况:
REPORT RequestId: xxx Duration: 12.34 ms Billed Duration: 100 ms
Memory Size: 128 MB Max Memory Used: 18 MB
超时配置:
根据实际执行时间设置合理的超时值。过短会导致正常请求失败,过长会延迟错误检测。
# 测量函数执行时间
import time
def lambda_handler(event, context):
start = time.time()
result = process_request(event)
elapsed = time.time() - start
print(f"Processing took {elapsed:.2f} seconds")
# 如果接近超时,记录告警
remaining = context.get_remaining_time_in_millis()
if remaining < 1000: # 少于 1 秒
print(f"WARNING: Only {remaining}ms remaining!")
return result
使用 ARM64 (Graviton) 架构:
Graviton 处理器提供更好的性价比,对于大多数工作负载可以降低成本并提升性能。
# 使用 AWS CLI 更新架构
aws lambda update-function-configuration \
--function-name my-function \
--architectures arm64
并发配置:
- 预留并发:为函数保留特定数量的执行环境,防止其他函数占用所有并发
- 预置并发:预先初始化执行环境,消除冷启动延迟
# 设置预留并发
aws lambda put-function-concurrency \
--function-name my-function \
--reserved-concurrent-executions 100
# 配置预置并发
aws lambda put-provisioned-concurrency-config \
--function-name my-function \
--qualifier LIVE \
--provisioned-concurrent-executions 50
监控和告警最佳实践
使用 CloudWatch 监控关键指标:
| 指标 | 说明 | 告警阈值建议 |
|---|---|---|
| Duration | 函数执行时间 | 接近超时值的 80% |
| Invocations | 调用次数 | 异常突增 |
| Errors | 错误次数 | > 0 |
| Throttles | 被限流的调用 | > 0 |
| IteratorAge | 流处理延迟 | > 30000ms (30秒) |
| ConcurrentExecutions | 并发执行数 | 接近账户限制 |
# CloudWatch 告警配置示例
Resources:
LambdaErrorAlarm:
Type: AWS::CloudWatch::Alarm
Properties:
AlarmName: LambdaErrors
MetricName: Errors
Namespace: AWS/Lambda
Dimensions:
- Name: FunctionName
Value: my-function
Statistic: Sum
Period: 60
EvaluationPeriods: 1
Threshold: 1
ComparisonOperator: GreaterThanThreshold
TreatMissingData: notBreaching
使用 Embedded Metric Format (EMF) 发送指标:
EMF 允许通过日志异步发送指标,减少延迟和成本。
import json
def lambda_handler(event, context):
# 执行业务逻辑
result = process_event(event)
# 使用 EMF 发送自定义指标
print(json.dumps({
"_aws": {
"Timestamp": int(time.time() * 1000),
"CloudWatchMetrics": [{
"Namespace": "MyApplication",
"Dimensions": [["FunctionName"]],
"Metrics": [
{"Name": "ProcessingTime", "Unit": "Milliseconds"},
{"Name": "RecordsProcessed", "Unit": "Count"}
]
}]
},
"FunctionName": context.function_name,
"ProcessingTime": result['duration'],
"RecordsProcessed": result['count']
}))
return result
结构化日志记录:
使用 JSON 格式的日志便于搜索和分析。
import json
import logging
# 配置结构化日志
logger = logging.getLogger()
logger.setLevel(logging.INFO)
class JsonFormatter(logging.Formatter):
def format(self, record):
return json.dumps({
"timestamp": self.formatTime(record),
"level": record.levelname,
"message": record.getMessage(),
"function": record.name
})
handler = logging.StreamHandler()
handler.setFormatter(JsonFormatter())
logger.addHandler(handler)
def lambda_handler(event, context):
logger.info("Processing started", extra={
"request_id": context.aws_request_id,
"event_type": event.get('type')
})
result = process_event(event)
logger.info("Processing completed", extra={
"records_processed": len(result)
})
return result
分布式追踪:
使用 AWS X-Ray 追踪函数调用链。
from aws_xray_sdk.core import xray_recorder
from aws_xray_sdk.core import patch_all
# 自动追踪 boto3 调用
patch_all()
def lambda_handler(event, context):
# 创建子分段
with xray_recorder.capture('process_event'):
result = process_event(event)
# 添加注解和元数据
xray_recorder.put_annotation('user_id', event['user_id'])
xray_recorder.put_metadata('processing_time', result['duration'])
return result
安全最佳实践
最小权限 IAM 策略:
只授予函数完成任务所需的最小权限。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject"
],
"Resource": "arn:aws:s3:::my-bucket/*"
},
{
"Effect": "Allow",
"Action": [
"dynamodb:PutItem",
"dynamodb:GetItem"
],
"Resource": "arn:aws:dynamodb:region:account:table/my-table"
}
]
}
保护敏感信息:
不要在代码或环境变量中硬编码敏感信息。
import boto3
# 使用 Secrets Manager
secrets_client = boto3.client('secretsmanager')
def get_database_credentials():
response = secrets_client.get_secret_value(SecretId='db-credentials')
return json.loads(response['SecretString'])
# 使用 SSM Parameter Store
ssm_client = boto3.client('ssm')
def get_api_key():
response = ssm_client.get_parameter(
Name='/my-app/api-key',
WithDecryption=True
)
return response['Parameter']['Value']
VPC 配置:
当需要访问 VPC 内资源时,正确配置 VPC 访问。
Resources:
MyFunction:
Type: AWS::Lambda::Function
Properties:
VpcConfig:
SubnetIds:
- subnet-12345
- subnet-67890
SecurityGroupIds:
- sg-12345
注意事项:
- VPC 内的 Lambda 函数无法直接访问公网
- 需要配置 NAT 网关或 VPC 端点访问 AWS 服务
- 冷启动延迟会增加(需要配置 ENI)
启用 GuardDuty Lambda 保护:
GuardDuty 可以监控 Lambda 函数的网络活动,检测潜在的安全威胁。
成本优化最佳实践
合理配置内存:
过高的内存配置是浪费,过低会影响性能。使用 Power Tuning 工具找到最佳配置。
使用预置并发优化成本:
对于稳定的工作负载,预置并发可以:
- 消除冷启动延迟
- 比按需付费更经济(对于稳定流量)
监控和分析成本:
# 使用 Cost Explorer 查看 Lambda 成本
aws ce get-cost-and-usage \
--time-period Start=2024-01-01,End=2024-01-31 \
--granularity DAILY \
--metrics BlendedCost \
--group-by Type=DIMENSION,Key=SERVICE \
--filter file://lambda-filter.json
清理未使用的函数:
# 列出所有函数
aws lambda list-functions --query 'Functions[*].FunctionName'
# 删除未使用的函数
aws lambda delete-function --function-name unused-function
流处理最佳实践
批处理配置:
优化批处理大小和窗口可以提升吞吐量。
def lambda_handler(event, context):
# 处理一批记录
batch_item_failures = []
for record in event['Records']:
try:
process_record(record)
except Exception as e:
# 记录失败项,Lambda 只会重试失败的记录
batch_item_failures.append({
'itemIdentifier': record['messageId']
})
# 返回部分批处理响应
return {'batchItemFailures': batch_item_failures}
Kinesis 优化:
- 增加分片数可以线性提升吞吐量
- 使用良好的分区键确保数据均匀分布
- 监控 IteratorAge 指标检测处理延迟
小结
无服务器计算让开发者无需管理服务器,专注于业务逻辑开发。它适合事件驱动、短执行时间、流量波动大的场景。主流平台包括 AWS Lambda、Azure Functions、Google Cloud Functions 等。
掌握无服务器最佳实践对于构建高效、可靠、经济的云原生应用至关重要:
- 代码实践:利用执行环境复用、编写幂等代码、避免递归调用
- 配置优化:合理设置内存、使用 Graviton 架构、配置并发
- 监控告警:使用 CloudWatch 和 X-Ray、结构化日志、EMF 指标
- 安全防护:最小权限原则、保护敏感信息、VPC 配置
- 成本优化:优化内存配置、监控使用量、清理未使用资源
无服务器架构带来了冷启动、执行时间限制等挑战,需要合理设计函数、选择合适的场景。随着技术的发展,无服务器正在成为云原生应用开发的重要选择。
下一章我们将探讨云数据库,了解云计算中数据存储和管理的各种方案。