亚马逊AWS官方博客

使用更新后的无服务器 Java 容器运行时进行 Java 应用改造

本文由担任 Principal Solutions Architect 的 Dennis Kieselhorst 撰写。

Java 集可移植性、效率、社区和功能广度于一身,成为过去超过 25 年来企业构建应用程序的热门选择。作为率先推出的无服务器函数,AWS Lambda 改变了对编程语言和运行时环境的需求。函数通常是短暂使用、单一用途的,不需要大量的基础设施配置。

这篇博文展示了如何使用更新后的 AWS 无服务器 Java 容器,对传统 Java 应用程序进行现代化改造,只需极少的代码更改即可在 Lambda 上运行。

部署模型比较

经典 Java 企业应用程序通常运行在应用程序服务器(例如 JBoss/ WildFly、Oracle WebLogic 和 IBM WebSphere)上,或者运行在 Servlet 容器(例如 Apache Tomcat)上。底层 Java 虚拟机通常全天候运行,使用多线程功能为多个请求提供服务。

典型的长时间运行的 Java 应用程序服务器

典型的长时间运行的 Java 应用程序服务器

使用 Java 构建 Lambda 函数时,不再需要 HTTP 服务器,但在 Lambda 环境中运行代码有一些其它注意事项。代码在执行环境中运行,该环境一次处理一个调用。函数最长可以运行 15 分钟,最多分配 10 Gb 内存。

函数由事件触发,例如对应负载的 HTTP 请求。Amazon API Gateway HTTP 请求使用以下 JSON 负载来调用函数:

Amazon API Gateway HTTP 请求负载

Amazon API Gateway HTTP 请求负载

用于处理这些事件的代码不同于您在传统应用程序中的实施方式。

AWS 无服务器 Java 容器

借助 AWS 无服务器 Java 容器,您可以更轻松地在 Lambda 中,运行使用 Spring、Spring Boot 或 Jax-RS/Jersey 等框架编写的 Java 应用程序。

容器提供了适配器逻辑,最大限度减少了代码更改。传入的事件将转换为 Servlet 规范,因此框架可以像以前一样工作。

AWS 无服务器 Java 容器适配器

AWS 无服务器 Java 容器适配器

此库的第 1 版于 2018 年发布。今天,AWS 宣布推出第 2 版,该版本支持最新的 Jakarta EE 规范,以及 Spring Framework 6.x、Spring Boot 3.x 和 Jersey 3.x。

示例:修改 Spring Boot 应用程序

