手记

在AWS Lambda上使用AWS Serverless Java容器运行Spring Boot 3.4应用(第一部分)

简介

在《Spring Boot 3 应用程序在 AWS Lambda 上》系列文章中,我们在第二部分(第二部分)中介绍了 AWS 无服务器 Java 容器。在第三部分(第三部分)中,我们解释了如何使用 Java 21 和 Spring Boot 3.2 通过 AWS 无服务器 Java 容器实现 AWS Lambda 函数。在第四部分(第四部分)中,我们测量了 Lambda 函数的启动性能(包括冷启动和热启动时间),包括启用 Lambda SnapStart 并介绍了各种基于 SnapStart 的预热技术。

由于示例中使用的 Spring Boot 3.2 版本已经发布超过一年,而本文撰写时(2024年底)的当前版本已是 3.4,我决定更新所有示例并重新测量 Lambda 性能。此外,示例应用程序中使用的 Spring Boot 3 无服务器 Java 容器及其他许多依赖项的版本也已更新。我还决定使用不同的 Java 编译选项进行更详细的 Lambda 性能测量,并更好地可视化 Lambda SnapStart 快照分层缓存的效果。

如何使用 AWS Serverless Java Container 和 Java 21 管理运行时以及 Spring Boot 3.4 编写 AWS Lambda 函数

AWS Serverless Java Container 的概念在[部分 2]中介绍的,以及如何在 AWS Lambda 中使用 AWS Serverless Java Container 的方法在[部分 3]中解释的,仍然适用。

这个简单的样本应用程序也保持不变,请参见下面的架构图。

但我将所有依赖项更新到了撰写本文时的最新版本(即2024年底),并将源代码发布在spring-boot-3.4-with-aws-serverless-java-container仓库中。我们使用的是Spring Boot 3.4.0版本和AWS Serverless Java Container for Spring Boot 3版本2.1.0。当然,从那时起,库中的其他较小或较大版本更新已经发布,但我认为除了在pom.xml中更新版本外,无需对应用程序代码进行任何其他更改即可使其正常运行。据我记忆,在一年后的这种大规模版本更新中,我没有更改应用程序代码。

以下内容需要被安装以搭建并运行示例:

构建应用程序,执行 mvn clean package
执行 sam deploy -g(部署应用)。

为了创建id为1的产品,请执行

curl -X PUT -d '{ "id": 1, "name": "Print 10x13", "price": 0.15 }‘ -H "X-API-Key: a6ZbcDefQW12BN56WEI34 " https://{$API_GATEWAY_URL}/prod/products

此命令用于更新产品信息

为了找到id为1的产品,请这样做:

curl -H "X-API-Key: a6ZbcDefQW12BN56WEI34" https://{$API_GATEWAY_URL}/prod/products/1

我发现,使用 Spring Boot 3.4 版本编译出来的工件在部署到 AWS Lambda 上时,其大小比使用 3.2 版本时更大(25.500 KB 对比 22.000 KB)。我猜测所有依赖项更新版本的工件变大了,导致了这种情况。我们知道,工件越大,Lambda 函数的冷启动时间就越长。所以,咱们来看看。

使用Java 21托管运行时和Spring Boot 3.4,通过AWS无服务器Java容器来测量AWS Lambda函数的冷启动和暖启动时间

在第 4 部分中介绍的所有衡量 AWS Lambda 性能的技术仍然适用。我们将应用这些技术,包括 SnapStart 以及额外的 DynamoDB 和 API Gateway 请求预调用。

以下实验的结果也是基于超过100次冷启动和大约10万次热启动,使用内存设置为1024 MB的Lambda函数GetProductByIdFunction持续1小时。实验中使用了Java Corretto版本java:21.v27。你可以使用任何你想要的工具,例如Serverless-artilleryPostman

我还利用了两种不同的Java编译选项来测量Lambda的性能:分层编译(这是Java 21中的默认编译选项,即tiered),和编译选项XX:TieredStopAtLevel=1。对于后者,您需要在Lambda函数的JAVA_TOOL_OPTIONS环境变量中设置以下内容:"-XX:+TieredCompilation -XX:TieredStopAtLevel=1",如所示。

    全局设置:
      功能:
        运行时环境: java21
        环境:
          环境变量:
            JAVA_TOOL_OPTIONS: "-XX:+TieredCompilation -XX:TieredStopAtLevel=1"

切换到全屏 退出全屏

我也希望更好地可视化Lambda SnapStart快照分层缓存策略的效果,展示所有100个冷启动的性能测量,但只展示最后70个较快速的冷启动,忽略初始较慢的30个冷启动。根据Lambda函数的更新频率以及缓存层失效的情况,Lambda函数可能经历数千或数万个冷启动,因此,最初的较慢冷启动不再显著影响整体性能。您可以在Mike Danilov的文章《AWS Lambda Under the Hood》中了解更多关于Lambda SnapStart快照分层缓存策略的效果。在我的文章《AWS SnapStart - Part 17:Java 21在AWS Lambda中的快照分层缓存对冷启动的影响》中也探讨了这一效果。

所以让我们来提供测量结果。缩写 c 代表冷启动,w 代表热启动。

冷启动(c)和暖启动(w)时间(以毫秒为单位),(使用了分层编译):

