【问题标题】:How to improve performance of initial calls to AWS services from an AWS Lambda (Java)?如何提高从 AWS Lambda (Java) 对 AWS 服务的初始调用的性能?
【发布时间】:2021-03-17 07:41:12
【问题描述】:

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

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

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

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

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

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

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

【问题讨论】:

  • 我建议您使用 AWS X-Ray。这可能会让您有更深入的了解。
  • 您的测试代码调用了 Lambda - 这对于确定 Lambda 代码为何表现不佳没有用处。 Lambda 中的代码在哪里?
  • 两个 lambdas 中的代码都在测试项目中。目标 lambda 只打印“invoked”,然后返回一个空的 json 对象“{}”。测试的主要目标是弄清楚如何优化调用不同 lambda 函数的过程,所以我认为调用一个简单的“hello-world”lambda 是最准确的测试

标签: java amazon-web-services aws-lambda serverless-framework cold-start


【解决方案1】:

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

请参阅开启预置并发部分here

【讨论】:

  • 提供的并发肯定有助于避免冷启动,但它并不能解决问题,因为在实际使用该功能时会发生冷启动(例如,实际发布到 SNS 主题或实际调用另一个 Lambda),因此,无论是预配置的并发性还是像 thundra 这样的工具都不能真正预热 lambda 容器。
【解决方案2】:

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

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

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

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

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


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

我的示例程序与 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)。

【讨论】:

  • 所以,如果我理解正确的话,我遇到的延迟可能主要是加载类文件并处理它们,对吧?在那种情况下,更轻量级的 AWS Lambda sdk 包会不会很有趣?在任何情况下,我都会尝试在包装上摆弄一下,并尝试组装插件,看看我会得到什么其他结果。
  • @Wietlol - 我不确定你会在哪里找到“更轻量级”的 SDK,除非你想自己编写它。即便如此,现有的 SDK 还是比较轻量级的。
  • 但我敢打赌,增加分配给 Lambda 的 RAM 量将远远超过您可以做的任何其他事情。我重读了您的问题,发现您不想增加 RAM。为什么?
  • 增加 RAM 没有问题,我可能会选择 3GB 左右,但我正在寻找其他方法来加快初始化过程。如果我可以将初始化时间控制在 1 秒以下,我就可以忘记强制使用暖容器,只是将 RAM 提高到 10GB 不是我愿意去的选项,因此我寻找优化选项
  • 我尝试用 graalvm 制作一个带有本机图像的“hello world”应用程序,但发现这样做非常困难,我还看到了一篇关于使用较新的 java sdk 并指定更多客户端设置的帖子创作时:docs.aws.amazon.com/sdk-for-java/latest/developer-guide/…,但性能仍然没有显着变化。我决定暂时只增加容器的容量(内存/cpu)。
【解决方案3】:

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

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

【讨论】:

    猜你喜欢
    • 2017-08-02
    • 2017-05-15
    • 2017-10-08
    • 1970-01-01
    • 1970-01-01
    • 2019-01-21
    • 1970-01-01
    • 2017-07-28
    • 1970-01-01
    相关资源
    最近更新 更多