View a markdown version of this page

使用 Lambda SnapStart 处理唯一性 - AWS Lambda

使用 Lambda SnapStart 处理唯一性

调用在 SnapStart 函数上纵向扩展时,Lambda 使用单个初始化的快照来恢复多个执行环境。如果您的初始化代码生成了快照中包含的唯一内容,则跨执行环境重复使用该内容时,该内容可能不是唯一的。若要在使用 SnapStart 时保持唯一性,则必须在初始化后生成唯一的内容。这包括唯一的 ID、唯一的密钥和用于生成伪随机性的熵。

我们推荐使用以下最佳实践来帮助您保持代码的唯一性。对于 Java 函数,Lambda 还提供了开源 SnapStart 扫描工具,以帮助检查假设唯一性的代码。如果您在初始化阶段生成唯一的数据,则可以使用运行时挂钩来还原唯一性。使用运行时挂钩,您可以在 Lambda 拍摄快照之前立即运行特定代码,也可以在 Lambda 从快照恢复函数后立即运行特定代码。

避免在初始化期间保存依赖唯一性的状态

在函数的初始化阶段,避免缓存旨在保持唯一性的数据,例如生成用于日志记录的唯一 ID 或为随机函数设置种子。相反,建议您在函数处理程序中生成唯一的数据或为随机函数设置种子 – 或使用运行时钩子。

以下示例演示如何在函数处理程序中生成 UUID。

Java
例– 在函数处理程序中生成唯一的 ID
import java.util.UUID; public class Handler implements RequestHandler<String, String> { private static UUID uniqueSandboxId = null; @Override public String handleRequest(String event, Context context) { if (uniqueSandboxId == null) uniqueSandboxId = UUID.randomUUID(); System.out.println("Unique Sandbox Id: " + uniqueSandboxId); return "Hello, World!"; } }
Python
例– 在函数处理程序中生成唯一的 ID
import json import random import time unique_number = None def lambda_handler(event, context): seed = int(time.time() * 1000) random.seed(seed) global unique_number if not unique_number: unique_number = random.randint(1, 10000) print("Unique number: ", unique_number) return "Hello, World!"
.NET
例– 在函数处理程序中生成唯一的 ID
namespace Example; public class SnapstartExample { private Guid _myExecutionEnvironmentGuid; public SnapstartExample() { // This GUID is set for non-restore use cases, such as testing or if SnapStart is turned off _myExecutionEnvironmentGuid = new Guid(); // Register the method which will run after each restore. You may need to update Amazon.Lambda.Core to see this Amazon.Lambda.Core.SnapshotRestore.RegisterAfterRestore(MyAfterRestore); } private ValueTask MyAfterRestore() { // After restoring this snapshot to a new execution environment, update the GUID _myExecutionEnvironmentGuid = new Guid(); return ValueTask.CompletedTask; } public string Handler() { return $"Hello World! My Execution Environment GUID is {_myExecutionEnvironmentGuid}"; } }

使用加密安全伪随机数生成器(CSPRNG)

启用 SnapStart 后,Lambda 将为函数执行环境生成快照,快照包含全部应用程序内存与系统内存。这意味着每个随机数生成器(RNG)的内部状态会完整保留快照捕获瞬间的值,同时应用程序及其依赖项已生成、存放在内存缓冲区中的所有随机字节也一并保留。

当 Lambda 从快照恢复执行环境时,会使用全新熵为 /dev/random、/dev/urandom 对应的内核随机数生成器重新设置种子。直接从这类设备读取随机数的软件,在 SnapStart 下可维持随机性。

如果您的应用程序依赖随机性,我们建议您使用加密安全的随机数生成器(CSPRNG)。除 AL 提供的 OpenSSL 外,支持 SnapStart 的 Lambda 托管运行时(Java 11 及以上、Python 3.12 及以上、.NET 8 及以上)内置以下密码学安全伪随机数生成器(CSPRNG):

  • Java:java.security.SecureRandom

  • Python:random.SystemRandom

  • .NET:System.Security.Cryptography.RandomNumberGenerator

注意

对于 Go Lambda 运行时,若函数使用标准库的 crypto/rand,则无需修改,该组件默认兼容 SnapStart。

将函数打包为容器映像时,基础映像决定其与 SnapStart 的兼容特性:

