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

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

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

本页介绍如何使用采用 Go 的 Lambda 函数处理程序,包括项目设置、命名约定和最佳实践。本页还包括 Go Lambda 函数的示例,该函数接收订单信息,生成文本文件收据,然后将此文件放入 Amazon Simple Storage Service(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

下面列出了有效的处理程序签名。TInTOut 表示类型与 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)

在处理程序中使用 AWS SDK for Go v2

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

注意

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

要向函数添加 SDK 依赖项,请使用适用于所需特定 SDK 客户端的 go get 命令。在上述示例代码中,我们使用了 config 库和 s3 库。在包含 go.modmain.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 函数具有幂等性?