AWS 第3模块:Lambda 与 Go

发布: (2025年12月15日 GMT+8 06:21)
7 min read
原文: Dev.to

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?

诚实的基准(我自己的测量)

语言冷启动使用内存热调用
Go156 ms48 MB45 ms
Node.js342 ms89 MB89 ms
Python478 ms127 MB134 ms
Java1,240 ms186 MB203 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 以及其他资源,依据你的基础设施需求。)

Back to Blog

相关文章

阅读更多 »

AWS Terraform IAM 用户管理

介绍 在 AWS 中手动管理 IAM 用户很快会变得复杂、容易出错且难以扩展。随着团队规模的扩大,您需要一种可重复、可审计的方式来管理这些用户。

第16天:创建 IAM 用户

封面图片(第 16 天)创建 IAM 用户 https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads...