跳到主要内容

无服务器计算

无服务器计算(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 配置
  • 成本优化:优化内存配置、监控使用量、清理未使用资源

无服务器架构带来了冷启动、执行时间限制等挑战,需要合理设计函数、选择合适的场景。随着技术的发展,无服务器正在成为云原生应用开发的重要选择。

下一章我们将探讨云数据库,了解云计算中数据存储和管理的各种方案。