View a markdown version of this page

定义采用 Go 的 Lambda 函数处理程序 - AWS Lambda

定义采用 Go 的 Lambda 函数处理程序

Lambda 函数处理程序是函数代码中处理事件的方法。当调用函数时,Lambda 运行处理程序方法。您的函数会一直运行,直到处理程序返回响应、退出或超时。

本页介绍如何使用采用 Go 的 Lambda 函数处理程序,包括项目设置、命名约定和最佳实践。本页还包括 Go Lambda 函数的示例,在示例中该函数接收订单信息,生成文本文件收据,然后将此文件放入 Amazon Simple Storage Service(Amazon S3)存储桶中。有关如何在编写函数后部署函数的信息,请参阅使用 .zip 文件归档部署 Go Lambda 函数或使用容器镜像部署 Go Lambda 函数。

设置 Go 处理程序项目

在 Go 中编写的 Lambda 函数被编写为 Go 可执行文件。与初始化任何其他 Go 项目的方法相同,您可以使用以下 go mod init 命令初始化 Go Lambda 函数项目:

go mod init example-go

此处,example-go 表示模块名称。您可以使用任意值替换。此命令将初始化项目,并生成列出项目依赖项的 go.mod 文件。

使用 go get 命令将任何外部依赖项添加到项目中。例如,在采用 Go 的 Lambda 函数中,需要包含 github.com/aws/aws-lambda-go/lambda 包,该包将实现适用于 Go 的 Lambda 编程模型。使用以下 go get 命令安装此包:

go get github.com/aws/aws-lambda-go

函数代码应该位于 Go 文件中。在以下示例中,我们将此文件命名为 main.go。在此文件中,您可以在处理程序方法以及调用此处理程序的 main() 函数中实现核心函数逻辑。

示例 Go Lambda 函数代码

以下示例 Go Lambda 函数代码接收有关订单的信息,生成文本文件接收,并将此文件放入 Amazon S3 存储桶中。

