2

我最近尝试分析 AWS Lambda 中托管的服务的一些性能问题。分解这个问题,我意识到它只是在每个容器的第一次调用中。在隔离问题时,我发现自己创建了一个新的测试项目来获取一个简单的示例。

测试项目 (您可以克隆它、构建它mvn package、部署它sls deploy,然后通过 AWS 管理控制台对其进行测试。)

该项目有 2 个 AWS Lambda 函数:sourcetarget. 该target函数只返回一个空的 json {}。该函数使用 AWS Lambda 开发工具包source调用该函数。target

target函数的大致持续时间在冷启动时为 300-350 毫秒,在热调用时为 1 毫秒。该source函数的大致持续时间是冷启动时 6000-6300 毫秒,热调用时为 280 毫秒。

函数冷启动的 6 秒开销source似乎是获取客户端的 3 秒和调用另一个函数的 3 秒,热调用分别为 3 毫秒和 250 毫秒。对于 AWS SNS 等其他服务,我的时间也差不多。

我真的不明白它在这 6 秒内做了什么,以及我能做些什么来避免它。在进行预热调用时,我可以获取客户端并存储参考以避免前几秒钟,但其他几秒钟来自实际使用其他服务(SNS、Lambda 等),我不能真正做到这一点无操作。

那么,其他人是否经历相同的冷启动持续时间,我该怎么做才能提高性能?(除了设置内存)

4

3 回答 3

3

预置并发有助于您拥有的代码初始化持续时间。除此之外,它还针对来自函数代码的执行环境设置的另一个开销。

请参阅此处的打开预配置并发部分。

于 2020-12-05T15:50:32.120 回答
2

Java Lambda 冷启动速度慢的主要原因是需要加载类和初始化对象。对于简单的程序,这可能非常快:除了打印“Hello, World”之外什么都不做的 Lambda 将在大约 40 毫秒内运行,这与 Python 运行时类似。另一方面,一个 Spring 应用程序需要更多的时间来启动,因为即使是一个简单的 Spring 应用程序在它做任何有用的事情之前都会加载数千个类。

虽然减少冷启动时间的明显方法是减少需要加载的类的数量,但这很难做到,而且通常是不可能的。例如,如果您在 Spring 中编写 Web 应用程序,则无法在处理 Web 请求之前初始化 Spring 应用程序上下文。

如果这不是一个选项,并且您正在使用 Maven Shade 插件来生成“uber-JAR”,那么您应该切换到我在此处描述的 Assembly 插件。原因是 Lambda 解压缩了您的部署包,因此“uber-JAR”变成了许多必须单独打开的小类文件。

最后,增加你的内存分配。毫无疑问,这是您可以为 Lambda 性能、Java 或其他方式做的最好的事情。首先,因为增加内存减少了 Java 垃圾收集器必须做的工作量。其次,因为您的 Lambda 获得的 CPU 数量取决于内存分配。直到 1,769 MB,您才能获得完整的虚拟 CPU。我建议为 Java 应用程序提供 2 GB 的空间;较大分配的成本通常会被减少的 CPU 需求所抵消。

不会做的一件事是为预置并发付费。如果您想让机器一直运行,请使用 ECS/EKS/EC2。并认识到,如果您的需求激增,您仍然会遇到冷启动。


更新:我在假期里花了一些时间量化各种性能改进技术。完整的文章在这里,但这些数字值得重复。

我的示例程序就像 OP 一样,是一个“什么都不做”,它只是创建了一个 SDK 客户端并用它来调用一个 API:

public void handler(Object ignored, Context context)
{
    long start = System.currentTimeMillis();
    
    AWSLogs client = AWSLogsClientBuilder.defaultClient();
    
    long clientCreated = System.currentTimeMillis();
    
    client.describeLogGroups();
    
    long apiInvoked = System.currentTimeMillis();
    
    System.err.format("time to create SDK client = %6d\n", (clientCreated - start));
    System.err.format("time to make API call     = %6d\n", (apiInvoked - clientCreated));
}

我用不同的内存大小运行它,每次都强制冷启动。所有时间都以毫秒为单位:

|                   |  512 MB | 1024 MB | 2048 MB | 4096 MB |
|+++++++++++++++++++|+++++++++|+++++++++|+++++++++|+++++++++|
| Create client     |    5298 |    2493 |    1272 |    1019 |
| Invoke API call   |    3844 |    2023 |    1061 |     613 |
| Billed duration   |    9213 |    4555 |    2349 |    1648 |

正如我上面所说,增加内存的主要好处是同时增加 CPU。创建和初始化 SDK 客户端是 CPU 密集型的,因此您可以提供的 CPU 越多越好。


更新 2:今天早上我尝试用GraalVM编译一个简单的 AWS 程序。构建独立可执行文件需要几分钟时间,即使如此,由于 AWS 开发工具包的依赖性,它还创建了一个“后备映像”(具有嵌入式 JDK)。当我比较运行时时,使用标准 Java 运行没有区别。

底线:将 Java 用于运行时间足够长以受益于 Hotspot 的事物。对运行时间短且需要低延迟的事情使用不同的语言(Python、JavaScript,也许是 Go)。

于 2020-12-05T19:11:40.880 回答
0

aws-lightweight-client-java是一个独立的 jar(无依赖项),小于 60K。它的构建的确切目的是减少 Java Lambda 冷启动时间,它可以大大减少并且易于使用(尽管您可能需要检查 AWS API 文档以完成您的任务)。我发现使用 AWS SDK S3 jar 时,我的冷启动时间约为 10 秒,而使用这个轻量级客户端则缩短到 4 秒(这是分配了 512MB 内存)。为 Lambda 分配 2GB 内存使用 AWS 开发工具包产生 3.6 秒的冷启动时间,而使用轻量级客户端则降至 1 秒。

库进行 https 调用这一事实确实带来了 2000 个左右的类的加载,因此很难比 1 秒快得多(除非有一些很酷的 https 库在这方面效率更高)。

于 2021-06-04T11:36:10.630 回答