亚马逊云科技 Lambda 现在支持 Java 17

作者: 本杰明·史密斯 马克·赛尔斯 | 2023

这篇文章由无服务器高级专业解决方案架构师 Mark Sailes 撰写。

现在,你可以使用 Java 17 的 亚马逊 Corretto 发行版开发 亚马逊云科技 Lambda 函数。 此版本的Corretto具有长期支持(LTS),这意味着它将在很长一段时间内获得更新和错误修复,从而为在其上构建应用程序的开发人员提供稳定性和可靠性。此运行时还支持 亚马逊云科技 Lambda SnapStart,因此您可以在不损失性能改进的情况下升级到最新的托管运行环境。

Java 17 为开发人员提供了新的语言功能,包括 Java 记录 密封类 多行字符串 。它还进行了改进,以进一步优化在 Gravit on等ARM CPU架构上运行Java。

这篇博客解释了如何开始使用带有 Lambda 的 Java 17、如何使用新的语言功能以及运行时发生了哪些其他变化。

新的语言功能

在 Java 中,通常使用不可变对象传递数据。在 Java 17 之前,这导致了锅炉板代码或使用了像 Lombok 这样的外部库。例如,通用的 Person 对象可能如下所示:

public class Person {
    
    private final String name;
    private final int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }
    
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Person person = (Person) o;

        if (age != person.age) return false;
        return Objects.equals(name, person.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

在 Java 17 中,你可以用一条记录替换整个类,表示为:

public record Person(String name, int age) {

}

等号、HashCode 和 toString 方法,以及私有、最终字段和公共构造函数,由 Java 编译器生成。这简化了你必须维护的代码。

Java 17 托管运行时引入了一项新功能,允许开发人员使用记录作为对象来表示处理程序方法中的事件数据。记录是在 Java 14 中引入的,它提供了一种更简单的语法来声明主要用于存储数据的类。记录允许开发人员定义一个不可变的类,该类具有一组命名属性和访问这些属性的方法,使其成为事件数据的理想之选。此功能简化了代码,使其更易于阅读和维护。此外,它可以提供更好的性能,因为默认情况下记录是不可变的,而且 Java 的运行时可以优化内存分配和垃圾收集过程。要使用记录作为事件处理方法的参数,请定义具有所需属性的记录,并将该记录传递给该方法。在处理程序方法中使用记录作为对象来表示事件数据的能力是 Java 语言的有用补充,它为定义事件数据结构提供了一种简洁而有效的方法。

例如,以下 Lambda 函数使用 Person 记录来表示事件数据:

public class App implements RequestHandler<Person, APIGatewayProxyResponseEvent> {

    public APIGatewayProxyResponseEvent handleRequest(Person person, Context context) {
        
        String id = UUID.randomUUID().toString();
        Optional<Person> savedPerson = createPerson(id, person.name(), person.age());
        if (savedPerson.isPresent()) {
            return new APIGatewayProxyResponseEvent().withStatusCode(200);
        } else {
            return new APIGatewayProxyResponseEvent().withStatusCode(500);
        }
    }

垃圾收集

Java 17 提供了两个新的 Java 垃圾收集器 (GC):Java 15 中引入的 Z 垃圾收集器 (ZGC) 和在 Java 12 中 引入的 Shenandoah

您可以根据三个轴评估 GC:

  • 吞吐量:可以完成的工作量。
  • 延迟:工作需要多长时间才能完成。
  • 内存占用:需要多少额外内存。

ZGC 和 Shenandoah GC 都交换吞吐量和占用空间,尽可能专注于减少延迟。它们可以同时执行所有昂贵的工作,不会停止应用程序线程的执行超过几毫秒。

在 Java 17 托管运行时中,Lambda 继续像在 Java 11 中一样使用 串行 GC。这是一款低占用空间的 GC,非常适合单处理器计算机,在使用 Lambda 函数时通常会出现这种情况。

如果需要,您可以使用 JAVA_TOOL_OPTIONS 环境变量 将默认 GC 更改为替代变量 。例如,如果你使用更多内存运行,因此有多个 CPU,可以考虑使用 Parallel GC。要使用它,请将 JAVA_TOOL_OPTIONS 设置为-XX: +useParallel G C 。

运行时 JVM 配置更改

在 Java 17 运行时中,用于分层编译的 JVM 标志现在默认设置为停止在级别 1。在之前的版本中,你必须通过将 JAVA_TOOL_OPTIONS 设置为-XX: +TieredCompilition - xx: TieredStopatLevel= 1 来做到这一点。

这对于大多数同步工作负载很有用,因为它可以将启动延迟减少多达60%。有关配置分层编译的更多信息,请参阅 “ 优化 Java 的 亚马逊云科技 Lambda 函数性能 ”。

如果您运行的工作负载处理大量批处理、模拟事件或任何其他高度重复的操作,您可能会发现这会减慢函数的持续时间。蒙特卡罗模拟就是一个例子。要更改回之前的设置,请将 JAVA_TOOL_OPTIONS 设置为-XX:-TieredCompil it ion 。

在 Lambda 中使用 Java 17

亚马逊云科技 管理控制台

要使用 Java 17 运行时来开发您的 Lambda 函数,请在创建或更新函数时将运行时值设置为 Java 17。

要将现有 Lambda 函数更新为 Java 17,请在 Lambda 控制台中导航到该函数,然后 在 “运行时设置” 面板中选择编辑。新版本可在运行时下拉列表中找到:

亚马逊云科技 无服务器应用程序模型 (亚马逊云科技 SAM)

亚马逊云科技 SAM 中 ,将运行时属性设置为 java17 以使用此版本:

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: Simple Lambda Function

Resources:
  HelloWorldFunction:
    Type: AWS::Serverless::Function 
    Properties:
      CodeUri: HelloWorldFunction
      Handler: helloworld.App::handleRequest
      Runtime: java17
      MemorySize: 1024

亚马逊云科技 SAM 支持使用 sam init 命令为新的无服务器应用程序生成此开箱即用的 Java 17 模板。请参阅 此处 的 亚马逊云科技 SAM 文档 。

亚马逊云科技 云开发套件 (亚马逊云科技 CDK)

亚马逊云科技 CDK 中 ,将运行时属性设置为 runtime.java_17 以使用此版本。在 Java 中:

import software.amazon.awscdk.core.Construct;
import software.amazon.awscdk.core.Stack;
import software.amazon.awscdk.core.StackProps;
import software.amazon.awscdk.services.lambda.Code;
import software.amazon.awscdk.services.lambda.Function;
import software.amazon.awscdk.services.lambda.Runtime;

public class InfrastructureStack extends Stack {

    public InfrastructureStack(final Construct parent, final String id, final StackProps props) {
        super(parent, id, props);

        Function.Builder.create(this, "HelloWorldFunction")
                .runtime(Runtime.JAVA_17)
                .code(Code.fromAsset("target/hello-world.jar"))
                .handler("helloworld.App::handleRequest")
                .memorySize(1024)
                .build();
    }
}

应用程序框架

Java 应用程序框架 Spring 和 Micronaut 已经宣布,他们的最新版本 Spring Boot 3 Micronaut 4 至少 需要 Java 17。 Quarkus 3 继续支持 Java 11。Java 17 比 8 或 11 快,框架开发人员希望将性能改进传递给客户。他们还希望在自己的代码中使用对 Java 语言的改进,并以最现代的工作方式展示代码示例。

要试用 Micronaut 4 和 Java 17,您可以使用 Micronaut 启动网络服务 生成一个示例项目,其中包含 将其部署到 Lambda 所需的全部应用程序代码和 亚马逊云科技 云开发套件 (CDK) 基础设施。

以下命令创建一个 Micronaut 应用程序,该应用程序使用常见的控制器模式来处理 REST 请求。基础设施代码将创建 亚马逊 API Gatew ay 并将其所有请求代理给 Lambda 函数。

curl --location --request GET 'https://launch.micronaut.io/create/default/blog.example.lambda-java-17?lang=JAVA&build=MAVEN&test=JUNIT&javaVersion=JDK_17&features=amazon-api-gateway&features=aws-cdk&features=crac' --output lambda-java-17.zip

解压缩下载的文件,然后运行以下 Maven 命令来生成可部署的工件。

./mvnw package

最后,使用 CDK 将资源部署到 亚马逊云科技:

cd infra
cdk deploy

结论

这篇博客文章描述了如何创建运行亚马逊 Corretto Java 17 托管运行时的新的 Lambda 函数。它引入了新的记录语言功能,用于对发送到您的 Lambda 函数的事件进行建模,并解释了默认 JVM 配置的更改会如何影响函数的性能。

如果你有兴趣了解更多信息,请访问 serverless land.com。如果这激发了您尝试将现有应用程序迁移到 Lambda 的灵感,请阅读我们的平台构指南。