当前位置:
首页
文章
后端
详情

使用 Java 的 AWS Lambda:快速且低成本的方法

AWS Lambda 是一个流行的无服务器开发平台,作为一名 Java 开发人员,我想使用它,但有一些问题需要解决。我将通过下面文章,和大家分享一下Java AWS Lambda开发平台的内容。

引入

AWS Lambda 是一个流行的无服务器开发平台,作为一名 Java 开发人员,我喜欢能够使用这个平台,但有一些要点需要首先解决。

  1. AWS Lambda 上的无服务器函数的成本对于 JVM 来说会很昂贵。
  2. AWS Lambda 上的冷启动在 JVM 上可能是一个真正的问题。
  3. 在 AWS Lambda 上为每个请求最大化效率可能代价高昂,并且在 JVM 中效率不高。

本文的两个主要目的如下:

  • 学习如何在无服务器平台(Lambda)上使用 AWS 服务,例如 Quarkus 框架的 DynamoDB。
  • 在 AWS Lambda 上获得最佳性能并降低成本。

演示应用程序

此存储库包含一个由 JDK 11 和 Quarkus 开发的 Java 应用程序示例,它是一个简单的 AWS Lambda 函数。这个简单的函数将接受一个 JSON 格式的水果名称(输入)并返回一种水果类型。

{
  "name": "apple"
}

水果的类型将是:

  • 春季水果
  • 夏季水果
  • 秋季水果
  • 冬季水果

演示应用程序


演示应用程序工作流程

这个演示是一个简单的 Java 应用程序,它获取请求的水果信息,提取水果的类型,并返回正确的水果类型。哪有那么简单?!

演示应用程序工作流程

创建基于 Quarkus 的 Java 应用程序

Quarkus 提供了扩展 AWS Lambda 项目的明确指南。可以使用 Maven 命令轻松访问此项目模板。

mvn archetype:generate \
 -DarchetypeGroupId=com.thinksky \
 -DarchetypeArtifactId=aws-lambda-handler-qaurkus \ 
 -DarchetypeVersion=2.1.3.Final

该命令将使用 AWS Java SDK 生成应用程序。

Quarkus 框架具有针对 DynamoDB、S3、SNS、SQS 等的扩展,我更喜欢使用提供非阻塞功能的 AWS Java SDK V2。因此,项目 pom.xml 文件需要按照本指南进行修改。

该项目具有 Lambda,它是 pom 文件中的一个依赖项。

<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-amazon-lambda</artifactId>
</dependency>

需要添加依赖项以使用 AWS DynamoDB 建立与 DynamoDB 的连接

<dependencies>
    <dependency>
        <groupId>io.quarkus</groupId>
        <artifactId>quarkus-amazon-dynamodb</artifactId>
    </dependency>
    <dependency>
        <groupId>io.quarkus</groupId>
        <artifactId>quarkus-apache-httpclient</artifactId>
    </dependency>
    <dependency>
        <groupId>software.amazon.awssdk</groupId>
        <artifactId>apache-client</artifactId>
        <exclusions>
            <exclusion>
                <artifactId>commons-logging</artifactId>
                <groupId>commons-logging</groupId>
            </exclusion>
        </exclusions>
    </dependency>
</dependencies>

我将在可以使用apache-client依赖项添加的应用程序的设置上使用 apache 客户端。

quarkus.dynamodb.sync-client.type=apache

使用 Quarkus 在 AWS Lambda 上开发 Java 应用程序的好处

常规的 AWS Lambda Java 项目将是一个普通的 Java 项目;然而,Quarkus 将在 Java 项目中引入依赖注入。

@ApplicationScoped
public class FruitService extends AbstractService {

    @Inject
    DynamoDbClient dynamoDB;

    public List<Fruit> findAll() {
        return              dynamoDB.scanPaginator(scanRequest()).items().stream()
                .map(Fruit::from)
                .collect(Collectors.toList());
    }

    public List<Fruit> add(Fruit fruit) {
        dynamoDB.putItem(putRequest(fruit));
        return findAll();
    }

}

DynamoDbClient是 AWS Java SDK.v2 中的一个类,Quarkus 将在其依赖注入生态系统中构建并提供该类。该FruitService是由叫做抽象类继承AbstractService,这抽象类提供的基本对象DynamoDbClient的需求,例如ScanRequest,PutItemRequest等等。

