

# 将 AWS Lambda 与 Amazon RDS 结合使用
<a name="services-rds"></a>

您可以将 Lambda 函数直接连接到 Amazon Relational Database Service（Amazon RDS），也可以通过 Amazon RDS 代理连接。直接连接适用于简单的场景，而在生产中则推荐使用代理。数据库代理管理共享数据库连接池，从而让函数能够在不耗尽数据库连接的情况下达到高并发级别。

对于频繁建立短数据库连接或打开和关闭大量数据库连接的 Lambda 函数，我们建议使用 Amazon RDS 代理。有关更多信息，请参阅《Amazon Relational Database Service 开发人员指南》中的[自动连接 Lambda 函数和数据库实例](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/lambda-rds-connect.html)。

**提示**  
要将 Lambda 函数快速连接到 Amazon RDS 数据库，您可以使用控制台内的引导式向导。要打开向导，执行以下操作：  
打开 Lamba 控制台的[函数](https://console.aws.amazon.com/lambda/home#/functions)页面。
选择要将数据库连接到其中的函数。
在**配置**选项卡上，选择 **RDS 数据库**。
选择**连接到 RDS 数据库**。
将函数连接到数据库后，您可以通过选择**添加代理**来创建代理。

## 配置函数以使用 RDS 资源
<a name="rds-configuration"></a>

在 Lambda 控制台中，您可以预置和配置 Amazon RDS 数据库实例和代理资源。您可以导航到**配置**选项卡下的 **RDS 数据库**完成此操作。或者，您也可以在 Amazon RDS 控制台中创建和配置到 Lambda 函数的连接。配置 RDS 数据库实例以便与 Lambda 配合使用时，请注意以下条件：
+ 要连接到数据库，您的函数必须位于数据库在其中运行的 Amazon VPC。
+ 您可以将 Amazon RDS 数据库与 MySQL、MariaDB、PostgreSQL 或 Microsoft SQL Server 引擎一起使用。
+ 您也可以将 Aurora 数据库集群与 MySQL 或 PostgreSQL 引擎一起使用。
+ 您需要提供用于数据库身份验证的 Secrets Manager 密钥。
+ IAM 角色必须提供使用密钥的权限和必须允许 Amazon RDS 代入角色的信任策略。
+  使用控制台配置 Amazon RDS 资源并将其连接到函数的 IAM 主体必须具有以下权限：

### 权限策略示例
<a name="rds-lambda-permissions"></a>

**注意**  
 只有在配置 Amazon RDS 代理来管理数据库连接池时，才需要 Amazon RDS 代理权限。

------
#### [ JSON ]

****  

```
{
  "Version":"2012-10-17",		 	 	 
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "ec2:CreateSecurityGroup",
        "ec2:DescribeSecurityGroups",
        "ec2:DescribeSubnets",
        "ec2:DescribeVpcs",
        "ec2:AuthorizeSecurityGroupIngress",
        "ec2:AuthorizeSecurityGroupEgress",
        "ec2:RevokeSecurityGroupEgress",
        "ec2:CreateNetworkInterface",
        "ec2:DeleteNetworkInterface",
        "ec2:DescribeNetworkInterfaces"
      ],
      "Resource": "*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "rds-db:connect",
        "rds:CreateDBProxy",
        "rds:CreateDBInstance",
        "rds:CreateDBSubnetGroup",
        "rds:DescribeDBClusters",
        "rds:DescribeDBInstances",
        "rds:DescribeDBSubnetGroups",
        "rds:DescribeDBProxies",
        "rds:DescribeDBProxyTargets",
        "rds:DescribeDBProxyTargetGroups",
        "rds:RegisterDBProxyTargets",
        "rds:ModifyDBInstance",
        "rds:ModifyDBProxy"
      ],
      "Resource": "*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "lambda:CreateFunction",
        "lambda:ListFunctions",
        "lambda:UpdateFunctionConfiguration"
      ],
      "Resource": "*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "iam:AttachRolePolicy",
        "iam:CreateRole",
        "iam:CreatePolicy"
      ],
      "Resource": "*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "secretsmanager:GetResourcePolicy",
        "secretsmanager:GetSecretValue",
        "secretsmanager:DescribeSecret",
        "secretsmanager:ListSecretVersionIds",
        "secretsmanager:CreateSecret"
      ],
      "Resource": "*"
    }
  ]
}
```

------

Amazon RDS 根据数据库实例大小按小时收取代理费率，详情请参阅 [RDS 代理定价](https://aws.amazon.com/rds/proxy/pricing/)。有关通用代理连接的更多信息，请参阅《Amazon RDS 用户指南》中的[使用 Amazon RDS 代理](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/rds-proxy.html)。

### Amazon RDS 连接的 SSL/TLS 要求
<a name="rds-lambda-certificates"></a>

要与 Amazon RDS 数据库实例建立安全的 SSL/TLS 连接，您的 Lambda 函数必须使用可信证书来验证数据库服务器的身份。Lambda 会根据您的部署包类型以不同的方式处理这些证书：
+ [.zip 文件存档](configuration-function-zip.md)：证书处理因运行时而异：
  + **Node.js 18 及更早版本**：Lambda 会自动包含 CA 证书和 RDS 证书。
  + **Node.js 20 及更高版本**：Lambda 不再默认加载其他 CA 证书。将 `NODE_EXTRA_CA_CERTS` 环境变量设置为 `/var/runtime/ca-cert.pem`。

  将新 AWS 区域的 Amazon RDS 证书添加到 Lambda 托管运行时可能需要长达 4 周的时间。
+ [容器映像](images-create.md)：AWS 基础映像仅包含 CA 证书。如果函数连接到 Amazon RDS 数据库实例，则必须在容器映像中包含相应的证书。在您的 Dockerfile 中，下载[与托管数据库的 AWS 区域相对应的证书捆绑包](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/UsingWithRDS.SSL.html#UsingWithRDS.SSL.CertificatesDownload)。示例：

  ```
  RUN curl https://truststore.pki.rds.amazonaws.com/us-east-1/us-east-1-bundle.pem -o /us-east-1-bundle.pem
  ```

此命令可下载 Amazon RDS 证书捆绑包并将其保存在容器根目录的绝对路径 `/us-east-1-bundle.pem` 下。在函数代码中配置数据库连接时，必须引用此确切路径。示例：

------
#### [ Node.js ]

`readFileSync` 函数是必需的，因为 Node.js 数据库客户端需要内存中的实际证书内容，而不仅仅是证书文件的路径。没有 `readFileSync`，客户端会将路径字符串解释为证书内容，从而导致“证书链中的自签名证书”错误。

**Example OCI 函数的 Node.js 连接配置**  

```
import { readFileSync } from 'fs';

// ...

let connectionConfig = {
    host: process.env.ProxyHostName,
    user: process.env.DBUserName,
    password: token,
    database: process.env.DBName,
    ssl: {
        ca: readFileSync('/us-east-1-bundle.pem') // Load RDS certificate content from file into memory
    }
};
```

------
#### [ Python ]

**Example OCI 函数的 Python 连接配置**  

```
connection = pymysql.connect(
    host=proxy_host_name,
    user=db_username,
    password=token,
    db=db_name,
    port=port,
    ssl={'ca': '/us-east-1-bundle.pem'}  #Path to the certificate in container
)
```

------
#### [ Java ]

对于使用 JDBC 连接的 Java 函数，连接字符串必须包含：
+ `useSSL=true`
+ `requireSSL=true`
+ 指向容器映像中 Amazon RDS 证书位置的 `sslCA` 参数

**Example OCI 函数的 Java 连接字符串**  

```
// Define connection string
String connectionString = String.format("jdbc:mysql://%s:%s/%s?useSSL=true&requireSSL=true&sslCA=/us-east-1-bundle.pem", // Path to the certificate in container
        System.getenv("ProxyHostName"),
        System.getenv("Port"),
        System.getenv("DBName"));
```

------
#### [ .NET ]

**Example OCI 函数中用于 MySQL 连接的 .NET 连接字符串**  

```
/// Build the Connection String with the Token 
string connectionString = $"Server={Environment.GetEnvironmentVariable("RDS_ENDPOINT")};" +
                         $"Port={Environment.GetEnvironmentVariable("RDS_PORT")};" +
                         $"Uid={Environment.GetEnvironmentVariable("RDS_USERNAME")};" +
                         $"Pwd={authToken};" +
                         "SslMode=Required;" +
                         "SslCa=/us-east-1-bundle.pem";  // Path to the certificate in container
```

------
#### [ Go ]

对于使用 MySQL 连接的 Go 函数，请将 Amazon RDS 证书加载到证书池中，并将其注册到 MySQL 驱动程序。然后，连接字符串必须使用 `tls` 参数来引用此配置。

**Example OCI 函数中用于 MySQL 连接的 Go 代码**  

```
import (
    "crypto/tls"
    "crypto/x509"
    "os"
    "github.com/go-sql-driver/mysql"
)

...

// Create certificate pool and register TLS config
rootCertPool := x509.NewCertPool()
pem, err := os.ReadFile("/us-east-1-bundle.pem")  // Path to the certificate in container
if err != nil {
    panic("failed to read certificate file: " + err.Error())
}
if ok := rootCertPool.AppendCertsFromPEM(pem); !ok {
    panic("failed to append PEM")
}

mysql.RegisterTLSConfig("custom", &tls.Config{
    RootCAs: rootCertPool,
})

dsn := fmt.Sprintf("%s:%s@tcp(%s)/%s?allowCleartextPasswords=true&tls=custom",
    dbUser, authenticationToken, dbEndpoint, dbName,
)
```

------
#### [ Ruby ]

**Example OCI 函数的 Ruby 连接配置**  

```
conn = Mysql2::Client.new(
    host: endpoint,
    username: user,
    password: token,
    port: port,
    database: db_name,
    sslca: '/us-east-1-bundle.pem',  # Path to the certificate in container
    sslverify: true
)
```

------

## 使用 Lambda 函数连接到 Amazon RDS 数据库
<a name="rds-connection"></a>

以下代码示例显示了如何实现连接到 Amazon RDS 数据库的 Lambda 函数。该函数发出一个简单的数据库请求并返回结果。

**注意**  
这些代码示例仅适用于 [.zip 部署包](configuration-function-zip.md)。如果您使用[容器映像](images-create.md)来部署函数，则必须在函数代码中指定 Amazon RDS 证书文件，如[上一节](#oci-certificate)所述。

------
#### [ .NET ]

**适用于 .NET 的 SDK**  
 查看 GitHub，了解更多信息。在[无服务器示例](https://github.com/aws-samples/serverless-snippets/tree/main/lambda-function-connect-rds-iam)存储库中查找完整示例，并了解如何进行设置和运行。
在 Lambda 函数中使用 .NET 连接到 Amazon RDS 数据库。  

```
using System.Data;
using System.Text.Json;
using Amazon.Lambda.APIGatewayEvents;
using Amazon.Lambda.Core;
using MySql.Data.MySqlClient;

// Assembly attribute to enable the Lambda function's JSON input to be converted into a .NET class.
[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))]

namespace aws_rds;

public class InputModel
{
    public string key1 { get; set; }
    public string key2 { get; set; }
}

public class Function
{
    /// <summary>
    // Handles the Lambda function execution for connecting to RDS using IAM authentication.
    /// </summary>
    /// <param name="input">The input event data passed to the Lambda function</param>
    /// <param name="context">The Lambda execution context that provides runtime information</param>
    /// <returns>A response object containing the execution result</returns>

    public async Task<APIGatewayProxyResponse> FunctionHandler(APIGatewayProxyRequest request, ILambdaContext context)
    {
        // Sample Input: {"body": "{\"key1\":\"20\", \"key2\":\"25\"}"}
        var input = JsonSerializer.Deserialize<InputModel>(request.Body);

        /// Obtain authentication token
        var authToken = RDSAuthTokenGenerator.GenerateAuthToken(
            Environment.GetEnvironmentVariable("RDS_ENDPOINT"),
            Convert.ToInt32(Environment.GetEnvironmentVariable("RDS_PORT")),
            Environment.GetEnvironmentVariable("RDS_USERNAME")
        );

        /// Build the Connection String with the Token 
        string connectionString = $"Server={Environment.GetEnvironmentVariable("RDS_ENDPOINT")};" +
                                  $"Port={Environment.GetEnvironmentVariable("RDS_PORT")};" +
                                  $"Uid={Environment.GetEnvironmentVariable("RDS_USERNAME")};" +
                                  $"Pwd={authToken};";


        try
        {
            await using var connection = new MySqlConnection(connectionString);
            await connection.OpenAsync();

            const string sql = "SELECT @param1 + @param2 AS Sum";

            await using var command = new MySqlCommand(sql, connection);
            command.Parameters.AddWithValue("@param1", int.Parse(input.key1 ?? "0"));
            command.Parameters.AddWithValue("@param2", int.Parse(input.key2 ?? "0"));

            await using var reader = await command.ExecuteReaderAsync();
            if (await reader.ReadAsync())
            {
                int result = reader.GetInt32("Sum");

                //Sample Response: {"statusCode":200,"body":"{\"message\":\"The sum is: 45\"}","isBase64Encoded":false}
                return new APIGatewayProxyResponse
                {
                    StatusCode = 200,
                    Body = JsonSerializer.Serialize(new { message = $"The sum is: {result}" })
                };
            }

        }
        catch (Exception ex)
        {
            Console.WriteLine($"Error: {ex.Message}");
        }

        return new APIGatewayProxyResponse
        {
            StatusCode = 500,
            Body = JsonSerializer.Serialize(new { error = "Internal server error" })
        };
    }
}
```

------
#### [ Go ]

**适用于 Go 的 SDK V2**  
 查看 GitHub，了解更多信息。在[无服务器示例](https://github.com/aws-samples/serverless-snippets/tree/main/lambda-function-connect-rds-iam)存储库中查找完整示例，并了解如何进行设置和运行。
在 Lambda 函数中使用 Go 连接到 Amazon RDS 数据库。  

```
/*
Golang v2 code here.
*/

package main

import (
	"context"
	"database/sql"
	"encoding/json"
	"fmt"
	"os"

	"github.com/aws/aws-lambda-go/lambda"
	"github.com/aws/aws-sdk-go-v2/config"
	"github.com/aws/aws-sdk-go-v2/feature/rds/auth"
	_ "github.com/go-sql-driver/mysql"
)

type MyEvent struct {
	Name string `json:"name"`
}

func HandleRequest(event *MyEvent) (map[string]interface{}, error) {

	var dbName string = os.Getenv("DatabaseName")
	var dbUser string = os.Getenv("DatabaseUser")
	var dbHost string = os.Getenv("DBHost") // Add hostname without https
	var dbPort int = os.Getenv("Port")      // Add port number
	var dbEndpoint string = fmt.Sprintf("%s:%d", dbHost, dbPort)
	var region string = os.Getenv("AWS_REGION")

	cfg, err := config.LoadDefaultConfig(context.TODO())
	if err != nil {
		panic("configuration error: " + err.Error())
	}

	authenticationToken, err := auth.BuildAuthToken(
		context.TODO(), dbEndpoint, region, dbUser, cfg.Credentials)
	if err != nil {
		panic("failed to create authentication token: " + err.Error())
	}

	dsn := fmt.Sprintf("%s:%s@tcp(%s)/%s?tls=true&allowCleartextPasswords=true",
		dbUser, authenticationToken, dbEndpoint, dbName,
	)

	db, err := sql.Open("mysql", dsn)
	if err != nil {
		panic(err)
	}

	defer db.Close()

	var sum int
	err = db.QueryRow("SELECT ?+? AS sum", 3, 2).Scan(&sum)
	if err != nil {
		panic(err)
	}
	s := fmt.Sprint(sum)
	message := fmt.Sprintf("The selected sum is: %s", s)

	messageBytes, err := json.Marshal(message)
	if err != nil {
		return nil, err
	}

	messageString := string(messageBytes)
	return map[string]interface{}{
		"statusCode": 200,
		"headers":    map[string]string{"Content-Type": "application/json"},
		"body":       messageString,
	}, nil
}

func main() {
	lambda.Start(HandleRequest)
}
```

------
#### [ Java ]

**适用于 Java 的 SDK 2.x**  
 查看 GitHub，了解更多信息。在[无服务器示例](https://github.com/aws-samples/serverless-snippets/tree/main/lambda-function-connect-rds-iam)存储库中查找完整示例，并了解如何进行设置和运行。
在 Lambda 函数中使用 Java 连接到 Amazon RDS 数据库。  

```
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent;
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent;
import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.rdsdata.RdsDataClient;
import software.amazon.awssdk.services.rdsdata.model.ExecuteStatementRequest;
import software.amazon.awssdk.services.rdsdata.model.ExecuteStatementResponse;
import software.amazon.awssdk.services.rdsdata.model.Field;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;

public class RdsLambdaHandler implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> {

    @Override
    public APIGatewayProxyResponseEvent handleRequest(APIGatewayProxyRequestEvent event, Context context) {
        APIGatewayProxyResponseEvent response = new APIGatewayProxyResponseEvent();

        try {
            // Obtain auth token
            String token = createAuthToken();

            // Define connection configuration
            String connectionString = String.format("jdbc:mysql://%s:%s/%s?useSSL=true&requireSSL=true",
                    System.getenv("ProxyHostName"),
                    System.getenv("Port"),
                    System.getenv("DBName"));

            // Establish a connection to the database
            try (Connection connection = DriverManager.getConnection(connectionString, System.getenv("DBUserName"), token);
                 PreparedStatement statement = connection.prepareStatement("SELECT ? + ? AS sum")) {

                statement.setInt(1, 3);
                statement.setInt(2, 2);

                try (ResultSet resultSet = statement.executeQuery()) {
                    if (resultSet.next()) {
                        int sum = resultSet.getInt("sum");
                        response.setStatusCode(200);
                        response.setBody("The selected sum is: " + sum);
                    }
                }
            }

        } catch (Exception e) {
            response.setStatusCode(500);
            response.setBody("Error: " + e.getMessage());
        }

        return response;
    }

    private String createAuthToken() {
        // Create RDS Data Service client
        RdsDataClient rdsDataClient = RdsDataClient.builder()
                .region(Region.of(System.getenv("AWS_REGION")))
                .credentialsProvider(DefaultCredentialsProvider.create())
                .build();

        // Define authentication request
        ExecuteStatementRequest request = ExecuteStatementRequest.builder()
                .resourceArn(System.getenv("ProxyHostName"))
                .secretArn(System.getenv("DBUserName"))
                .database(System.getenv("DBName"))
                .sql("SELECT 'RDS IAM Authentication'")
                .build();

        // Execute request and obtain authentication token
        ExecuteStatementResponse response = rdsDataClient.executeStatement(request);
        Field tokenField = response.records().get(0).get(0);

        return tokenField.stringValue();
    }
}
```

------
#### [ JavaScript ]

**SDK for JavaScript（v3）**  
 查看 GitHub，了解更多信息。在[无服务器示例](https://github.com/aws-samples/serverless-snippets/tree/main/lambda-function-connect-rds-iam)存储库中查找完整示例，并了解如何进行设置和运行。
在 Lambda 函数中使用 JavaScript 连接到 Amazon RDS 数据库。  

```
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
/* 
Node.js code here.
*/
// ES6+ example
import { Signer } from "@aws-sdk/rds-signer";
import mysql from 'mysql2/promise';

async function createAuthToken() {
  // Define connection authentication parameters
  const dbinfo = {

    hostname: process.env.ProxyHostName,
    port: process.env.Port,
    username: process.env.DBUserName,
    region: process.env.AWS_REGION,

  }

  // Create RDS Signer object
  const signer = new Signer(dbinfo);

  // Request authorization token from RDS, specifying the username
  const token = await signer.getAuthToken();
  return token;
}

async function dbOps() {

  // Obtain auth token
  const token = await createAuthToken();
  // Define connection configuration
  let connectionConfig = {
    host: process.env.ProxyHostName,
    user: process.env.DBUserName,
    password: token,
    database: process.env.DBName,
    ssl: 'Amazon RDS'
  }
  // Create the connection to the DB
  const conn = await mysql.createConnection(connectionConfig);
  // Obtain the result of the query
  const [res,] = await conn.execute('select ?+? as sum', [3, 2]);
  return res;

}

export const handler = async (event) => {
  // Execute database flow
  const result = await dbOps();
  // Return result
  return {
    statusCode: 200,
    body: JSON.stringify("The selected sum is: " + result[0].sum)
  }
};
```
在 Lambda 函数中使用 TypeScript 连接到 Amazon RDS 数据库。  

```
import { Signer } from "@aws-sdk/rds-signer";
import mysql from 'mysql2/promise';

// RDS settings
// Using '!' (non-null assertion operator) to tell the TypeScript compiler that the DB settings are not null or undefined,
const proxy_host_name = process.env.PROXY_HOST_NAME!
const port = parseInt(process.env.PORT!)
const db_name = process.env.DB_NAME!
const db_user_name = process.env.DB_USER_NAME!
const aws_region = process.env.AWS_REGION!


async function createAuthToken(): Promise<string> {

    // Create RDS Signer object
    const signer = new Signer({
        hostname: proxy_host_name,
        port: port,
        region: aws_region,
        username: db_user_name
    });

    // Request authorization token from RDS, specifying the username
    const token = await signer.getAuthToken();
    return token;
}

async function dbOps(): Promise<mysql.QueryResult | undefined> {
    try {
        // Obtain auth token
        const token = await createAuthToken();
        const conn = await mysql.createConnection({
            host: proxy_host_name,
            user: db_user_name,
            password: token,
            database: db_name,
            ssl: 'Amazon RDS' // Ensure you have the CA bundle for SSL connection
        });
        const [rows, fields] = await conn.execute('SELECT ? + ? AS sum', [3, 2]);
        console.log('result:', rows);
        return rows;
    }
    catch (err) {
        console.log(err);
    }
}

export const lambdaHandler = async (event: any): Promise<{ statusCode: number; body: string }> => {
    // Execute database flow
    const result = await dbOps();

    // Return error is result is undefined
    if (result == undefined)
        return {
            statusCode: 500,
            body: JSON.stringify(`Error with connection to DB host`)
        }

    // Return result
    return {
        statusCode: 200,
        body: JSON.stringify(`The selected sum is: ${result[0].sum}`)
    };
};
```

------
#### [ PHP ]

**适用于 PHP 的 SDK**  
 查看 GitHub，了解更多信息。在[无服务器示例](https://github.com/aws-samples/serverless-snippets/tree/main/lambda-function-connect-rds-iam)存储库中查找完整示例，并了解如何进行设置和运行。
在 Lambda 函数中使用 PHP 连接到 Amazon RDS 数据库。  

```
<?php
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0

# using bref/bref and bref/logger for simplicity

use Bref\Context\Context;
use Bref\Event\Handler as StdHandler;
use Bref\Logger\StderrLogger;
use Aws\Rds\AuthTokenGenerator;
use Aws\Credentials\CredentialProvider;

require __DIR__ . '/vendor/autoload.php';

class Handler implements StdHandler
{
    private StderrLogger $logger;
    public function __construct(StderrLogger $logger)
    {
        $this->logger = $logger;
    }


    private function getAuthToken(): string {
        // Define connection authentication parameters
        $dbConnection = [
            'hostname' => getenv('DB_HOSTNAME'),
            'port' => getenv('DB_PORT'),
            'username' => getenv('DB_USERNAME'),
            'region' => getenv('AWS_REGION'),
        ];

        // Create RDS AuthTokenGenerator object
        $generator = new AuthTokenGenerator(CredentialProvider::defaultProvider());

        // Request authorization token from RDS, specifying the username
        return $generator->createToken(
            $dbConnection['hostname'] . ':' . $dbConnection['port'],
            $dbConnection['region'],
            $dbConnection['username']
        );
    }

    private function getQueryResults() {
        // Obtain auth token
        $token = $this->getAuthToken();

        // Define connection configuration
        $connectionConfig = [
            'host' => getenv('DB_HOSTNAME'),
            'user' => getenv('DB_USERNAME'),
            'password' => $token,
            'database' => getenv('DB_NAME'),
        ];

        // Create the connection to the DB
        $conn = new PDO(
            "mysql:host={$connectionConfig['host']};dbname={$connectionConfig['database']}",
            $connectionConfig['user'],
            $connectionConfig['password'],
            [
                PDO::MYSQL_ATTR_SSL_CA => '/path/to/rds-ca-2019-root.pem',
                PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT => true,
            ]
        );

        // Obtain the result of the query
        $stmt = $conn->prepare('SELECT ?+? AS sum');
        $stmt->execute([3, 2]);

        return $stmt->fetch(PDO::FETCH_ASSOC);
    }

    /**
     * @param mixed $event
     * @param Context $context
     * @return array
     */
    public function handle(mixed $event, Context $context): array
    {
        $this->logger->info("Processing query");

        // Execute database flow
        $result = $this->getQueryResults();

        return [
            'sum' => $result['sum']
        ];
    }
}

$logger = new StderrLogger();
return new Handler($logger);
```

------
#### [ Python ]

**适用于 Python 的 SDK（Boto3）**  
 查看 GitHub，了解更多信息。在[无服务器示例](https://github.com/aws-samples/serverless-snippets/tree/main/lambda-function-connect-rds-iam)存储库中查找完整示例，并了解如何进行设置和运行。
在 Lambda 函数中使用 Python 连接到 Amazon RDS 数据库。  

```
import json
import os
import boto3
import pymysql

# RDS settings
proxy_host_name = os.environ['PROXY_HOST_NAME']
port = int(os.environ['PORT'])
db_name = os.environ['DB_NAME']
db_user_name = os.environ['DB_USER_NAME']
aws_region = os.environ['AWS_REGION']


# Fetch RDS Auth Token
def get_auth_token():
    client = boto3.client('rds')
    token = client.generate_db_auth_token(
        DBHostname=proxy_host_name,
        Port=port
        DBUsername=db_user_name
        Region=aws_region
    )
    return token

def lambda_handler(event, context):
    token = get_auth_token()
    try:
        connection = pymysql.connect(
            host=proxy_host_name,
            user=db_user_name,
            password=token,
            db=db_name,
            port=port,
            ssl={'ca': 'Amazon RDS'}  # Ensure you have the CA bundle for SSL connection
        )
        
        with connection.cursor() as cursor:
            cursor.execute('SELECT %s + %s AS sum', (3, 2))
            result = cursor.fetchone()

        return result
        
    except Exception as e:
        return (f"Error: {str(e)}")  # Return an error message if an exception occurs
```

------
#### [ Ruby ]

**适用于 Ruby 的 SDK**  
 查看 GitHub，了解更多信息。在[无服务器示例](https://github.com/aws-samples/serverless-snippets/tree/main/lambda-function-connect-rds-iam)存储库中查找完整示例，并了解如何进行设置和运行。
在 Lambda 函数中使用 Ruby 连接到 Amazon RDS 数据库。  

```
# Ruby code here.

require 'aws-sdk-rds'
require 'json'
require 'mysql2'

def lambda_handler(event:, context:)
  endpoint = ENV['DBEndpoint'] # Add the endpoint without https"
  port = ENV['Port']           # 3306
  user = ENV['DBUser']
  region = ENV['DBRegion']     # 'us-east-1'
  db_name = ENV['DBName']

  credentials = Aws::Credentials.new(
    ENV['AWS_ACCESS_KEY_ID'],
    ENV['AWS_SECRET_ACCESS_KEY'],
    ENV['AWS_SESSION_TOKEN']
  )
  rds_client = Aws::RDS::AuthTokenGenerator.new(
    region: region, 
    credentials: credentials
  )

  token = rds_client.auth_token(
    endpoint: endpoint+ ':' + port,
    user_name: user,
    region: region
  )

  begin
    conn = Mysql2::Client.new(
      host: endpoint,
      username: user,
      password: token,
      port: port,
      database: db_name,
      sslca: '/var/task/global-bundle.pem', 
      sslverify: true,
      enable_cleartext_plugin: true
    )
    a = 3
    b = 2
    result = conn.query("SELECT #{a} + #{b} AS sum").first['sum']
    puts result
    conn.close
    {
      statusCode: 200,
      body: result.to_json
    }
  rescue => e
    puts "Database connection failed due to #{e}"
  end
end
```

------
#### [ Rust ]

**适用于 Rust 的 SDK**  
 查看 GitHub，了解更多信息。在[无服务器示例](https://github.com/aws-samples/serverless-snippets/tree/main/lambda-function-connect-rds-iam)存储库中查找完整示例，并了解如何进行设置和运行。
在 Lambda 函数中使用 Rust 连接到 Amazon RDS 数据库。  

```
use aws_config::BehaviorVersion;
use aws_credential_types::provider::ProvideCredentials;
use aws_sigv4::{
    http_request::{sign, SignableBody, SignableRequest, SigningSettings},
    sign::v4,
};
use lambda_runtime::{run, service_fn, Error, LambdaEvent};
use serde_json::{json, Value};
use sqlx::postgres::PgConnectOptions;
use std::env;
use std::time::{Duration, SystemTime};

const RDS_CERTS: &[u8] = include_bytes!("global-bundle.pem");

async fn generate_rds_iam_token(
    db_hostname: &str,
    port: u16,
    db_username: &str,
) -> Result<String, Error> {
    let config = aws_config::load_defaults(BehaviorVersion::v2024_03_28()).await;

    let credentials = config
        .credentials_provider()
        .expect("no credentials provider found")
        .provide_credentials()
        .await
        .expect("unable to load credentials");
    let identity = credentials.into();
    let region = config.region().unwrap().to_string();

    let mut signing_settings = SigningSettings::default();
    signing_settings.expires_in = Some(Duration::from_secs(900));
    signing_settings.signature_location = aws_sigv4::http_request::SignatureLocation::QueryParams;

    let signing_params = v4::SigningParams::builder()
        .identity(&identity)
        .region(&region)
        .name("rds-db")
        .time(SystemTime::now())
        .settings(signing_settings)
        .build()?;

    let url = format!(
        "https://{db_hostname}:{port}/?Action=connect&DBUser={db_user}",
        db_hostname = db_hostname,
        port = port,
        db_user = db_username
    );

    let signable_request =
        SignableRequest::new("GET", &url, std::iter::empty(), SignableBody::Bytes(&[]))
            .expect("signable request");

    let (signing_instructions, _signature) =
        sign(signable_request, &signing_params.into())?.into_parts();

    let mut url = url::Url::parse(&url).unwrap();
    for (name, value) in signing_instructions.params() {
        url.query_pairs_mut().append_pair(name, &value);
    }

    let response = url.to_string().split_off("https://".len());

    Ok(response)
}

#[tokio::main]
async fn main() -> Result<(), Error> {
    run(service_fn(handler)).await
}

async fn handler(_event: LambdaEvent<Value>) -> Result<Value, Error> {
    let db_host = env::var("DB_HOSTNAME").expect("DB_HOSTNAME must be set");
    let db_port = env::var("DB_PORT")
        .expect("DB_PORT must be set")
        .parse::<u16>()
        .expect("PORT must be a valid number");
    let db_name = env::var("DB_NAME").expect("DB_NAME must be set");
    let db_user_name = env::var("DB_USERNAME").expect("DB_USERNAME must be set");

    let token = generate_rds_iam_token(&db_host, db_port, &db_user_name).await?;

    let opts = PgConnectOptions::new()
        .host(&db_host)
        .port(db_port)
        .username(&db_user_name)
        .password(&token)
        .database(&db_name)
        .ssl_root_cert_from_pem(RDS_CERTS.to_vec())
        .ssl_mode(sqlx::postgres::PgSslMode::Require);

    let pool = sqlx::postgres::PgPoolOptions::new()
        .connect_with(opts)
        .await?;

    let result: i32 = sqlx::query_scalar("SELECT $1 + $2")
        .bind(3)
        .bind(2)
        .fetch_one(&pool)
        .await?;

    println!("Result: {:?}", result);

    Ok(json!({
        "statusCode": 200,
        "content-type": "text/plain",
        "body": format!("The selected sum is: {result}")
    }))
}
```

------

## 处理来自 Amazon RDS 的事件通知
<a name="rds-events"></a>

您可以使用 Lambda 处理 Amazon RDS 数据库中的事件通知。Amazon RDS 将通知发送到 Amazon Simple Notification Service (Amazon SNS) 主题，您可以将其配置为调用 Lambda 函数。Amazon SNS 将来自 Amazon RDS 的消息封装在其自己的事件文档中，并将其发送到您的函数。

有关配置 Amazon RDS 数据库以发送通知的详细信息，请参阅[使用 Amazon RDS 事件通知](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_Events.html)。

**Example Amazon SNS 事件中的 Amazon RDS 消息**  

```
{
        "Records": [
          {
            "EventVersion": "1.0",
            "EventSubscriptionArn": "arn:aws:sns:us-east-2:123456789012:rds-lambda:21be56ed-a058-49f5-8c98-aedd2564c486",
            "EventSource": "aws:sns",
            "Sns": {
              "SignatureVersion": "1",
              "Timestamp": "2023-01-02T12:45:07.000Z",
              "Signature": "tcc6faL2yUC6dgZdmrwh1Y4cGa/ebXEkAi6RibDsvpi+tE/1+82j...65r==",
              "SigningCertUrl": "https://sns.us-east-2.amazonaws.com/SimpleNotificationService-ac565b8b1a6c5d002d285f9598aa1d9b.pem",
              "MessageId": "95df01b4-ee98-5cb9-9903-4c221d41eb5e",
              "Message": "{\"Event Source\":\"db-instance\",\"Event Time\":\"2023-01-02 12:45:06.000\",\"Identifier Link\":\"https://console.aws.amazon.com/rds/home?region=eu-west-1#dbinstance:id=dbinstanceid\",\"Source ID\":\"dbinstanceid\",\"Event ID\":\"http://docs.amazonwebservices.com/AmazonRDS/latest/UserGuide/USER_Events.html#RDS-EVENT-0002\",\"Event Message\":\"Finished DB Instance backup\"}",
              "MessageAttributes": {},
              "Type": "Notification",
              "UnsubscribeUrl": "https://sns.us-east-2.amazonaws.com/?Action=Unsubscribe&amp;SubscriptionArn=arn:aws:sns:us-east-2:123456789012:test-lambda:21be56ed-a058-49f5-8c98-aedd2564c486",
              "TopicArn":"arn:aws:sns:us-east-2:123456789012:sns-lambda",
              "Subject": "RDS Notification Message"
            }
          }
        ]
      }
```

## Lambda 和 Amazon RDS 完整教程
<a name="rds-database-samples"></a>
+ [使用 Lambda 函数访问 Amazon RDS 数据库](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/rds-lambda-tutorial.html)：在《Amazon RDS 用户指南》中，了解如何使用 Lambda 函数通过 Amazon RDS 代理将数据写入 Amazon RDS 数据库。Lambda 函数将从 Amazon SQS 队列中读取记录，每当添加消息时，都将新的项目写入数据库的表中。

# 为基于 Lambda 的应用程序选择数据库服务
<a name="ddb-rds-database-decision"></a>

许多无服务器应用程序需要存储和检索数据。AWS 提供了多个与 Lambda 函数一起使用的数据库选项。最受欢迎的两个选项是 Amazon DynamoDB（NoSQL 数据库服务）和 Amazon RDS（传统关系数据库解决方案）。以下几节将说明这些服务在与 Lambda 一起使用时的主要区别，并帮助您为无服务器应用程序选择合适的数据库服务。

要详细了解由 AWS 提供的其他数据库服务，并更广泛地了解其用例和权衡，请参阅 [Choosing an AWS database service](https://docs.aws.amazon.com/decision-guides/latest/databases-on-aws-how-to-choose/databases-on-aws-how-to-choose.html)。所有 AWS 数据库服务都与 Lambda 兼容，但并非所有服务都适合您的特定用例。

## 选择使用 Lambda 的数据库服务时，您有哪些选择？
<a name="w2aad101d101c19b9"></a>

AWS 提供多种数据库服务。对于无服务器应用程序，最受欢迎的两个选择是 DynamoDB 和 Amazon RDS。
+ **DynamoDB** 是一项完全托管的 NoSQL 数据库服务，其针对无服务器应用程序进行了优化。该服务可在任何规模下提供无缝的扩展和一致的个位数毫秒级性能。
+ **Amazon RDS** 是一项托管关系数据库服务，其支持多个数据库引擎，包括 MySQL 和 PostgreSQL。该服务通过托管基础架构提供熟悉的 SQL 功能。

## 在已经知道自己要求的情况下的建议
<a name="w2aad101d101c19c11"></a>

如果您已经明确自己的要求，则以下是我们的基本建议：

对于需要一致的低延迟性能、自动扩展且不需要复杂联接或事务的无服务器应用程序，建议使用 [DynamoDB](with-ddb.md)。由于其无服务器性质，该服务特别适合基于 Lambda 的应用程序。

当您需要复杂的 SQL 查询、联接或拥有使用关系数据库的现有应用程序时，[Amazon RDS](services-rds.md) 是更好的选择。但请注意，将 Lambda 函数连接到 Amazon RDS 需要额外的配置，并且可能会影响冷启动时间。

## 选择数据库服务时需要考虑的因素
<a name="w2aad101d101c19c13"></a>

在为您的 Lambda 应用程序选择 DynamoDB 或 Amazon RDS 时，请考虑以下因素：
+ 连接管理和冷启动
+ 数据访问模式
+ 查询的复杂性
+ 数据一致性要求
+ 扩展特征
+ 成本模型

通过了解这些因素，您可以选择能满足自己特定用例需求的最佳选项。

### 连接管理和冷启动
<a name="w2aad101d101c19c13b9b1"></a>
+ DynamoDB 对所有操作都使用 HTTP API。Lambda 函数可以在不维护连接的情况下立即发出请求，从而提高冷启动性能。每个请求都使用 AWS 凭证进行身份验证，而不会产生连接开销。
+ Amazon RDS 需要管理连接池，因为其使用传统数据库连接。这可能会影响冷启动，因为新的 Lambda 实例需要建立连接。您需要实施连接池策略，并有可能使用 [Amazon RDS 代理](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/rds-proxy.html)来有效地管理连接。请注意，使用 Amazon RDS 代理会产生额外的费用。

### 数据访问模式
<a name="w2aad101d101c19c13b9b3"></a>
+ DynamoDB 最适合已知的访问模式和单表设计。其非常适合需要基于主键或二级索引对数据进行一致低延迟访问的 Lambda 应用程序。
+ Amazon RDS 为复杂的查询和不断变化的访问模式提供了灵活性。当您的 Lambda 函数需要在多个表之间执行独特、量身定制的查询或复杂联接时，该服务更适合。

### 查询的复杂性
<a name="w2aad101d101c19c13b9b5"></a>
+ DynamoDB 擅长简单、基于键的操作和预定义的访问模式。复杂的查询必须围绕索引结构进行设计，且联接必须在应用程序代码中进行处理。
+ Amazon RDS 支持带有联接、子查询和聚合的复杂 SQL 查询。当需要复杂的数据操作时，这可以简化您的 Lambda 函数代码。

### 数据一致性要求
<a name="w2aad101d101c19c13b9b7"></a>
+ DynamoDB 提供最终一致性和强一致性选项，单项读取可使用强一致性。其支持事务，但有一些限制。
+ Amazon RDS 提供完整的原子性、一致性、隔离性与持久性（ACID）合规以及复杂的事务支持。如果您的 Lambda 函数需要复杂的事务或多条记录之间的强一致性，则 Amazon RDS 可能更合适。

### 扩展特征
<a name="w2aad101d101c19c13b9b9"></a>
+ DynamoDB 会根据您的工作负载自动扩展。其无需预置即可处理来自 Lambda 函数的流量突然猛增。您可以使用按需容量模式，以仅为使用的容量付费，该模式与 Lambda 的扩展模型完美匹配。
+ 根据您选择的实例大小，Amazon RDS 具有固定容量。如果多个 Lambda 函数尝试同时连接，则可能会超出连接配额。您需要仔细管理连接池，并有可能实施重试逻辑。

### 成本模型
<a name="w2aad101d101c19c13b9c11"></a>
+ DynamoDB 的定价与无服务器应用程序非常吻合。使用按需容量，您只需为 Lambda 函数执行的实际读取和写入付费。空闲时间不收取任何费用。
+ 无论使用情况如何，Amazon RDS 都会对正在运行的实例收费。对于无服务器应用程序中典型的零星工作负载，此服务的成本效益可能较低。但对于持续使用的高吞吐量工作负载来说，此服务可能更经济。

## 开始使用您所选的数据库服务
<a name="w2aad101d101c19c15"></a>

现在，您已经了解了在 DynamoDB 与 Amazon RDS 之间进行选择的标准以及两者之间的主要区别，您可以选择最符合自己需求的选项，并通过以下资源开始使用。

------
#### [ DynamoDB ]

**通过以下资源开始使用 DynamoDB**
+ 有关 DynamoDB 服务的简介，请阅读《Amazon DynamoDB 开发人员指南》**中的[什么是 DynamoDB？](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Introduction.html)。
+ 按照教程：[利用 API Gateway 使用 Lambda](services-apigateway-tutorial.md) 进行操作，查看使用 Lambda 函数响应 API 请求对 DynamoDB 表执行 CRUD 操作的示例。
+ 阅读《Amazon DynamoDB 开发人员指南》**中的[使用 DynamoDB 和 AWS SDK 编程](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Programming.html)，了解如何使用其中一个 AWS SDK 从 Lambda 函数内访问 DynamoDB 的更多信息。

------
#### [ Amazon RDS ]

**通过以下资源开始使用 Amazon RDS**
+ 有关 Amazon RDS 服务的简介，请阅读《Amazon Relational Database Service 用户指南》**中的[什么是 Amazon Relational Database Service (Amazon RDS)？](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Welcome.html)。
+ 按照《Amazon Relational Database Service 用户指南》**中的教程：[使用 Lambda 函数访问 Amazon RDS 数据库](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/rds-lambda-tutorial.html)进行操作。
+ 通过阅读[将 AWS Lambda 与 Amazon RDS 结合使用](services-rds.md)，了解有关将 Lambda 与 Amazon RDS 搭配使用的更多信息。

------