在容器时代(“Docker时代”),无论如何,Java仍然活着。Java在性能方面一直很有名,主要是因为代码和真实机器之间的抽象层,多平台的成本(一次编写,随处运行——还记得吗?),中间有一个JVM(JVM:模拟真实机器的软件机器)。
如今,有了微服务架构,也许它不再有意义,也没有任何优势,为总是在同一地方和平台上运行的东西(Docker 容器 — Linux 环境)构建多平台(解释)的东西。可移植性现在不那么重要了(也许比以往任何时候都更重要),那些额外的抽象级别并不重要。
话虽如此,让我们对在 Java 中生成微服务的两种替代方案进行简单而原始的比较:非常著名的 Spring Boot 和不太知名的 Quarkus。
对手
Quarkus
一套适用于GraalVM和HotSpot的开源技术,用于编写Java应用程序。它提供(承诺)超快的启动时间和更低的内存占用。这使其成为容器和无服务器工作负载的理想选择。它使用 Eclipse Microprofile(JAX-RS、CDI、JSON-P)来构建微服务,这是 Java EE 的一个子集。
GraalVM 是一个通用的多语言虚拟机(JavaScript、Python、Ruby、R、Java、Scala、Kotlin)。 GraalVM(特别是 Substrate VM)使提前 (AOT) 编译成为可能,将字节码转换为本机机器码,从而生成可以在本机执行的二进制文件。
请记住,并非每个功能在本机执行中都可用,AOT 编译有其局限性。注意这句话(引用GraalVM团队的话):
我们运行一个积极的静态分析,它需要一个封闭世界的假设,这意味着在运行时可以访问的所有类和所有字节码都必须在构建时是已知的。
因此,例如,反射和 Java 本机接口 (JNI) 将不起作用,至少是开箱即用的(需要一些额外的工作)。您可以在此处找到限制列表本机映像 Java 限制文档。
Spring Boot
不用多介绍了,用一句话说(随意跳过它):Spring Boot 建立在 Spring Framework 之上,是一个开源框架,它提供了一种更简单的方法来构建、配置和运行基于 Java Web 的应用程序。使其成为微服务的良好候选者。
战斗准备 — 创建 Docker 镜像
Quarkus 镜像
让我们创建 Quarkus 应用程序,稍后将其包装在 Docker 映像中。基本上,我们将执行与Quarkus入门教程相同的操作。
使用 Quarkus maven 原型创建项目:
mvn io.quarkus:quarkus-maven-plugin:1.0.0.CR2:create \
-DprojectGroupId=ujr.combat.quarkus \
-DprojectArtifactId=quarkus-echo \
-DclassName="ujr.combat.quarkus.EchoResource" \
-Dpath="/echo"
这将项目的结构,如下所示:
请注意,还创建了两个示例 Dockerfile (src/main/docker):一个用于普通 JVM 应用程序映像,另一个用于本机应用程序映像。
在生成的代码中,我们只需要更改一件事,添加下面的依赖项,因为我们想要生成 JSON 内容。
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-jsonb</artifactId>
</dependency>
Quarkus 在整个 RESTEasy 项目实现中使用 JAX-RS 规范。
这是我们的“完整”应用程序:
仅此而已,通过下一个命令,我们可以看到应用程序正在运行:
mvn clean compile quarkus:dev
在这种模式下,我们还启用了热部署,并带有后台编译。让我们做一个简单的测试来看看它:
curl -sw "\n\n" http://localhost:8080/echo/ualter | jq .
现在我们已经看到它正在工作,让我们创建 Docker 映像。从此处下载 GraalVM:Releases · graalvm/graalvm-ce-builds · GitHub。
重要! 不要下载最新版本 19.3.0, Quarkus 1.0 它与它不兼容,也许 Quarkus 1.1 会。现在应该可以运行的版本是 GraalVM 19.2.1,获取这个版本。
配置其环境变量主路径:
## At macOS will be: export
GRAALVM_HOME=/Users/ualter/Developer/quarkus/graalvm-ce-java8-19.2.1/Contents/Home/
然后在您的环境中安装 GraalVM 的本机映像:
$GRAALVM_HOME/bin/gu install native-image
让我们为当前平台生成本机版本(在本例中,将为 macOS 生成本机可执行文件)。
mvn package -Pnative
如果一切正常,我们可以在 ./target 文件夹中找到一个名为的文件。这是应用程序的可执行二进制文件,您可以运行以下命令启动它:.无需使用 JVM(普通:java -cp app:lib/*:etc App.jar),它是一个本机可执行二进制文件。quarkus-echo-1.0-SNAPSHOT-runner
./target/quarkus-echo-1.0-SNAPSHOT-runner
让我们为应用程序生成一个原生 Docker 映像。此命令将创建一个本机映像,即具有 Linux 本机可执行应用程序的 Docker 映像。默认情况下,本机可执行文件是基于当前平台(macOS)创建的,因为我们知道这个生成的可执行文件与容器(Linux)的平台不同,我们将指示Maven构建从容器内部生成可执行文件,生成本机docker映像:
mvn package -Pnative -Dquarkus.native.container-build=true
此时,请确保有一个 Docker 容器运行时,一个工作环境。
该文件将是一个 64 位 Linux 可执行文件,因此自然而然地,这个二进制文件不适用于我们的 macOS,它是为我们的 docker 容器映像构建的。所以,向前迈进......让我们来生成 Docker 镜像......
docker build -t ujr/quarkus-echo -f src/main/docker/Dockerfile.native .
## Testing it...
docker run -i --name quarkus-echo --rm -p 8081:8081 ujr/quarkus-echo
关于 Docker 镜像大小的旁注:
最终的 docker 镜像是 115MB,但您可以使用无发行版的镜像版本来拥有一个很小的 Docker 镜像。无发行版映像仅包含您的应用程序及其运行时依赖项,其他所有内容(包管理器、shell 或标准 Linux 发行版中常见的普通程序)都将被删除。应用程序的 Distroless 映像大小为 42.3MB。该文件具有生成它的收据。./src/main/docker/Dockerfile.native-distroless
关于Distroless Images:“ 将运行时容器中的内容限制为应用程序所需的内容是 Google 和其他在生产中使用容器多年的科技巨头采用的最佳实践"
Spring Boot 镜像
至此,大概大家都知道如何制作一个普通的Spring Boot Docker镜像了,我们先跳过细节吧?只有一个重要的观察结果,代码是完全相同的。几乎是一样的,因为我们当然使用的是 Spring 框架注解。这是唯一的区别。您可以在提供的源代码中查看每个细节。
mvn install dockerfile:build
## Testing it...
docker run --name springboot-echo --rm -p 8082:8082 ujr/springboot-echo
战斗
让我们启动这两个容器,让它们启动并运行几次,并比较启动时间和内存占用。
在这个过程中,每个容器都被创建和销毁了 10 次。后来,分析了他们的启动时间和内存占用。下面显示的数字是基于所有这些测试的平均结果。
启动时间
显然,当与可扩展性和无服务器架构相关时,这方面可能会发挥重要作用。
关于 Serverless 架构,在此模型中,通常由事件触发临时容器来执行任务/功能。在云环境中,价格通常基于执行次数,而不是以前购买的一些计算容量。因此,这里的冷启动可能会影响这种类型的解决方案,因为容器(通常)只会在执行其任务的时间内处于活动状态。
在可伸缩性中,很明显,如果需要突然横向扩展,启动时间将定义容器完全准备就绪(启动并运行)以响应所呈现的加载方案所需的时间。
这种情况有多突然(需要和快速),更糟糕的情况可能是长时间的冷启动。
让我们看看它们在启动时间方面的表现:
好吧,您可能已经注意到,这是在“启动时间”图中插入的又一个经过测试的选项。实际上,它与Quarkus 应用程序完全相同,但使用 JVM Docker 映像(使用Dockerfile.jvm)生成。正如我们所看到的,即使是使用带有 JVM 的 Docker 映像的应用程序,Quarkus 应用程序的启动时间也比 Spring Boot 更快。
毋庸置疑,Quarkus Native 应用程序显然是赢家,它是迄今为止启动速度最快的应用程序。
内存占用
现在,让我们检查一下内存的情况。检查每个容器应用程序在启动时需要消耗多少内存,以启动并运行,并准备好接收请求。
结论
总而言之,这就是我们在 Linux Ubuntu 中查看的结果:
Quarkus 似乎赢得了这两轮战斗(启动时间和内存足迹),以一些明显的优势战胜了他的对手 SpringBoot。
这可能会让我们想知道......也许是时候考虑一些真实的实验、经验和一些 Quarkus 的尝试了。我们应该看看它在真实世界中的表现如何,它如何适应我们的业务场景,以及在什么方面最有用。
但是,我们不要忘记缺点,正如我们在上面看到的,JVM的某些功能在本机可执行二进制文件中无法(还/容易)工作。无论如何,也许是时候给 Quarkus 一个证明自己的机会了,特别是如果冷启动的问题一直困扰着你。在环境中使用一两个由 Quarkus 驱动的 Pod (K8s) 怎么样,看看一段时间后它的表现会很有意思,不是吗?