反射在 Java 框架中很流行,但这将是 GraalVM native-image 的新挑战。(但是 Quarkus 有一个简单的解决方案来应对这个挑战,那就是对 classes 的注释@RegisterForReflection。这不是在 GraalVM 中注册反射类的最简单方法吗?

@RegisterForReflection
public class Fruit {

    private String name;
    private Season type;

    public Fruit() {
    }

    public Fruit(String name, Season type) {
        this.name = name;
        this.type = type;
    }
}

还值得一提的是,Quarkus 在使用 AWS Lambda 平台的同时还提供了许多其他好处。我将在以后的一系列文章中描述它们。

在 AWS Lambda 上部署演示应用程序

现在是 AWS 上的部署时间,使用 Maven 和 Quarkus 框架的过程会相对简单。但是,在部署和运行应用程序之前,需要在 AWS 上做更多准备。部署过程包括以下步骤:

1) 在 DynamoDB 中定义 Fruits_TBL 表

$ aws dynamodb create-table --table-name Fruits_TBL \
   --attribute-definitions AttributeName=fruitName,AttributeType=S \ 
   AttributeName=fruitType,AttributeType=S \ 
   --key-schema AttributeName=fruitName,KeyType=HASH \               AttributeName=fruitType,KeyType=RANGE \ 
   --provisioned-throughput ReadCapacityUnits=1,WriteCapacityUnits=1

然后在桌子上插入一些 fruits。

$ aws dynamodb put-item --table-name Fruits_TBL \ 
      --item file://item.json \ 
      --return-consumed-capacity TOTAL \ 
      --return-item-collection-metrics SIZE

这是 item.json 的内容

{
  "fruitName": {
    "S": "Apple"
  },
  "fruitType": {
    "S": "Fall"
  }
}

最后,从 Dynamodb 运行查询以确保我们有项目。

$ aws dynamodb query \
     --table-name  Fruits_TBL \ 
     --key-condition-expression "fruitName = :name" \
     --expression-attribute-values '{":name":{"S":"Apple"}}'

2) 在 IAM 中定义一个角色以访问 DynamoBD 并将其分配给我们的 Lambda 应用程序。

$ aws iam create-role --role-name Fruits_service_role --assume-role-policy-document file://policy.json

policy.json

{
  "Version": "2012-10-17",
  "Statement": {
    "Effect": "Allow",
    "Principal": {
      "Service": [
        "dynamodb.amazonaws.com",
        "lambda.amazonaws.com"
      ]
    },
    "Action": "sts:AssumeRole"
  }
}

然后,将 DynamoDB 权限分配给该角色。

$ aws iam attach-role-policy --role-name Fruits_service_role -- policy-arn "arn:aws:iam::aws:policy/AmazonDynamoDBFullAccess"

然后这个。

$ aws iam attach-role-policy --role-name Fruits_service_role --policy-arn "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"

并且该角色可能还需要以下权限。

$ aws iam attach-role-policy --role-name Fruits_service_role --policy-arn "arn:aws:iam::aws:policy/AWSLambda_FullAccess"

最后,AWS 平台现已准备好托管我们的应用程序。

为了继续部署过程,我们需要构建我们的应用程序并修改生成的文章。

$  mvn clean install

Quarkus 框架将负责创建 JAR 工件文件、压缩该 JAR 文件并准备AWS的SAM 模板。这次应该使用JVM版本,修改方法如下:

1) 将定义的角色添加到 Lambda 以获得适当的访问权限

Role:arn:aws:iam::{Your-Account-Number-On-AWS}:role/fruits_service_role

2)增加超时时间

因此,SAM 模板现在已准备好部署在 AWS Lambda 上。

$ sam deploy -t target/sam.jvm.yaml -g

此命令会将 jar 文件以 zip 格式上传到 AWS 并将其部署为 Lambda 函数。下一步将是通过调用请求来测试应用程序

在 AWS Lambda + JVM 平台上观看演示应用程序的性能

是时候运行部署的 Lambda 函数、测试它并查看它的执行情况了。

$ aws lambda invoke response.txt --cli-binary-format raw-in-base64-out --function-name {:fruitApp} --payload file://payload.json --log-type Tail --query LogResult --output text | base64 --decode

我们可以使用以下命令找出 FUNCTION_NAME。

$ aws lambda list-functions --query 'Functions[?starts_with(FunctionName, `fruitAppJVM`) == `true`].FunctionName'

FruitAppJVM 是我在部署过程中给 SAM CLI 的 Lambda 的名称。

然后我们可以参考AWS的web控制台查看调用该函数的结果。

AWS 网络控制台

数字在说话,由于 AWS Lambda 的冷启动功能,这对于一个简单的应用程序来说是一个可怕的性能。

什么是 AWS Lambda 冷启动?