  • 托管运行时对应的 Lambda 基础映像(Java 11 及以上、Python 3.12 及以上、.NET 8 及以上):如上所述,可兼容 SnapStart。

  • provided.al2023 基础映像:若编程语言运行时从 /dev/random、/dev/urandom,或是 Amazon Linux 2023 软件包列表内列出、兼容 SnapStart 的 OpenSSL 构建包(openssl-snapsafe-libs)获取熵源,则可兼容 SnapStart。若运行时依赖其他熵源,请按照下文“自定义基础映像”中的步骤操作。

  • 自定义基础映像:审计加密库,并使用恢复后运行时挂钩,丢弃所有已缓存随机数生成器(RNG)状态与缓冲随机字节,确保库在环境恢复后从系统读取全新熵。例如,使用 rand::rngs::ThreadRng 的 Rust 函数,必须在环境恢复后调用 reseed()。建议尽可能采用 Lambda 基础映像来构建函数。特别注意:若基础映像自带 OpenSSL 版本或是 OpenSSL 衍生分支(如 BoringSSL、LibreSSL),请改用 provided.al2023 基础映像(内含兼容 SnapStart 的 OpenSSL 构建包:openssl-snapsafe-libs),或使用 AWS libcrypto(AWS-LC)。

最低支持的库版本

AWS 加密库会自动保持 SnapStart 的随机性,从下表中指定的最低版本开始。如果您将这些库与 Lambda 函数一起使用,请确保使用以下最低版本或更高版本:

库 支持的最低版本(x86) 支持的最低版本(ARM)
AWS libcrypto(AWS-LC)

1.16.0

1.30.0

AWS libcrypto FIPS

2.0.13

2.0.13

如果通过以下库将上述加密库与 Lambda 函数打包为可传递依赖项,请确保使用以下最低版本或更高版本:

库 支持的最低版本(x86) 支持的最低版本(ARM)
AWS SDK for Java 2.x

2.23.20

2.26.12

AWS Common Runtime for Java

0.29.8

0.29.25

Amazon Corretto 加密提供商

2.4.1

2.4.1
Amazon Corretto 加密提供商 FIPS

2.4.1

2.4.1

以下示例演示如何使用 CSPRNG 来保证唯一的编号序列,即使从快照中还原函数也是如此。

Java
例– java.security.SecureRandom
import java.security.SecureRandom; public class Handler implements RequestHandler<String, String> { private static SecureRandom rng = new SecureRandom(); @Override public String handleRequest(String event, Context context) { for (int i = 0; i < 10; i++) { System.out.println(rng.next()); } return "Hello, World!"; } }
Python
例– random.SystemRandom
import json import random secure_rng = random.SystemRandom() def lambda_handler(event, context): random_numbers = [secure_rng.random() for _ in range(10)] for number in random_numbers: print(number) return "Hello, World!"
.NET
例– RandomNumberGenerator
using Amazon.Lambda.Core; using System.Security.Cryptography; [assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] namespace DotnetSecureRandom; public class Function { public string FunctionHandler() { using (RandomNumberGenerator rng = RandomNumberGenerator.Create()) { byte[] randomUnsignedInteger32Bytes = new byte[4]; for (int i = 0; i < 10; i++) { rng.GetBytes(randomUnsignedInteger32Bytes); int randomInt32 = BitConverter.ToInt32(randomUnsignedInteger32Bytes, 0); Console.WriteLine("{0:G}", randomInt32); } } return "Hello World!"; } }

SnapStart 扫描工具(仅 Java)

Lambda 为 Java 提供了扫描工具,以帮助您检查假设唯一性的代码。SnapStart 扫描工具是一个开源 SpotBugs 插件,该工具根据一组规则运行静态分析。该扫描工具有助于识别可能打破有关唯一性假设的潜在代码实施。有关安装说明和扫描工具执行的检查列表,请参阅 GitHub 上的 aws-lambda-snapstart-java-rules 存储库。

要详细了解如何使用 SnapStart 处理唯一性,请参阅 AWS 计算博客上的通过 AWS Lambda SnapStart 加快启动速度。