场景编号 c p50 c p75 c p90 c p99 c p99.9 c max w p50 w p75 w p90 w p99 w p99.9 w max
未启用 SnapStart 功能 6611.74 6725.05 6799.39 6978.40 6999.36 7003.54 6.45 7.21 8.33 20.01 49.23 1741.84
启用 SnapStart 但未应用预热机制,全部 2141.30 2197.67 3511.92 3543.66 3547.20 3549.65 6.61 7.39 8.60 19.53 53.31 2742.84
启用 SnapStart 但未应用预热机制,最后 70 次调用 2127.51 2176.16 2236.17 2360.17 2360.17 2360.17 6.61 7.39 8.67 19.53 49.23 1796.23
启用 SnapStart 并应用 DynamoDB 调用预热机制,全部 925.73 966.39 2296.48 2331.17 2342.84 2343.82 6.40 7.16 8.53 18.92 42.99 1565.13
启用 SnapStart 并应用 DynamoDB 调用预热机制,最后 70 次调用 910.57 942.65 973.39 1047.29 1047.29 1047.29 6.30 7.10 8.39 18.47 41.64 474.53
启用 SnapStart 并应用 API Gateway 请求调用预热机制,全部 668.31 710.32 1528.95 1555.15 1558.26 1558.57 6.35 7.16 8.53 19.22 42.99 715.99
启用 SnapStart 并应用 API Gateway 请求调用预热机制,最后 70 次调用 658.01 685.51 720.31 910.7 907.7 910.7 6.35 7.16 8.53 19.22 41.97 213.41

冷启动 (c) 和热启动 (w) 时间(毫秒),使用 -XX:+TieredCompilation -XX:TieredStopAtLevel=1 编译选项:

场景编号 c p50 c p75 c p90 c p99 c p99.9 c max w p50 w p75 w p90 w p99 w p99.9 w max
SnapStart未启用 6799.39 6964.47 7083.82 7604.78 8164.06 8165.55 6.61 7.51 8.80 20.41 62.99 1843
SnapStart启用但未应用预置,全部 2191.09 2248.78 3755.16 3789.09 3792.88 3793.42 6.61 7.51 8.80 20.09 52.90 2883
SnapStart启用但未应用预置,最后70次 2151 2176.16 2202.21 2260.85 2432.66 2432.66 6.51 7.39 8.66 20.09 46.59 1906
SnapStart启用并应用了DynamoDB调用预置,全部 926.66 972.20 2716.36 2738.16 2749.13 2749.51 6.40 7.21 8.60 20.17 76.30 1812
SnapStart启用并应用了DynamoDB调用预置,最后70次 913.04 935.13 1003.67 1453.38 1453.38 1453.38 6.30 7.10 8.46 19.38 65.58 484
SnapStart启用并应用了API Gateway请求调用预置,全部 678.40 737.82 1244.44 1267.03 1302.99 1303.27 6.30 7.04 8.33 18.92 44.03 456.75
SnapStart启用并应用了API Gateway请求调用预置,最后70次 665.72 685.75 754.55 849.83 849.83 849.83 6.25 6.99 8.20 18.62 41.64 218.7
结论部分

在这篇文章中,我们将示例应用程序更新为使用Spring Boot 3.4、AWS Serverless Java Container Spring Boot 3版本2.1.0以及其他依赖项的最新版本(截至2024年底)。我们还通过不同的方法和Java编译选项来测试Lambda的性能。我的总体感觉是,分层编译器在减少冷启动时间方面表现更佳。

与仅针对 Spring Boot 3.2-XX:+TieredCompilation -XX:TieredStopAtLevel=1 编译选项的 Lambda 性能测量相比,我们发现 Spring Boot 3.4 的冷启动时间较长(这是由于更大的部署文件大小),但热启动时间较短。值得研究哪些不必要的依赖项是否可以进一步从 pom.xml 中排除,正如我们已经对 spring-boot-starter-loggingspring-boot-starter-tomcat 两个依赖项所做的那样,参见以下来自 pom.xml 的代码片段:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <exclusions>
            <exclusion>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-logging</artifactId>
            </exclusion>
            <exclusion>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-tomcat</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
</dependencies>

全屏 | 退出全屏

当我们使用 Amazon API Gateway 时,我们的应用程序不需要 Apache Tomcat,这本身就会让部署包的大小增加约 4 到 5 MB。也许还有其他依赖项可以排除,比如 JPA 实现,因为我们使用了 DynamoDB NoSQL 数据库。这样,我们还可以进一步减小 Lambda 部署包的大小,从而缩短冷启动时间。

我们还清楚地注意到Lambda SnapStart快照分层缓存的效果。所以,不要只测量最初的几次冷启动时间,因为这些冷启动确实比较慢,但随着后续调用会明显变快。最初的几次长时间冷启动可能不会显著影响你应用程序的整体性能。

在系列中的下一部分,我们将基于 AWS Lambda Web Adapter 的示例 Spring Boot 应用更新,来使用最新版本的依赖库。

如果你喜欢我写的文章内容,请支持我,关注我的GitHub账户,并给我仓库点个星。
OOP:面向对象编程
CRUD:创建、读取、更新和删除
JVM:Java虚拟机
SUT:系统被测

0人推荐
随时随地看视频
慕课网APP