运行 Lambda 函数时,只要它被积极使用,它就会保持活动状态,这意味着您的容器保持活动状态并准备好执行。但是,AWS 将在一段时间不活动(通常很短)后丢弃容器,并且您的函数将变得不活动或冷。当请求到达空闲 lambda 函数时,会发生冷启动。之后,Lambda 函数将被初始化以能够响应请求。(Java 框架的初始化模式)。

另一方面,当有可用的 lambda 容器时会发生热启动。

冷启动是我们有这种糟糕性能的主要原因,因为每次冷启动发生时,AWS 都会初始化我们的 Java 应用程序,显然,每个请求都需要很长时间。

AWS Lambda 冷启动挑战的可用解决方案

有两种方法可以应对这一基本挑战。

  • 使用不属于本文范围的预配置并发。
  • 在应用程序的初始化和响应时间上获得更好的性能带来了如何在我们的 Java 应用程序中实现更好性能的问题。答案是从我们的 Java 应用程序创建一个本地二进制可执行文件,并使用Oracle GraalVM将其部署在 AWS Lambda 上。

GraalVM 是什么?

GraalVM 是一种高性能 JDK 发行版,旨在加速用 Java 和其他 JVM 语言编写的应用程序的执行,同时支持 JavaScript、Ruby、Python 和许多其他流行语言。Native-Image 是一种提前技术,可将 Java 代码编译为独立的可执行文件。此可执行文件包括应用程序类、其依赖项中的类、运行时库类以及来自 JDK 的静态链接本机代码。它不在 Java VM 上运行,但包括来自不同运行时系统(称为“Substrate VM”)的必要组件,如内存管理、线程调度等。

从 Java 应用程序构建本机二进制可执行文件

首先,我们需要安装 GraalVM 及其 Native-Image 。然后,通过安装 GraalVM,我们可以使用 GraalVM 将 Java 应用程序转换为原生二进制可执行文件。Quarkus 使它变得简单,它有一个 Maven/Gradle 插件,所以在一个典型的基于 Quarkus 的应用程序中,我们将有一个名为native.

$ mvn clean install -Pnative

Maven 将根据您使用的操作系统构建一个本地二进制可执行文件。如果你在 Windows 上开发,这个文件将只能在 Windows 机器上运行;但是,AWS Lambda 需要基于 Linux 的二进制可执行文件。在这种情况下,Quarkus 框架将通过其插件上的一个简单参数来满足此要求-Dquarkus.native.container-build=true。

$ mvn clean install -Pnative \
     -Dquarkus.native.container-build=true \
     -Dquarkus.native.builder-image=quay.io/quarkus/ubi-quarkus-native-image:21.2-java11

如上命令所示, using-Dquarkus.native.builder-image可以指定我们要使用的 GraalVm 版本。

AWS Lambda 环境

AWS Lambda 有几个不同的可部署环境。

╔═════════╦═══════════════════╦════════════════════╗
║ Runtime ║   Amazon Linux    ║   Amazon Linux 2   ║
╠═════════╬═══════════════════╬════════════════════╣
║ Node.js ║ nodejs12.x        ║ nodejs10.x         ║
║ Python  ║ python3.7 and 3.6 ║ python3.8          ║
║ Ruby    ║ ruby2.5           ║ ruby2.7            ║
║ Java    ║ java              ║ java11 , java8.al2 ║
║ Go      ║ go1.x             ║ provided.al2       ║
║ .NET    ║ dotnetcore2.1     ║ dotnetcore3.1      ║
║ Custom  ║ provided          ║ provided.al2       ║
╚═════════╩═══════════════════╩════════════════════╝

所以我们之前通过java11(Corretto 11)在Lambda上部署了Java Application,并没有表现出很好的性能。

对于 Lambda 上的纯 Linux 平台,我们目前有两个选项,它们是provided和provided.al2。

值得一提的是,provided会使用Amazon Linux,并且provided.al2会使用Amazon Linux 2,因此,由于版本2的长期支持,强烈推荐使用版本2。

在 AWS Lambda 上部署二进制可执行文件

正如我们所见,Quarkus 会为我们生成两个 sam 模板;一个用于 JVM 基础 Lambda,第二个是本机二进制可执行文件。这次我们应该使用原生的 sam 模板,它也需要一些小的修改。

1.更改为 AWS Linux V2

Runtime: provided.al2

2. 将定义的角色添加到 Lambda 以获得适当的访问权限。

Role: arn:aws:iam::{Your-Account-Number-On-AWS}:role/fruits_service_role

3.增加超时时间

Timeout: 30