以下示例说明了如何迁移 Spring Boot 3 应用程序。您可以在 GitHub 存储库中找到 Spring 和其它框架的完整示例。

  1. 将 AWS 无服务器 Java 依赖项添加到您的 Maven POM 构建文件(或相应的 Gradle)中:
  2. <dependency>
        <groupId>com.amazonaws.serverless</groupId>
        <artifactId>aws-serverless-java-container-springboot3</artifactId>
        <version>2.0.0</version>
    </dependency>
  3. 默认情况下,Spring Boot 中嵌入了 Apache Tomcat,用以处理 HTTP 请求。这些示例使用 Amazon API Gateway 来处理入站 HTTP 请求,这样您便可以不包括相应的依赖项。
  4. <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <configuration>
                    <createDependencyReducedPom>false</createDependencyReducedPom>
                </configuration>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                        <configuration>
                            <artifactSet>
                                <excludes>
                                    <exclude>org.apache.tomcat.embed:*</exclude>
                                </excludes>
                            </artifactSet>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

    AWS 无服务器 Java 容器接受 API Gateway 代理请求,并将请求转换为普通 Java 对象。该库还将输出转换为合适的 API Gateway 响应对象。

    运行构建流程后,Maven 的 Shade-plugin 现在会生成一个 Uber-JAR,其中打包了所有依赖项,您可以将它们上传到 Lambda

  5. Lambda 运行时必须知道要调用哪个处理程序方法。您可以配置和使用 SpringDelegatingLambdaContainerHandler 实施,或者实施您自己的处理程序 Java 类来委派给 AWS 无服务器 Java 容器。在您想添加其它功能时,这很有用。
  6. 在函数的运行时设置中配置处理程序名称。
  7. 配置处理程序名称

    配置处理程序名称

  8. 配置名为 MAIN_CLASS环境变量,告知常规处理程序在哪里查找您的原始应用程序主类,该类通常标注为 @SpringBootApplication
  9. 配置 MAIN_CLASS 环境变量

    配置 MAIN_CLASS 环境变量

    您还可以使用基础设施即代码(IaC)工具配置这些设置,例如 AWS CloudFormationAWS Cloud Development Kit(AWS CDK)AWS Serverless Application Model(AWS SAM)

    在 AWS SAM 模板中,相关的更改如下所示。完整模板在 GitHub 存储库中提供。

    Handler: com.amazonaws.serverless.proxy.spring.SpringDelegatingLambdaContainerHandler 
    Environment:
      Variables:
        MAIN_CLASS: com.amazonaws.serverless.sample.springboot3.Application

    优化内存配置

    运行 Lambda 函数时,启动时间和内存占用是重要的考虑因素。您为 Lambda 函数配置的内存量,同时还决定了可用的虚拟 CPU 数量。添加更多内存会成比例地增加 CPU 数量,从而增加可用的整体计算能力。如果函数会受到 CPU、网络或内存的限制,则添加更多内存可以提高性能。

    Lambda 按函数使用的 GB-秒数总量收费。GB-秒数是总内存(以 GB 为单位)和持续时间(以秒为单位)的组合。增加内存会产生额外的成本。但是,在许多情况下,由于增加了可用的 CPU,增加可用内存会缩短函数的运行时间。结果就是,提高性能所需的总体成本可以忽略不计,甚至可能降低。

    选择分配给 Lambda 函数的内存是一个在速度(持续时间)和成本之间取得平衡的优化过程。您可以通过选择不同的内存分配并测量完成时间,来手动测试函数。AWS Lambda Power Tuning 是一款用于简化和自动化这一流程的工具,您可以使用它来优化配置。

    Power Tuning 使用 AWS Step Functions 在不同的内存分配下运行 Lambda 函数的多个并行版本,并衡量性能。该函数在您的 AWS 账户中运行,执行实时 HTTP 调用和 SDK 交互,来衡量生产场景中的性能。

    使用 AWS Lambda SnapStart 缩短冷启动时间

    传统应用程序通常有一大堆依赖项。在 Lambda 生命周期的初始化阶段,Lambda 加载函数代码并初始化依赖项。由于依赖项很多,初始化时间可能会太长,无法满足您的要求。适用于 Java 函数的 AWS Lambda SnapStart 可以将启动性能提高达 10 倍。

    Lambda SnapStart 不会在每次冷启动时都运行函数初始化阶段,而是在部署时运行函数初始化过程。Lambda 获取已初始化执行环境的快照。此快照加密保存在分层缓存中,以实现低延迟访问。调用和扩展函数时,Lambda 将从保存的快照中恢复执行环境,而不是运行完整的初始化过程。这样可以降低启动延迟。

    启用 Lambda SnapStart,您必须首先启用配置设置,并发布函数版本

    启用 SnapStart

    启用 SnapStart

    务必将 API Gateway 端点指向已发布的版本或别名,从而确保使用启用了 SnapStart 的函数。

    AWS SAM 模板中的相应设置包含以下内容:

    SnapStart: 
      ApplyOn: PublishedVersions
    AutoPublishAlias: my-function-alias

    请阅读文档中的 Lambda SnapStart 兼容性注意事项,因为您的应用程序可能包含特定的代码,需要加以注意。

    结论

    使用 Lambda 构建无服务器应用程序,您可以更快地交付功能,但您的语言和运行时必须能够在无服务器架构模型中运行。通过 AWS 无服务器 Java 容器,您可以在传统 Java 企业应用程序与现代化云原生无服务器函数之间架起桥梁。

    您可以使用 AWS Lambda Power Tuning 工具优化 Java Lambda 函数的内存配置,并启用 SnapStart 来优化初始冷启动时间。

    自定进度的 Java on AWS Lambda 研讨会演示了如何构建云原生 Java 应用程序,以及如何将现有 Java 应用程序迁移到 Lambda。

    浏览 AWS 无服务器 Java 容器 GitHub 存储库,您可以在其中报告相关问题和功能请求

    有关更多无服务器学习资源,请访问 Serverless Land