【squids.cn】 全网zui低价RDS,免费的迁移工具DBMotion、数据库备份工具DBTwin、SQL开发工具等
正如我们之前所看到的,以JVM模式运行我们的微服务意味着将它们打包并作为可执行JARs运行。但是Quarkus也允许我们将它们编译成机器码并作为原生进程运行。这具有显著提高应用程序启动时间和内存使用的优点。因此,原生模式是Quarkus应用在生产中的首选执行模式。
关于原生模式的所有原因和结果都在Quarkus的文档中以非常清晰和详细的方式解释。简而言之,为了将Java代码编译成机器码,需要一个所谓的“C原生编译环境”,以及GraalVM发行版。然而,考虑到GraalVM的安装和配置过程的相对复杂性,Quarkus能够为我们避免这个问题,并提供了在不必安装和配置GraalVM的情况下创建Linux可执行代码的可能性。这是我们在这里将采用的方法。
遗憾的是,将Java代码编译成机器码并不是一件容易的事情,而且,在这里的原型的情况下,这个挑战比预期的更为重要。事实上,为了实现这一点,我花了比原计划更多的时间,还需要在Quarkus的GitHub论坛上注册一些问题。原因是,一个在JVM模式下运行的Java应用程序在原生模式下运行之前需要一些重构。
我们为了让Java应用程序在原生模式下运行需要进行的第一个也是最重要的修改是将属性quarkus.package.type设置为native。有几种方法可以做到这一点,但在我们的情况下,我们在项目的Maven主POM中这样做,如下所示:
...
native
此属性仅对项目的Quarkus模块有效,即使用quarkus-maven-plugin的模块。在我们的例子中,这些模块是我们的微服务;即aws-camelk-file,aws-camelk-s3,aws-kamelk-jaxrs和aws-kamelk-sqs。对aws-camelk-model,aws-camelk-api和aws-camelk-provider模块不会有任何影响。
既然我们在谈论属性,我们还需要提及名为quarkus.native.resources.includes的属性,当我们想声明要从类路径加载的额外资源时,这是必需的。默认情况下,GraalVM不会包括类路径中的任何资源。至于Quarkus,它只会包括META-INF/resources。所有其他资源都需要通过上述属性进行声明。
例如,aws-camelk-file微服务正在轮询输入目录以查找XML文件,一旦这样的文件落在里面,它会尝试根据存储在src/main/resources/xsd文件夹中的相关模式进行验证。如果我们尝试编译并执行我们的微服务,它会在尝试访问文件money-transfers.xsd和money-transfer.xsd时引发FileNotFoundException。要解决此问题,我们需要将以下属性添加到此模块的application.properties文件中。
...
quarkus.native.resources.includes=xsd/*.xsd
...
这样,所有在xsd子目录中找到的xsd类型的资源都将被添加到原生可执行文件中。
另一个重要的点,它与Quarkus原生模式总体上无关,而与我们的原型的特点有关,是为了处理S3桶和SQS队列和消息而使用的AWS SDK。此SDK通过以下Maven工件进行配置:
...
com.amazonaws
aws-java-sdk-bom
1.12.454
pom
import
...
...
com.amazonaws
aws-java-sdk-s3
com.amazonaws
aws-java-sdk-sqs
...
尝试在这些依赖关系下以原生模式运行我们的微服务会引发一系列奇怪的异常。原因是构成AWS SDK的库没有被“quarkify”,可以这么说。这确切地意味着,为了构建原生GraalVM应用程序,Quarkus需要执行一个名为构建时增强的过程,为了执行这个过程,Quarkus需要扩展。一个Quarkus扩展是一个库,它具有在构建时处理元数据(如注解或XML描述符)的能力。因此,只有当所有应用程序的库和其他工件都被“quarkify”(即,有关联的Quarkus扩展)时,才可能在原生模式下运行。
这是这里的问题,因为我们在这里使用的AWS SDK没有关联的Quarkus扩展。这在JVM模式下不是一个问题,其中上述的所有增强都是在运行时而不是在构建时完成的,因此,它们不需要特定的“quarkified”版本的库。
那么,当我们需要在原生模式下运行的Quarkus应用程序中使用给定的库,而恰好没有该库的Quarkus扩展时,解决方案是什么呢?嗯,唯一的解决方案就是找到另一个库,等待前者的编辑器提供一个Quarkus扩展。
好消息是,在我们的情况下,确实存在这样另一个库,叫做Quarkiverse。因此,为了使用Quarkiverse而不是AWS SDK,我们需要用以下内容替换上面显示的Maven依赖项:
...
...
io.quarkus.platform
quarkus-amazon-services-bom
${quarkus.platform.version}
pom
import
...
...
...
software.amazon.awssdk
url-connection-client
io.quarkiverse.amazonservices
quarkus-amazon-s3
io.quarkiverse.amazonservices
quarkus-amazon-sqs
...
一旦这些修改完成,我们的代码将无法再编译,我们需要转换使用AWS SDK的语句,使它们使用Quarkiverse。例如,在aws-camelk-file模块中,为了获取当前AWS账户中的S3桶列表,我们使用以下代码序列:
...
private static AmazonS3 amazonS3Client;
private static List buckets;
@BeforeAll
public static void beforeAll()
{
amazonS3Client = AmazonS3ClientBuilder.standard().build();
buckets = amazonS3Client.listBuckets();
}
...
为了使用Quarkiverse实现同样的功能,我们需要按照以下方式修改上面的代码:
...
@Inject
S3Client s3client;
private static List buckets;
...
@BeforeAll
public void beforeAll()
{
buckets = s3client.listBuckets(ListBucketsRequest.builder().build()).buckets();
}
...
正如你所看到的,Quarkiverse是一个更现代的库,支持CDI并允许我们简单地注入S3客户端。它还支持构建者设计模式,如上面的代码片段所示。
这些修改必须在整个应用程序中进行,在使用AWS SDK的所有微服务中都要进行。有时,这可能会令人厌烦并且耗时。在JVM和原生模式下,Quarkiverse库都按预期工作。最好从一开始就使用它,而不是使用AWS SDK。如果我没有这样做,那是因为AWS SDK比Quarkiverse更为人所知,而我只是在尝试修复上述问题的过程中发现了Quarkiverse。
另一个重要的点是,正如你所看到的,Quarkiverse使用的是客户端的概念。有一个S3客户端,一个SQS客户端,等等。这些客户端是一种HTTP客户端,并且有两种:老牌的Apache HTTP客户端和一个较新的,被称为“URL客户端”。为了配置要使用哪一个HTTP客户端,我们需要设置属性quarkus-amazon-s3_quarkus.s3.sync-client.type,它接受以下值之一:apache和url,最后一个是默认值。在我们的原型中,我们使用的是URL HTTP客户端,并且由于这是默认值,我们不需要初始化这个属性。但是,我们需要包含以下依赖:
...
software.amazon.awssdk
url-connection-client
...
在某些情况下,我遇到了一些意想不到的问题,但我只能绕过它,正如我在提交给Quarkiverse论坛的这个工单中所记录的。在这里,注入S3客户端导致了NPE,并且HTTP客户端的默认值似乎没有被接受。因此,我得出了一些代码,例如:
@Inject
S3Client s3client;
...
s3client = S3Client.builder().httpClient(UrlConnectionHttpClient.builder().build()).build();
...
我原以为注入S3客户端应该足够了,但事实并非如此。为了绕过上述的异常,我需要在注入S3客户端后手动初始化它(只手动初始化而不注入是不够的)。
单元测试和集成测试是另一个关键点,在JVM和原生模式之间你可能会期望有一些差异。你可能已经注意到,我们的单元测试并不像真正的单元测试那样,因为它们使用了AWS,因此它们更像是集成测试。然而,它们使用了@QuarkusTest注解,这使它们成为了普通的单元测试。原因是,使用@QuarkusIntegrationTest注解(正如我们应该的那样)不允许我们使用CDI和注入,这会相当受限。
在JVM模式下,在单元测试中使用AWS不是问题,但在原生模式下,使用AWS服务的单元测试是通过LocalStack完成的。这可能会改变我们单元测试的结果,因为我们不是在AWS帐户中访问S3桶和SQS队列,而是在本地环境中访问它们,这可能会改变一切。
理想情况下,应该有一种方法可以在AWS上使用AWS服务执行Quarkus单元测试,而不是localstack。在找到这样做的方法之前,我禁用了一些在localstack上不如预期运行的测试。
总之,正如你所看到的,当从JVM模式切换到原生模式时,有很多事情需要注意。我们的原型有一个专门的native分支,如果想尝试它,请按以下步骤操作:
$ git clone https://github.com/nicolasduminil/aws-camelk
$ cd aws-camelk
$ git checkout native
$ ./delete-all-buckets.sh
$ ./create-sqs-queue.sh
$ mvn clean install
$ ./start-ms.sh
$ ...
$ ./kill-ms.sh
如果你发现构建过程比之前在JVM模式下慢得多,请不要惊讶。这是正常的,因为正如之前解释的,所有的增强和其他操作现在都在构建时完成。请耐心等待,过一会儿,构建过程应该会成功完成。
作者:Nicolas Duminil
更多内容请关注公号【云原生数据库】
squids.cn,云数据库RDS,迁移工具DBMotion,云备份DBTwin等数据库生态工具。