例 main.go Lambda 函数
package main import ( "context" "encoding/json" "fmt" "log" "os" "strings" "github.com/aws/aws-lambda-go/lambda" "github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/service/s3" ) type Order struct { OrderID string `json:"order_id"` Amount float64 `json:"amount"` Item string `json:"item"` } var ( s3Client *s3.Client ) func init() { // Initialize the S3 client outside of the handler, during the init phase cfg, err := config.LoadDefaultConfig(context.TODO()) if err != nil { log.Fatalf("unable to load SDK config, %v", err) } s3Client = s3.NewFromConfig(cfg) } func uploadReceiptToS3(ctx context.Context, bucketName, key, receiptContent string) error { _, err := s3Client.PutObject(ctx, &s3.PutObjectInput{ Bucket: &bucketName, Key: &key, Body: strings.NewReader(receiptContent), }) if err != nil { log.Printf("Failed to upload receipt to S3: %v", err) return err } return nil } func handleRequest(ctx context.Context, event json.RawMessage) error { // Parse the input event var order Order if err := json.Unmarshal(event, &order); err != nil { log.Printf("Failed to unmarshal event: %v", err) return err } // Access environment variables bucketName := os.Getenv("RECEIPT_BUCKET") if bucketName == "" { log.Printf("RECEIPT_BUCKET environment variable is not set") return fmt.Errorf("missing required environment variable RECEIPT_BUCKET") } // Create the receipt content and key destination receiptContent := fmt.Sprintf("OrderID: %s\nAmount: $%.2f\nItem: %s", order.OrderID, order.Amount, order.Item) key := "receipts/" + order.OrderID + ".txt" // Upload the receipt to S3 using the helper method if err := uploadReceiptToS3(ctx, bucketName, key, receiptContent); err != nil { return err } log.Printf("Successfully processed order %s and stored receipt in S3 bucket %s", order.OrderID, bucketName) return nil } func main() { lambda.Start(handleRequest) }

此 main.go 文件包含以下代码部分:

  • package main:在 Go 中,包含 func main() 函数的包必须始终名为 main。

  • import 数据块:使用此数据块来包含 Lambda 函数所需的库。

  • type Order struct {} 数据块:在此 Go 结构中定义预期输入事件的形状。

  • var () 数据块:使用此数据块定义您将在 Lambda 函数中使用的任何全局变量。

  • func init() {}:在此 init() 方法中,包含您希望 Lambda 在初始化阶段运行的任何代码。

  • func uploadReceiptToS3(...) {}:这是主 handleRequest 处理程序方法引用的帮助程序方法。

  • func handleRequest(ctx context.Context, event json.RawMessage) error {}:这是包含主应用程序逻辑的主处理程序方法。

  • func main() {}:这是 Lambda 处理程序必需的入口点。lambda.Start() 方法的参数是主处理程序方法。

要此函数正常运行,其执行角色必须允许 s3:PutObject 操作。此外,请确保您定义了 RECEIPT_BUCKET 环境变量。成功调用后,Amazon S3 存储桶应包含接收文件。

处理程序命名约定

对于采用 Go 的 Lambda 函数,处理程序可以使用任何名称。在此示例中,处理程序方法被命名为 handleRequest。要在代码中引用处理程序值,可以使用 _HANDLER 环境变量。

对于使用 .zip 部署包的 Go 函数,包含函数代码的可执行文件必须命名为 bootstrap。此外,bootstrap 文件必须位于 .zip 文件的根目录中。对于使用容器映像的 Go 函数,可执行文件可以使用任何名称。

定义和访问输入事件对象

JSON 是 Lambda 函数最常用且最标准的输入格式。在此示例中,该函数需要类似于下方的输入:

{ "order_id": "12345", "amount": 199.99, "item": "Wireless Headphones" }

在使用采用 Go 的 Lambda 函数时,您可以将预期输入事件的形状定义为 Go 结构。在此示例中,我们定义结构来表示 Order:

type Order struct { OrderID string `json:"order_id"` Amount float64 `json:"amount"` Item string `json:"item"` }

此结构与预期的输入形状相匹配。定义结构后,您可以编写处理程序签名,该签名采用与 encoding/json 标准库兼容的通用 JSON 类型。然后,您可以使用 func Unmarshal 函数将其反序列化到结构中。处理程序的前几行对此进行了说明:

func handleRequest(ctx context.Context, event json.RawMessage) error { // Parse the input event var order Order if err := json.Unmarshal(event, &order); err != nil { log.Printf("Failed to unmarshal event: %v", err) return err ... }

反序列化后,您可以访问 order 变量的字段。例如,order.OrderID 从原始输入中检索 "order_id" 的值。

注意

该 encoding/json 包只能访问导出的字段。若要导出,事件结构中的字段名称必须大写。

访问和使用 Lambda 上下文对象

Lambda 上下文对象包含有关调用、函数和执行环境的信息。在此例中,我们在处理程序签名中将此变量声明为 ctx:

func handleRequest(ctx context.Context, event json.RawMessage) error { ... }

ctx context.Context 输入是函数处理程序中的可选参数。有关所接受处理程序签名的更多信息,请参阅Go 处理程序的有效处理程序签名。

如果您使用 AWS SDK 调用其他服务,则多个关键区域中均需要上下文对象。例如,要正确初始化 SDK 客户端,您可以使用上下文对象加载正确的 AWS SDK 配置,如下所示:

// Load AWS SDK configuration using the default credential provider chain cfg, err := config.LoadDefaultConfig(ctx)

SDK 调用自身时可能需要上下文对象作为输入。例如,该 s3Client.PutObject 调用接受上下文对象作为其第一个参数:

// Upload the receipt to S3 _, err = s3Client.PutObject(ctx, &s3.PutObjectInput{ ... })

除了 AWS SDK 请求之外,您还可以使用上下文对象进行函数监控。有关上下文对象的更多信息,请参阅使用 Lambda 上下文对象检索 Go 函数信息。

Go 处理程序的有效处理程序签名

在 Go 中构建 Lambda 函数处理程序时,您有多个选项,但您必须遵守以下规则:

  • 处理程序必须为函数。

  • 处理程序可能需要 0 到 2 个参数。如果有两个参数,则第一个参数必须实现 context.Context。

  • 处理程序可能返回 0 到 2 个参数。如果有一个返回值,则它必须实现 error。如果有两个返回值,则第二个值必须实现 error。

下面列出了有效的处理程序签名。TIn 和 TOut 表示类型与 encoding/json 标准库兼容。有关更多信息,请参阅 func Unmarshal,以了解如何反序列化这些类型。

  • func ()
  • func () error
  • func () (TOut, error)
  • func (TIn) error
  • func (TIn) (TOut, error)
  • func (context.Context) error
  • func (context.Context) (TOut, error)
  • func (context.Context, TIn) error
  • func (context.Context, TIn) (TOut, error)

在处理程序中使用 适用于 Go 的 AWS SDK v2

通常,您将使用 Lambda 函数与其他 AWS 资源进行交互或对其进行更新。与此类资源最简单的交互方法是使用 适用于 Go 的 AWS SDK v2。

注意

适用于 Go 的 AWS SDK(v1)处于维护模式,其支持的终止日期为 2025 年 7 月 31 日。我们建议您继续仅使用 适用于 Go 的 AWS SDK v2。

要向函数添加 SDK 依赖项,请使用适用于所需特定 SDK 客户端的 go get 命令。在上述示例代码中,我们使用了 config 库和 s3 库。在包含 go.mod 和 main.go 文件的目录中,通过运行以下命令添加这些依赖项:

go get github.com/aws/aws-sdk-go-v2/config go get github.com/aws/aws-sdk-go-v2/service/s3

然后,相应地向函数的导入数据块中导入依赖项:

import ( ... "github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/service/s3" )

在处理程序中使用 SDK 时,请使用正确的设置配置客户端。最简单的方法是使用默认的凭证提供程序链。此示例说明了加载此配置的一种方法:

// Load AWS SDK configuration using the default credential provider chain cfg, err := config.LoadDefaultConfig(ctx) if err != nil { log.Printf("Failed to load AWS SDK config: %v", err) return err }

将此配置加载到 cfg 变量后,您可以将此变量传入至客户端实例化中。示例代码会按如下方式创建 Amazon S3 客户端:

// Create an S3 client s3Client := s3.NewFromConfig(cfg)

在此示例中,我们在 init() 函数中初始化了 Amazon S3 客户端,以免每次调用函数时都必须对其进行初始化。但问题在于,Lambda 无权在 init() 函数中访问上下文对象。作为解决方法,您可以模拟初始化阶段的 context.TODO() 传入占位符。稍后,使用客户端进行调用时,传入完整的上下文对象。此解决方法也在 在 AWS SDK 客户端初始化和调用中使用上下文 中进行了介绍。

配置并初始化 SDK 客户端后,您可以使用它与其他 AWS 服务进行交互。示例代码按如下方式调用 Amazon S3 PutObject API:

_, err = s3Client.PutObject(ctx, &s3.PutObjectInput{ Bucket: &bucketName, Key: &key, Body: strings.NewReader(receiptContent), })

评估环境变量

在处理程序代码中,您可以使用 os.Getenv() 方法引用任何环境变量。在此示例中,我们使用以下代码行引用已定义的 RECEIPT_BUCKET 环境变量:

// Access environment variables bucketName := os.Getenv("RECEIPT_BUCKET") if bucketName == "" { log.Printf("RECEIPT_BUCKET environment variable is not set") return fmt.Errorf("missing required environment variable RECEIPT_BUCKET") }

使用全局状态

为避免每次调用函数时创建新资源,您可以在 Lambda 函数的处理程序代码之外声明并修改全局变量。您可以在 var 数据块或语句中定义这些全局变量。此外,处理程序可能要声明 init() 函数,该函数在初始化阶段执行。init 方法与在 AWS Lambda 中的行为方式相同,正如在标准 Go 程序中一样。

Go Lambda 函数的代码最佳实践

在构建 Lambda 函数时,请遵循以下列表中的指南,采用最佳编码实践:

  • 从核心逻辑中分离 Lambda 处理程序。通过这种方法,您可以创建一个更易于进行单元测试的函数。

  • 将依赖关系的复杂性降至最低。首选在执行环境启动时可以快速加载的更简单的框架。

  • 将部署包大小精简为只包含运行时必要的部分。这样会减少调用前下载和解压缩部署包所需的时间。

利用执行环境重用来提高函数性能。连接软件开发工具包 (SDK) 客户端和函数处理程序之外的数据库,并在 /tmp 目录中本地缓存静态资产。由函数的同一实例处理的后续调用可重用这些资源。这样就可以通过缩短函数运行时间来节省成本。

为了避免调用之间潜在的数据泄露,请不要使用执行环境来存储用户数据、事件或其他具有安全影响的信息。如果您的函数依赖于无法存储在处理程序的内存中的可变状态,请考虑为每个用户创建单独的函数或单独的函数版本。

使用 keep-alive 指令来维护持久连接。Lambda 会随着时间的推移清除空闲连接。在调用函数时尝试重用空闲连接会导致连接错误。要维护您的持久连接,请使用与运行时关联的 keep-alive 指令。有关示例,请参阅在 Node.js 中通过 Keep-Alive 重用连接。

使用环境变量将操作参数传递给函数。例如,您在写入 Amazon S3 存储桶时,不应对要写入的存储桶名称进行硬编码,而应将存储桶名称配置为环境变量。

避免在 Lambda 函数中使用递归调用,在这种情况下,函数会调用自己或启动可能再次调用该函数的进程。这可能会导致意想不到的函数调用量和升级成本。如果您看到意外的调用量,请立即将函数保留并发设置为 0 来限制对函数的所有调用,同时更新代码。

Lambda 函数代码中不要使用非正式的非公有 API。对于 AWS Lambda 托管式运行时,Lambda 会定期为 Lambda 的内部 API 应用安全性和功能更新。这些内部 API 更新可能不能向后兼容,会导致意外后果,例如,假设您的函数依赖于这些非公有 API,则调用会失败。请参阅 API 参考以查看公开发布的 API 列表。

编写幂等代码。为您的函数编写幂等代码可确保以相同的方式处理重复事件。您的代码应该正确验证事件并优雅地处理重复事件。有关更多信息,请参阅如何使我的 Lambda 函数具有幂等性?。