

# 定义采用 Go 的 Lambda 函数处理程序
<a name="golang-handler"></a>

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

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

**Topics**
+ [设置 Go 处理程序项目](#golang-handler-setup)
+ [示例 Go Lambda 函数代码](#golang-example-code)
+ [处理程序命名约定](#golang-handler-naming)
+ [定义和访问输入事件对象](#golang-example-input)
+ [访问和使用 Lambda 上下文对象](#golang-example-context)
+ [Go 处理程序的有效处理程序签名](#golang-handler-signatures)
+ [在处理程序中使用 适用于 Go 的 AWS SDK v2](#golang-example-sdk-usage)
+ [评估环境变量](#golang-example-envvars)
+ [使用全局状态](#golang-handler-state)
+ [Go Lambda 函数的代码最佳实践](#go-best-practices)

## 设置 Go 处理程序项目
<a name="golang-handler-setup"></a>

在 [Go](https://golang.org/) 中编写的 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](https://github.com/aws/aws-lambda-go/tree/master/lambda) 包，该包将实现适用于 Go 的 Lambda 编程模型。使用以下 `go get` 命令安装此包：

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

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

## 示例 Go Lambda 函数代码
<a name="golang-example-code"></a>

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

**Example `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 在[初始化阶段](lambda-runtime-environment.md#runtimes-lifecycle-ib)运行的任何代码。
+ `func uploadReceiptToS3(...) {}`：这是主 `handleRequest` 处理程序方法引用的帮助程序方法。
+ `func handleRequest(ctx context.Context, event json.RawMessage) error {}`：这是包含主应用程序逻辑的**主处理程序方法**。
+ `func main() {}`：这是 Lambda 处理程序必需的入口点。`lambda.Start()` 方法的参数是主处理程序方法。

要此函数正常运行，其[执行角色](lambda-intro-execution-role.md)必须允许 `s3:PutObject` 操作。此外，请确保您定义了 `RECEIPT_BUCKET` 环境变量。成功调用后，Amazon S3 存储桶应包含接收文件。

## 处理程序命名约定
<a name="golang-handler-naming"></a>

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

对于使用 [.zip 部署包](golang-package.md)的 Go 函数，包含函数代码的可执行文件必须命名为 `bootstrap`。此外，`bootstrap` 文件必须位于 .zip 文件的根目录中。对于使用[容器映像](go-image.md#go-image-provided)的 Go 函数，可执行文件可以使用任何名称。

## 定义和访问输入事件对象
<a name="golang-example-input"></a>

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 标准库](https://pkg.go.dev/encoding/json)兼容的通用 JSON 类型。然后，您可以使用 [func Unmarshal](https://golang.org/pkg/encoding/json/#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 上下文对象
<a name="golang-example-context"></a>

Lambda [上下文对象](golang-context.md)包含有关调用、函数和执行环境的信息。在此例中，我们在处理程序签名中将此变量声明为 `ctx`：

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

`ctx context.Context` 输入是函数处理程序中的可选参数。有关所接受处理程序签名的更多信息，请参阅[Go 处理程序的有效处理程序签名](#golang-handler-signatures)。

如果您使用 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 函数信息](golang-context.md)。

## Go 处理程序的有效处理程序签名
<a name="golang-handler-signatures"></a>

在 Go 中构建 Lambda 函数处理程序时，您有多个选项，但您必须遵守以下规则：
+ 处理程序必须为函数。
+ 处理程序可能需要 0 到 2 个参数。如果有两个参数，则第一个参数必须实现 `context.Context`。
+ 处理程序可能返回 0 到 2 个参数。如果有一个返回值，则它必须实现 `error`。如果有两个返回值，则第二个值必须实现 `error`。

下面列出了有效的处理程序签名。`TIn` 和 `TOut` 表示类型与 *encoding/json* 标准库兼容。有关更多信息，请参阅 [func Unmarshal](https://golang.org/pkg/encoding/json/#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
<a name="golang-example-sdk-usage"></a>

通常，您将使用 Lambda 函数与其他 AWS 资源进行交互或对其进行更新。与此类资源最简单的交互方法是使用 [适用于 Go 的 AWS SDK v2](https://pkg.go.dev/github.com/aws/aws-sdk-go-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 时，请使用正确的设置配置客户端。最简单的方法是使用[默认的凭证提供程序链](https://docs.aws.amazon.com/sdkref/latest/guide/standardized-credentials.html#credentialProviderChain)。此示例说明了加载此配置的一种方法：

```
// 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 客户端初始化和调用中使用上下文](golang-context.md#golang-context-sdk) 中进行了介绍。

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

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

## 评估环境变量
<a name="golang-example-envvars"></a>

在处理程序代码中，您可以使用 `os.Getenv()` 方法引用任何[环境变量](configuration-envvars.md)。在此示例中，我们使用以下代码行引用已定义的 `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")
    }
```

## 使用全局状态
<a name="golang-handler-state"></a>

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

## Go Lambda 函数的代码最佳实践
<a name="go-best-practices"></a>

在构建 Lambda 函数时，请遵循以下列表中的指南，采用最佳编码实践：
+ **从核心逻辑中分离 Lambda 处理程序。**这样您可以创建更容易进行单元测试的函数。
+ **将依赖关系的复杂性降至最低。**首选在[执行环境](lambda-runtime-environment.md)启动时可以快速加载的更简单的框架。
+ **将部署包大小精简为只包含运行时必要的部分。**这样会减少调用前下载和解压缩部署程序包所需的时间。

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

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

**使用 keep-alive 指令来维护持久连接。**Lambda 会随着时间的推移清除空闲连接。在调用函数时尝试重用空闲连接会导致连接错误。要维护您的持久连接，请使用与运行时关联的 keep-alive 指令。有关示例，请参阅[在 Node.js 中通过 Keep-Alive 重用连接](https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/node-reusing-connections.html)。

**使用[环境变量](configuration-envvars.md)将操作参数传递给函数。**例如，您在写入 Amazon S3 存储桶时，不应对要写入的存储桶名称进行硬编码，而应将存储桶名称配置为环境变量。

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

Lambda 函数代码中**不要使用非正式的非公有 API**。对于 AWS Lambda 托管式运行时，Lambda 会定期为 Lambda 的内部 API 应用安全性和功能更新。这些内部 API 更新可能不能向后兼容，会导致意外后果，例如，假设您的函数依赖于这些非公有 API，则调用会失败。请参阅 [API 参考](https://docs.aws.amazon.com/lambda/latest/api/welcome.html)以查看公开发布的 API 列表。

**编写幂等代码。**为您的函数编写幂等代码可确保以相同的方式处理重复事件。您的代码应该正确验证事件并优雅地处理重复事件。有关更多信息，请参阅[如何使我的 Lambda 函数具有幂等性？](https://aws.amazon.com/premiumsupport/knowledge-center/lambda-function-idempotent/)。