这次我将通过多次重复进行非常准确的比较以获得可重现的结果。我将测试以下 JVM 实现:
Adoptium Eclipse Temurin
Alibaba Dragonwell
Amazon Corretto
Azul Zulu
BellSoft Liberica
IBM Semeru OpenJ9
Oracle JDK
Microsoft OpenJDK
对于所有测试,我将使用 Paketo Java buildpack。我们可以使用 Paketo 轻松地在多个 JVM 实现之间切换。我将测试一个简单的 Spring Boot 3 应用程序,它使用 Spring Data 与 Mongo 数据库进行交互。
如果您已经使用 Dockerfile 构建了镜像,那么您可能使用的是来自 Docker Hub 的官方 OpenJDK 基础镜像。然而,目前镜像网站上的公告称它已被正式弃用,所有用户都应该找到合适的替代品。
https://hub.docker.com/_/openjdk
在本文中,我们将比较所有最受欢迎的替代品,希望它能帮助您做出一个好的选择!
测试环境
在我们运行测试之前,拥有一个预配置的环境很重要。我将在本地运行所有测试。为了构建镜像,我将使用 Paketo Buildpacks。以下是我的环境的一些细节:
机器:MacBook Pro 32G RAM 英特尔
操作系统:macOS Ventura 13.1
Docker Desktop 上的 Kubernetes (v1.25.2):14G RAM + 4vCPU
我们将使用 Java 17 进行应用程序编译。为了运行负载测试,我将利用 k6 工具。https://k6.io/
我们的应用程序是用 Spring Boot 编写的。它连接到运行在同一 Kubernetes 实例上的 Mongo 数据库。每次我测试一个新的 JVM 提供程序时,我都会删除以前版本的应用程序和数据库。然后我再次部署新的完整配置。我们将测试以下参数:
应用程序启动时间(最佳结果和平均值):我们将直接从 Spring Boot 日志中读取它
吞吐量:使用 k6,我们将模拟 5 和 10 个虚拟用户。它将测量处理请求的数量
镜像大小
Pod 在负载测试期间消耗的 RAM 内存。基本上,我们将执行 kubectl top pod 命令
我们还将容器的内存限制设置为 1G。在我们的负载测试中,应用程序会将数据插入 Mongo 数据库。它公开了在测试期间调用的 REST 端点。为了尽可能准确地测量启动时间,我将多次重启该应用程序。
让我们看一下 Deployment YAML 清单。它向 Mongo 数据库注入凭据并将内存限制设置为 1G:
apiVersion: apps/v1
kind: Deployment
metadata:
name: sample-spring-boot-on-kubernetes-deployment
spec:
selector:
matchLabels:
app: sample-spring-boot-on-kubernetes
template:
metadata:
labels:
app: sample-spring-boot-on-kubernetes
spec:
containers:
name: sample-spring-boot-on-kubernetes
image: piomin/sample-spring-boot-on-kubernetes
ports:
containerPort: 8080
env:
name: MONGO_DATABASE
valueFrom:
configMapKeyRef:
name: mongodb
key: database-name
name: MONGO_USERNAME
valueFrom:
secretKeyRef:
name: mongodb
key: database-user
name: MONGO_PASSWORD
valueFrom:
secretKeyRef:
name: mongodb
key: database-password
name: MONGO_URL
value: mongodb
readinessProbe:
httpGet:
port: 8080
path: /readiness
scheme: HTTP
timeoutSeconds: 1
periodSeconds: 10
successThreshold: 1
failureThreshold: 3
resources:
limits:
memory: 1024Mi
源代码和镜像
如果您想自己尝试,可以随时查看我的源代码。为此,您需要克隆我的 GitHub 存储库。您还可以在我的 Docker Hub 存储库中找到所有镜像piomin/sample-spring-boot-on-kubernetes。
Spring Boot 应用程序公开了几个端点,但我将测试POST /persons用于将数据插入 Mongo 的端点。在与 Mongo 的集成中,我使用了 Spring Data MongoDB 项目及其 CRUD 存储库模式。
// controller
"/persons") (
public class PersonController {
private PersonRepository repository;
PersonController(PersonRepository repository) {
this.repository = repository;
}
public Person add(@RequestBody Person person) {
return repository.save(person);
}
// other endpoints implementation
}
// repository
public interface PersonRepository extends CrudRepository<Person, String> {
Set<Person> findByFirstNameAndLastName(String firstName,
String lastName);
Set<Person> findByAge(int age);
Set<Person> findByAgeGreaterThan(int age);
}
镜像的大小
镜像的大小是最简单的测量选项。如果您想检查镜像中到底有什么,您可以使用dive工具。不同 JVM 实现之间的大小差异是由内部包含的 Java 工具和二进制文件的数量造成的。从我的角度来看,尺寸越小越好。我宁愿不使用镜像内部的任何东西,只要能成功运行应用程序。无论如何,这是对镜像:piomin/sample-spring-boot-on-kubernetes:oracle 执行 dive 命令后 Oracle JDK 应用程序的内容。如您所见,JDK 占据了大部分空间。
另一方面,我们可以分析最小的镜像。我认为这解释了镜像大小的差异,因为 Zulu 包含 JRE,而不是整个 JDK。
这是从最小镜像到最大镜像排序的结果。
Azul Zulu: 271MB
IBM Semeru OpenJ9: 275MB
Eclipse Temurin: 286MB
BellSoft Liberica: 286MB
Oracle OpenJDK: 446MB
Alibaba Dragonwell: 459MB
Microsoft OpenJDK: 461MB
Amazon Corretto: 463MB
让我们想象一下第一个结果。我认为它很好地显示了哪个镜像包含 JDK 和哪个 JRE。
启动时间
老实说,测量启动时间不是很容易,因为供应商之间的差异并不大。此外,同一提供商的后续结果可能会有很大差异。例如,在第一次尝试时,应用程序在 5.8 秒后启动,而在 pod 重启后 8.4 秒。我的方法很简单。我为每个 JDK 提供程序重启了几次应用程序,以测量平均启动时间和系列中最快的启动时间。然后我再次重复相同的练习以验证结果是否可重复。相应厂商之间的第一系列和第二系列启动时间的比例相似。事实上,最快和最慢的平均启动时间之间的差别并不大。我得到的最佳结果是:Eclipse Temurin (7.2s) 和最差结果是:IBM Semeru OpenJ9 (9.05s) 。
让我们看看完整的结果列表。它显示应用程序从最快的开始的平均启动时间。
Eclipse Temurin: 7.20s
Oracle OpenJDK: 7.22s
Amazon Corretto: 7.27s
BellSoft Liberica: 7.44s
Oracle OpenJDK: 7.77s
Alibaba Dragonwell: 8.03s
Microsoft OpenJDK: 8.18s
IBM Semeru OpenJ9: 9.05s
这是我们结果的图形表示。供应商之间的差异有时是表面上的。也许,如果同样的测试从头再来一次,结果会大不相同。
正如我之前提到的,我还测量了最快的尝试。这次最好的前 3 名是 Eclipse Temurin、Amazon Corretto 和 BellSoft Liberica。
Eclipse Temurin: 5.6s
Amazon Corretto: 5.95s
BellSoft Liberica: 6.05s
Oracle OpenJDK: 6.1s
Azul Zulu: 6.2s
Alibaba Dragonwell: 6.45s
Microsoft OpenJDK: 6.9s
IBM Semero OpenJ9: 7.85s
内存
我正在通过模拟 10 个用户连续发送请求的测试来测量应用程序在重负载下的内存使用情况。它在应用程序级别为我提供了非常大的吞吐量:每秒大约 500 个请求。结果符合预期。除了使用 OpenJ9 JVM 的 IBM Semeru 之外,几乎所有供应商的内存使用情况都非常相似。理论上,OpenJ9 也应该有更好的启动时间。但是,就我而言,显着差异仅在于内存占用量。IBM Semeru 的内存使用量约为 135MB,而其他供应商的内存使用量在 210-230MB 范围内变化。
IBM Semero OpenJ9: 135M
Oracle OpenJDK: 211M
Azul Zulu: 215M
Alibaba DragonwellOracle OpenJDK: 216M
BellSoft Liberica: 219M
Microsoft OpenJDK: 219M
Amazon Corretto: 220M
Eclipse Temurin: 230M
这是我们结果的图形可视化:
吞吐量
为了为应用程序产生大量传入流量,我使用了k6工具。它允许我们用 JavaScript 创建测试。下面测试的实现。它使用POST /persons JSON 格式的输入数据调用 HTTP 端点。然后它验证请求是否已在服务器端成功处理。
import http from 'k6/http';
import { check } from 'k6';
export default function () {
const payload = JSON.stringify({
firstName: 'aaa',
lastName: 'bbb',
age: 50,
gender: 'MALE'
});
const params = {
headers: {
'Content-Type': 'application/json',
},
};
const res = http.post(`http://localhost:8080/persons`, payload, params);
check(res, {
'is status 200': (res) => res.status === 200,
'body size is > 0': (r) => r.body.length > 0,
});
}
这是k6运行测试的命令。可以定义并发虚拟用户的持续时间和数量。第一步,我模拟 5 个虚拟用户:
k6 run -d 90s -u 5 load-tests.js
然后,我对每个供应商的 10 个虚拟用户运行两次测试。
k6 run -d 90s -u 10 load-tests.js
以下是执行k6测试后打印的示例结果:
我根据 JDK 供应商重复了这个练习。以下是 5 个虚拟用户的吞吐量结果:
BellSoft Liberica: 451req/s
Amazon Corretto: 433req/s
IBM Semeru OpenJ9: 432req/s
Oracle OpenJDK: 420req/s
Microsoft OpenJDK: 418req/s
Azul Zulu: 414req/s
Eclipse Temurin: 407req/s
Alibaba Dragonwell: 405req/s
以下是 10 个虚拟用户的吞吐量结果:
Eclipse Temurin: 580req/s
Azul Zulu: 567req/s
Microsoft OpenJDK: 561req/s
Oracle OpenJDK: 561req/s
IBM Semeru OpenJ9: 552req/s
Amazon Corretto: 552req/s
Alibaba Dragonwell: 551req/s
BellSoft Liberica: 540req/s
总结
在多次重复负载测试后,我必须承认所有 JDK 供应商之间的性能没有显着差异。可能我运行的测试越多,不同供应商之间的结果就会更加相似。
如果字段的最大可能长度超过255字节,那么长度值可能…
只能说作者太用心了,优秀
感谢详解
一般干个7-8年(即30岁左右),能做到年入40w-50w;有…
230721