原生 SAM 模板的最终版本将是这样的final.sam.native.yaml;它现在已准备好部署在 AWS 上。

$ sam deploy -t target/sam.native.yaml -g

此命令会将二进制文件作为 zip 格式上传到 AWS 并将其部署为 Lambda 函数,与 JVM 版本完全一样。现在,我们可以跳到令人兴奋的部分,即监控性能。

在 AWS Lambda + 自定义平台上观看演示应用程序的性能

是时候运行部署的 Lambda 函数、测试它并查看它的执行情况了。

$ aws lambda invoke response.txt --cli-binary-format raw-in-base64-out --function-name {:fruitApp} --payload file://payload.json --log-type Tail --query LogResult --output text | base64 --decode

我们可以使用以下命令找出 FUNCTION_NAME。

$ aws lambda list-functions --query 'Functions[?starts_with(FunctionName, `fruitAppNative`) == `true`].FunctionName'

FruitAppNative 是我在部署过程中给 SAM CLI 的 Lambda 的名称。

然后我们可以打开 AWS Web 控制台查看调用该函数的结果。

AWS 网络控制台

在 AWS Lambda 上分析 JVM 与原生二进制的性能

我们可以在两个类别中分析和比较 AWS Lambda 平台上应用程序的两个版本。

  • 初始化时间:第一次调用或调用 Lambda 函数所消耗的时间称为初始化时间。这几乎是在 Lambda 上调用应用程序的最长持续时间,因为在此阶段我们的 Java 应用程序将从头开始。
AWS Lambda 平台
  • JVM 和 Binary 版本之间存在相当大的差异,这意味着原生二进制版本的初始化时间几乎比 JVM 版本快八倍。
  • 请求时间:我在初始化步骤后调用了 9 次 Lambda 函数,这是性能结果。
AWS Lambda 平台

根据结果​​,JVM 版本和 Native 二进制文件之间的性能存在显着差异。

结论

Quarkus 框架将通过提供一些很好的特性,如依赖注入,帮助我们在 Java 应用程序上拥有清晰和结构化的代码。此外,它还有助于在 GraalVM 的帮助下将我们的 Java 应用程序转换为原生二进制文件。

与 JVM 版本相比,本机二进制版本具有明显更好的性能。

  • 二进制版本仅使用 128 MB 内存,而 JVM 版本使用 512 MB,从而在 AWS Lambda 上节省了大量资源。
  • 二进制版本提供比 JVM 版本更好的请求时间,这意味着在 AWS Lambda 上可以节省更多时间。

总的来说,通过节省资源和时间,原生二进制方法已被证明是一种低成本的选择。


免责申明:本站发布的内容(图片、视频和文字)以转载和分享为主,文章观点不代表本站立场,如涉及侵权请联系站长邮箱:xbc-online@qq.com进行反馈,一经查实,将立刻删除涉嫌侵权内容。

同类热门文章

深入了解C++中的new操作符:使用具体实例学习

C++中的new操作符是动态分配内存的主要手段之一。在程序运行时,我们可能需要动态地创建和销毁对象,而new就是为此提供了便利。但是,使用new也常常会引发一些问题,如内存泄漏、空指针等等。因此,本文将通过具体的示例,深入介绍C++中的new操作符,帮助读者更好地掌握其使用。


深入了解C++中的new操作符:使用具体实例学习

怎么用Java反射获取包下所有类? 详细代码实例操作

Java的反射机制就是在运行状态下,对于任何一个类,它能知道这个类的所有属性和方法;对于任何一个对象,都能调用这个对象的任意一个方法。本篇文章将通过具体的代码示例,展示如何通过Java反射来获取包下的所有类。


怎么用Java反射获取包下所有类? 详细代码实例操作

员工线上学习考试系统

有点播,直播,在线支付,三级分销等功能,可以对学员学习情况的监督监控,有源码,可二次开发。支持外网和局域网私有化部署,经过测试源码完整可用!1、视频点播:视频播放,图文资料,课件下载,章节试学,限时免

员工线上学习考试系统

了解Java中的volati关键字的作用 以及具体使用方法

本篇文章将和大家分享一下Java当中的volatile关键字,下面将为各位小伙伴讲述volatile关键字的作用以及它的具体使用方法。


了解Java中的volati关键字的作用 以及具体使用方法

Java Map 所有的值转为String类型

可以使用 Java 8 中的 Map.replaceAll() 方法将所有的值转为 String 类型: 上面的代码会将 map 中所有的值都转为 String 类型。 HashMap 是 Java

Java Map 所有的值转为String类型