

# 最大限度地提高 Lambda SnapStart 的性能
<a name="snapstart-best-practices"></a>

**Topics**
+ [性能优化](#snapstart-tuning)
+ [联网最佳实践](#snapstart-networking)

## 性能优化
<a name="snapstart-tuning"></a>

要最大限度地发挥 SnapStart 的优势，请考虑为运行时提供以下代码优化建议。

**注意**  
SnapStart 在与大规模函数调用搭配使用时效果最佳。不经常调用的函数可能无法获得相同的性能改进。

### Java
<a name="snapstart-tuning-java"></a>

为了最大限度地发挥 SnapStart 的优势，我们建议您在初始化代码中而不是在函数处理程序中预加载依赖项并初始化导致启动延迟的资源。这会将与大量类加载相关的延迟移出调用路径，从而优化了 SnapStart 的启动性能。

如果您在初始化期间无法预加载依赖项或资源，那么我们建议您使用虚拟调用进行预加载。为此，请更新函数处理程序代码，如 AWS Labs GitHub 存储库上[宠物商店函数](https://github.com/awslabs/aws-serverless-java-container/tree/main/samples/spring/pet-store)的以下示例所示。

```
private static SpringLambdaContainerHandler<AwsProxyRequest, AwsProxyResponse> handler;
  static {
      try {
          handler = SpringLambdaContainerHandler.getAwsProxyHandler(PetStoreSpringAppConfig.class);

          // Use the onStartup method of the handler to register the custom filter
          handler.onStartup(servletContext -> {
              FilterRegistration.Dynamic registration = servletContext.addFilter("CognitoIdentityFilter", CognitoIdentityFilter.class);
              registration.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), false, "/*");
          });

          // Send a fake Amazon API Gateway request to the handler to load classes ahead of time
          ApiGatewayRequestIdentity identity = new ApiGatewayRequestIdentity();
          identity.setApiKey("foo");
          identity.setAccountId("foo");
          identity.setAccessKey("foo");

          AwsProxyRequestContext reqCtx = new AwsProxyRequestContext();
          reqCtx.setPath("/pets");
          reqCtx.setStage("default");
          reqCtx.setAuthorizer(null);
          reqCtx.setIdentity(identity);

          AwsProxyRequest req = new AwsProxyRequest();
          req.setHttpMethod("GET");
          req.setPath("/pets");
          req.setBody("");
          req.setRequestContext(reqCtx);

          Context ctx = new TestContext();
          handler.proxy(req, ctx);


      } catch (ContainerInitializationException e) {
          // if we fail here. We re-throw the exception to force another cold start
          e.printStackTrace();
          throw new RuntimeException("Could not initialize Spring framework", e);
      }
  }
```

### Python
<a name="snapstart-tuning-python"></a>

要最大限度地发挥 SnapStart 的优势，请在 Python 函数内专注于高效的代码组织和资源管理。作为一般指南，在[初始化阶段](lambda-runtime-environment.md#runtimes-lifecycle-ib)执行繁重的计算任务。此方法将耗时的操作移出调用路径，从而提高了整体函数性能。为了有效实施这一策略，建议采用以下最佳实践：
+ 导入函数处理程序之外的依赖项。
+ 创建处理程序之外的 `boto3` 实例。
+ 在调用处理程序之前，初始化静态资源或配置。
+ 考虑使用快照前的[运行时钩子](snapstart-runtime-hooks-python.md)来执行资源密集型任务，例如下载外部文件、预加载 Django 等框架或加载机器学习模型。

**Example – 优化 SnapStart 的 Python 函数**  

```
# Import all dependencies outside of Lambda handler
from snapshot_restore_py import register_before_snapshot
import boto3
import pandas
import pydantic

# Create S3 and SSM clients outside of Lambda handler
s3_client = boto3.client("s3")

# Register the function to be called before snapshot
@register_before_snapshot
def download_llm_models():
    # Download an object from S3 and save to tmp
    # This files will persist in this snapshot
    with open('/tmp/FILE_NAME', 'wb') as f:
        s3_client.download_fileobj('amzn-s3-demo-bucket', 'OBJECT_NAME', f)
    ...

def lambda_handler(event, context):
    ...
```

### .NET
<a name="snapstart-tuning-dotnet"></a>

要减少即时（JIT）编译和程序集加载时间，请考虑从 `RegisterBeforeCheckpoint` [运行时钩子](snapstart-runtime-hooks-dotnet.md)调用函数处理程序。由于 .NET 分层编译的工作方式，您可以通过多次调用处理程序来获得最佳结果，如以下示例所示。

**重要**  
请确保您的虚拟函数调用不会产生意外的副作用，例如启动业务事务。

**Example**  

```
public class Function
{
    public Function()
    {
        Amazon.Lambda.Core.SnapshotRestore.RegisterBeforeSnapshot(FunctionWarmup);
    }

    // Warmup method that calls the function handler before snapshot to warm up the .NET code and runtime.
    // This speeds up future cold starts after restoring from a snapshot.

    private async ValueTask FunctionWarmup()
    {
        var request = new APIGatewayProxyRequest
        {
            Path = "/heathcheck",
            HttpMethod = "GET"
        };

        for (var i = 0; i < 10; i++)
        {
            await FunctionHandler(request, null);
        }
    }

    public async Task<APIGatewayProxyResponse> FunctionHandler(APIGatewayProxyRequest request, ILambdaContext context)
    {
        //
        // Process HTTP request
        // 

        var response = new APIGatewayProxyResponse
        {
            StatusCode = 200
        };
        
        return await Task.FromResult(response);
    }
}
```

## 联网最佳实践
<a name="snapstart-networking"></a>

当 Lambda 从快照恢复您的函数时，无法保证您的函数在初始化阶段建立的连接的状态。在大多数情况下，AWS 开发工具包建立的网络连接会自动恢复。对于其他连接，我们建议您遵循以下最佳实践。

**重新建立网络连接**  
函数从快照恢复时，请务必重新建立网络连接。我们建议您在函数处理程序中重新建立网络连接。或者，您可以使用还原后的[运行时钩子](snapstart-runtime-hooks.md)。

**不要使用主机名作为唯一的执行环境标识符**  
我们建议不要使用 `hostname` 将执行环境标识为应用程序中的唯一节点或容器。通过 SnapStart，可以使用单个快照作为多个执行环境的初始状态。`InetAddress.getLocalHost()`（Java）、`socket.gethostname()`（Python）和 `Dns.GetHostName()`（.NET）的所有执行环境都返回相同的 `hostname` 值。对于需要唯一执行环境标识或 `hostname` 值的应用程序，我们建议您在函数处理程序中生成唯一的 ID。或者，使用还原后的[运行时钩子](snapstart-runtime-hooks.md)生成唯一的 ID，然后使用该唯一 ID 作为执行环境的标识符。

**避免将连接绑定到固定源端口**  
我们建议您避免将网络连接绑定到固定源端口。函数从快照恢复时，会重新建立连接，绑定到固定源端口的网络连接可能会失败。

**避免使用 Java DNS 缓存**  
Lambda 函数已经缓存了 DNS 响应。如果您将另一个 DNS 缓存与 SnapStart 结合使用，则函数从快照恢复时可能会出现连接超时。

`java.util.logging.Logger` 类可以间接启用 JVM DNS 缓存。要覆盖默认设置，请在初始化 `logger` 之前将 [networkaddress.cache.ttl](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/net/InetAddress.html#inetaddress-caching-heading) 设置为 0。示例：

```
public class MyHandler {
  // first set TTL property
  static{
   java.security.Security.setProperty("networkaddress.cache.ttl" , "0");
  }
 // then instantiate logger
  var logger = org.apache.logging.log4j.LogManager.getLogger(MyHandler.class);
}
```

为了防止 Java 11 运行时出现 `UnknownHostException` 故障，建议将 `networkaddress.cache.negative.ttl` 设置为 0。在 Java 17 及更高版本的运行时中，不必执行此步骤。您可以使用 `AWS_LAMBDA_JAVA_NETWORKADDRESS_CACHE_NEGATIVE_TTL=0` 环境变量为 Lambda 函数设置此属性。

禁用 JVM DNS 缓存并不能禁用 Lambda 的托管式 DNS 缓存。