AWS 第3模块:Lambda 与 Go
Source: Dev.to
在不离开我的 Mac 的情况下为 Linux 编译(费用仅 $0.06)
系列: AWS Zero to Architect - 第 3 章节
阅读时间: 20 分钟
实现时间: 120 分钟
在前面的章节中我们配置了 AWS、Terraform 和 IAM。现在轮到有趣的部分:创建你的第一个 Go Lambda,费用只有几分钱,而且比 Python 快 3 倍。
🤔 我们都面临的问题
常见场景
“我有一个简单的 API。我要不要部署一个 24/7 运行的服务器,花 $50/月,即使它每天只收到 100 次请求?”
答案: 不要。使用 Lambda。
⚡ Lambda 解释:餐车 vs 餐厅
🏢 餐厅(EC2 – 传统服务器)
- 租整间:$500/月
- 24/7 支付水电气费
- 全职员工
- 不管是 3 位客户还是 300 位,费用相同
- 高峰时,店面会饱和
固定成本: $500/月
🚚 餐车(Lambda – 无服务器)
- 只在提供服务时付费
- 每 100 万次请求 $0.20
- 无固定员工(由 AWS 管理)
- 自动弹性伸缩
- 3 位客户 = $0.0006
- 300 位客户 = $0.06
可变成本: $0‑$100/月
哪种方式更适合流量不稳定的 API?
💰 实际定价(无营销噱头)
我的当前 Lambda
100,000 次请求/月
128 MB 内存
平均执行时间 200 ms
计算:
请求费用: 100,000 ÷ 1,000,000 × $0.20 = $0.02
计算费用: 0.128 GB × 0.2 s × 100,000 × $0.0000166667 = $0.04
合计: $0.06/月
免费层
- 每月 100 万次请求
- 400,000 GB‑秒
翻译: 开发和小型应用免费。
🐹 为什么选 Go 而不是 Python/Node.js?
诚实的基准(我自己的测量)
| 语言 | 冷启动 | 使用内存 | 热调用 |
|---|---|---|---|
| Go | 156 ms | 48 MB | 45 ms |
| Node.js | 342 ms | 89 MB | 89 ms |
| Python | 478 ms | 127 MB | 134 ms |
| Java | 1,240 ms | 186 MB | 203 ms |
结果: Go 的冷启动速度是 3 倍 更快。
什么是冷启动?
当天的第一次调用:
1. AWS 创建容器 → 100 ms
2. 加载运行时(Node.js/Python) → 200 ms
3. 加载代码 → 100 ms
总计: ~400 ms
使用 Go:
1. AWS 创建容器 → 100 ms
2. 直接执行二进制 → 50 ms
总计: ~150 ms
对最终用户而言: Go 大约 150 ms 响应,Python 大约 400 ms。
成本优势
Python (512 MB): $0.0000083 每 100 ms
Go (128 MB): $0.0000021 每 100 ms
节省: 75 %
每月 1 百万请求
- Python: $83/月
- Go: $21/月
差额: $62/月 × 12 个月 = $744/年
🏗️ 架构:Serverless 中的六边形架构
什么是六边形架构?
核心理念: 业务逻辑不应依赖于 AWS。
// ❌ 错误(耦合到 AWS)
func CreateSession(userID string) {
dynamodb.PutItem(...) // 直接调用 AWS
}
// ✅ 正确(解耦)
// Domain(纯 Go,未引用 AWS)
type Session struct {
ID string
UserID string
}
func NewSession(userID string) *Session { ... }
// Adapter(翻译为 AWS 调用)
type DynamoDBRepo struct { ... }
func (r *DynamoDBRepo) Save(session *Session) { ... }
优势
- ✅ 可以在不改动领域层的情况下把 DynamoDB 换成 Postgres
- ✅ 可在本地进行单元测试,无需 AWS
- ✅ 业务逻辑更清晰
我的项目结构
go-hexagonal-auth/
├── cmd/lambda/main.go # Lambda 处理函数
├── internal/
│ ├── core/domain/
│ │ └── session.go # 业务逻辑
│ └── adapters/repository/
│ └── dynamodb_session.go # AWS 适配层
└── terraform/
└── lambda.tf # 基础设施
💻 真正重要的代码
领域模型(不依赖 AWS)
package domain
import (
"time"
"github.com/google/uuid"
)
type Session struct {
SessionID string
UserID string
ExpiresAt time.Time
CreatedAt time.Time
}
func NewSession(userID string, ttl time.Duration) *Session {
now := time.Now()
return &Session{
SessionID: uuid.New().String(),
UserID: userID,
CreatedAt: now,
ExpiresAt: now.Add(ttl),
}
}
func (s *Session) IsValid() bool {
return s.SessionID != "" && s.UserID != ""
}
说明: 完全没有 AWS import,纯业务逻辑。
DynamoDB 适配器
package repository
import (
"context"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/dynamodb"
"go-hexagonal-auth/internal/core/domain"
)
type DynamoDBRepo struct {
client *dynamodb.Client
tableName string
}
func (r *DynamoDBRepo) Save(ctx context.Context, session *domain.Session) error {
item := map[string]interface{}{
"session_id": session.SessionID,
"user_id": session.UserID,
"expires_at": session.ExpiresAt.Unix(),
}
_, err := r.client.PutItem(ctx, &dynamodb.PutItemInput{
TableName: aws.String(r.tableName),
Item: marshalMap(item),
})
return err
}
Lambda 处理函数
package main
import (
"context"
"encoding/json"
"time"
"github.com/aws/aws-lambda-go/lambda"
"go-hexagonal-auth/internal/core/domain"
"go-hexagonal-auth/internal/adapters/repository"
)
type APIGatewayRequest struct {
Body string `json:"body"`
}
type CreateSessionRequest struct {
UserID string `json:"user_id"`
}
type Response struct {
StatusCode int `json:"statusCode"`
Body string `json:"body"`
}
func handler(ctx context.Context, request APIGatewayRequest) (Response, error) {
// 1. 解析输入
var req CreateSessionRequest
if err := json.Unmarshal([]byte(request.Body), &req); err != nil {
return Response{StatusCode: 400, Body: err.Error()}, err
}
// 2. 创建会话(领域逻辑)
session := domain.NewSession(req.UserID, 24*time.Hour)
// 3. 保存(适配层)
repo := repository.NewDynamoDBRepo(ctx)
if err := repo.Save(ctx, session); err != nil {
return Response{StatusCode: 500, Body: err.Error()}, err
}
// 4. 返回响应
body, _ := json.Marshal(session)
return Response{
StatusCode: 201,
Body: string(body),
}, nil
}
func main() {
lambda.Start(handler)
}
流程: 解析 JSON → 业务逻辑(domain) → 持久化(adapter) → 返回结果。
🔨 跨平台编译(Go 的魔法)
问题
开发环境: macOS ARM64 (M1/M2/M3)
Lambda 运行环境: Linux ARM64
如何在 macOS 上直接编译出 Linux 可执行文件?
Go 的解决方案
# 一条命令搞定
GOOS=linux GOARCH=arm64 go build -o bootstrap ./cmd/lambda
就这么简单。 不需要 Docker、虚拟机或 CI/CD 环境,Go 能原生交叉编译。
Makefile(自动化)
build:
GOOS=linux GOARCH=arm64 CGO_ENABLED=0 \
go build -ldflags="-s -w" \
-o build/bootstrap ./cmd/lambda
zip: build
cd build && zip lambda.zip bootstrap
常用命令
make build # 为 Lambda 编译
make zip # 生成 lambda.zip
文件大小
build/bootstrap: 7.2 MB(未压缩)
build/lambda.zip: 2.8 MB(压缩后)
关键编译标志
-ldflags="-s -w" # 减少体积 30‑40 %
CGO_ENABLED=0 # 生成静态二进制(无 C 依赖)
🚀 使用 Terraform 部署
Lambda 配置
resource "aws_lambda_function" "create_session" {
filename = "../build/lambda.zip"
function_name = "go-hexagonal-auth-dev-create-session"
role = aws_iam_role.lambda_exec.arn
handler = "bootstrap"
runtime = "provided.al2"
memory_size = 128
timeout = 10
# 可选:环境变量、VPC 等
}
(后续请继续配置 IAM、DynamoDB 以及其他资源,依据你的基础设施需求。)