K8s上应该用哪个JDK?8种JDK性能测试

程序员咋不秃头 2024-09-01 01:23:31

这次我将通过多次重复进行非常准确的比较以获得可重现的结果。我将测试以下 JVM 实现:

Adoptium Eclipse TemurinAlibaba DragonwellAmazon CorrettoAzul ZuluBellSoft LibericaIBM Semeru OpenJ9Oracle JDKMicrosoft 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.1Docker 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/v1kind: Deploymentmetadata: name: sample-spring-boot-on-kubernetes-deploymentspec: 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@RestController@RequestMapping("/persons")public PersonController { private PersonRepository repository; PersonController(PersonRepository repository) { this.repository = repository; } @PostMapping public Person add(@RequestBody Person person) { return repository.save(person); } // other endpoints implementation}// repositorypublic 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: 271MBIBM Semeru OpenJ9: 275MBEclipse Temurin: 286MBBellSoft Liberica: 286MBOracle OpenJDK: 446MBAlibaba Dragonwell: 459MBMicrosoft OpenJDK: 461MBAmazon Corretto: 463MB

让我们想象一下第一个结果。我认为它很好地显示了哪个镜像包含 JDK 和哪个 JRE。

启动时间

老实说,测量启动时间不是很容易,因为供应商之间的差异并不大。此外,同一提供商的后续结果可能会有很大差异。例如,在第一次尝试时,应用程序在 5.8 秒后启动,而在 pod 重启后 8.4 秒。我的方法很简单。我为每个 JDK 提供程序重启了几次应用程序,以测量平均启动时间和系列中最快的启动时间。然后我再次重复相同的练习以验证结果是否可重复。相应厂商之间的第一系列和第二系列启动时间的比例相似。事实上,最快和最慢的平均启动时间之间的差别并不大。我得到的最佳结果是:Eclipse Temurin (7.2s) 和最差结果是:IBM Semeru OpenJ9 (9.05s) 。

让我们看看完整的结果列表。它显示应用程序从最快的开始的平均启动时间。

Eclipse Temurin: 7.20sOracle OpenJDK: 7.22sAmazon Corretto: 7.27sBellSoft Liberica: 7.44sOracle OpenJDK: 7.77sAlibaba Dragonwell: 8.03sMicrosoft OpenJDK: 8.18sIBM Semeru OpenJ9: 9.05s

这是我们结果的图形表示。供应商之间的差异有时是表面上的。也许,如果同样的测试从头再来一次,结果会大不相同。

正如我之前提到的,我还测量了最快的尝试。这次最好的前 3 名是 Eclipse Temurin、Amazon Corretto 和 BellSoft Liberica。

Eclipse Temurin: 5.6sAmazon Corretto: 5.95sBellSoft Liberica: 6.05sOracle OpenJDK: 6.1sAzul Zulu: 6.2sAlibaba Dragonwell: 6.45sMicrosoft OpenJDK: 6.9sIBM Semero OpenJ9: 7.85s内存

我正在通过模拟 10 个用户连续发送请求的测试来测量应用程序在重负载下的内存使用情况。它在应用程序级别为我提供了非常大的吞吐量:每秒大约 500 个请求。结果符合预期。除了使用 OpenJ9 JVM 的 IBM Semeru 之外,几乎所有供应商的内存使用情况都非常相似。理论上,OpenJ9 也应该有更好的启动时间。但是,就我而言,显着差异仅在于内存占用量。IBM Semeru 的内存使用量约为 135MB,而其他供应商的内存使用量在 210-230MB 范围内变化。

IBM Semero OpenJ9: 135MOracle OpenJDK: 211MAzul Zulu: 215MAlibaba DragonwellOracle OpenJDK: 216MBellSoft Liberica: 219MMicrosoft OpenJDK: 219MAmazon Corretto: 220MEclipse 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/sAmazon Corretto: 433req/sIBM Semeru OpenJ9: 432req/sOracle OpenJDK: 420req/sMicrosoft OpenJDK: 418req/sAzul Zulu: 414req/sEclipse Temurin: 407req/sAlibaba Dragonwell: 405req/s

以下是 10 个虚拟用户的吞吐量结果:

Eclipse Temurin: 580req/sAzul Zulu: 567req/sMicrosoft OpenJDK: 561req/sOracle OpenJDK: 561req/sIBM Semeru OpenJ9: 552req/sAmazon Corretto: 552req/sAlibaba Dragonwell: 551req/sBellSoft Liberica: 540req/s总结

在多次重复负载测试后,我必须承认所有 JDK 供应商之间的性能没有显着差异。可能我运行的测试越多,不同供应商之间的结果就会更加相似。

1 阅读:55