plugins {
java
application
id("com.github.johnrengelman.shadow") version "8.1.1"
}
repositories {
mavenCentral()
}
dependencies {
val vertxVersion = "5.0.0.CR2"
implementation("io.vertx:vertx-web:${vertxVersion}")
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(21)
}
}
application {
mainClass = "io.vertx.howtos.openj9.Main"
}
使用 Eclipse OpenJ9 运行 Eclipse Vert.x 应用程序
本操作指南提供了一些使用 OpenJ9 运行 Vert.x 应用程序的技巧。OpenJ9 是一个基于 OpenJDK 构建的替代 Java 虚拟机,对内存使用非常友好。
Vert.x 是一个用于构建各种现代分布式应用程序的资源高效型工具包,而 OpenJ9 则是一个资源高效型运行时,非常适合虚拟化和容器化部署。
您将构建和运行什么
-
您将构建一个简单的微服务,通过 HTTP JSON 端点计算 2 个数字的和。
-
我们将探讨使用 OpenJ9 改进启动时间的选项。
-
我们将在工作负载下测量 OpenJ9 上的驻留集大小内存占用。
-
您将为微服务和 OpenJ9 构建一个 Docker 镜像。
-
我们将讨论如何改进 Docker 容器的启动时间以及如何在该环境中调整 OpenJ9。
您需要什么
-
文本编辑器或 IDE
-
Java 21
-
OpenJ9
-
Maven 或 Gradle
-
Docker
-
使用 Locust 生成工作负载
除非通过了从 Oracle 授权的 Java SE 技术兼容性工具包(Technology Compatibility Kit,TCK),否则 Eclipse 基金会项目不得分发、营销或推广 JDK 二进制文件,而 Eclipse OpenJ9 项目目前无法访问该工具包。您可以自行构建 Eclipse OpenJ9 二进制文件,或者下载一个 IBM Semeru 运行时。 |
创建项目
此项目的代码包含功能等效的 Maven 和 Gradle 构建文件。
使用 Gradle
以下是您应该使用的 build.gradle.kts
文件的内容
使用 Maven
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>io.vertx.howtos</groupId>
<artifactId>openj9-howto</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<vertx.version>5.0.0.CR2</vertx.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-stack-depchain</artifactId>
<version>${vertx.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-core</artifactId>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-web</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<configuration>
<release>21</release>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.5.0</version>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<manifestEntries>
<Main-Class>io.vertx.howtos.openj9.Main</Main-Class>
</manifestEntries>
</transformer>
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
</transformers>
<createDependencyReducedPom>false</createDependencyReducedPom>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
编写服务
该服务公开了一个 HTTP 服务器,并包含在一个 Java 类中
package io.vertx.howtos.openj9;
import io.vertx.core.Future;
import io.vertx.core.VerticleBase;
import io.vertx.core.Vertx;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.RoutingContext;
import io.vertx.ext.web.handler.BodyHandler;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.NANOSECONDS;
public class Main extends VerticleBase {
@Override
public Future<?> start() {
Router router = Router.router(vertx);
router.post().handler(BodyHandler.create());
router.post("/sum").handler(this::sum);
return vertx.createHttpServer()
.requestHandler(router)
.listen(8080);
}
private void sum(RoutingContext context) {
JsonObject input = context.body().asJsonObject();
Integer a = input.getInteger("a", 0);
Integer b = input.getInteger("b", 0);
JsonObject response = new JsonObject().put("sum", a + b);
context.response()
.putHeader("Content-Type", "application/json")
.end(response.encode());
}
public static void main(String[] args) {
long startTime = System.nanoTime();
Vertx vertx = Vertx.vertx();
vertx.deployVerticle(new Main()).await();
long duration = MILLISECONDS.convert(System.nanoTime() - startTime, NANOSECONDS);
System.out.println("Started in " + duration + "ms");
}
}
我们可以运行该服务
$ ./gradlew run
然后使用 HTTPie 进行测试
$ http :8080/sum a:=1 b:=2 HTTP/1.1 200 OK Content-Type: application/json content-length: 9 { "sum": 3 } $
我们还可以构建一个捆绑了所有依赖项的 JAR 归档文件,然后执行它
$ ./gradlew shadowJar $ java -jar build/libs/openj9-howto-all.jar
改进启动时间
微服务通过测量从 main
方法入口到 HTTP 服务器启动后的回调通知之间的时间来报告启动时间。
我们可以运行几次 java -jar build/libs/openj9-howto-all.jar
并选择最佳时间。在我的机器上,我得到的最佳时间是 311ms。
OpenJ9 提供预先编译(ahead-of-time compiler)和类数据共享缓存,以提高启动时间并减少内存消耗。首次运行通常开销较大,但所有后续运行都将受益于缓存,这些缓存也会定期更新。
相关的 OpenJ9 标志如下
-
-Xshareclasses
:启用类共享 -
-Xshareclasses:name=NAME
:缓存的名称,通常每个应用程序一个 -
-Xshareclasses:cacheDir=DIR
:用于存储缓存文件的文件夹
让我们运行几次
$ java -Xshareclasses -Xshareclasses:name=sum -Xshareclasses:cacheDir=_cache -jar build/libs/openj9-howto-all.jar
在我的机器上,第一次运行需要 457ms,这比 311ms“多得多”!然而,接下来的运行都接近 130ms,最佳成绩为 112ms,这对于 JVM 应用程序的启动时间来说非常出色。
内存使用
现在让我们测量微服务在 OpenJ9 上的内存使用情况,并与 OpenJDK 进行比较。
这不是一个严格的基准测试。您已收到警告 😉 |
生成工作负载
我们使用 Locust 来生成一些工作负载。locustfile.py
文件包含模拟用户执行随机数求和的代码
from locust import *
import random
import json
class Client(HttpUser):
wait_time = between(0.5, 1)
host = "https://:8080"
@task
def sum(self):
data = json.dumps({"a": random.randint(1, 100), "b": random.randint(1, 100)})
self.client.post("/sum", data=data, name="Sum", headers={"content-type": "application/json"})
然后我们可以运行 locust
,并连接到 https://:8089 来开始测试。让我们模拟 100 个用户,每秒孵化 10 个新用户。这使我们每秒大约有 130 个请求。
测量 RSS
Quarkus 团队有一份关于测量 RSS 的良好指南。在 Linux 上,您可以使用 ps
或 pmap
来测量 RSS,而在 macOS 上,ps
即可。我正在使用 macOS,所以一旦我获得正在运行的应用程序的进程 ID,我就可以如下获取其 RSS
$ ps x -o pid,rss,command -p 66820 PID RSS COMMAND 66820 124032 java -jar build/libs/openj9-howto-all.jar
对于所有测量,我们启动 Locust 并让它预热微服务。一分钟后,我们重置统计数据并重新启动测试,然后查看 RSS 和 99% 的延迟。我们将尝试在不进行调优的情况下运行应用程序,然后通过限制最大堆大小(参见 -Xmx
标志)来运行。
使用 OpenJDK 21 且未调优
-
RSS:约 143 MB
-
99% 延迟:1ms
使用 Semeru 21 且未调优
-
RSS:约 90 MB
-
99% 延迟:1ms
OpenJ9 在内存消耗方面显然非常高效,同时不影响延迟。
像往常一样,请对这些数据持保留态度,并根据您的实际使用情况,在您自己的服务上使用适当的工作负载进行测量。 |
构建和运行 Docker 镜像
好的,我们已经看到即使在不调优的情况下,OpenJ9 对内存也如此友好。现在让我们将微服务打包成一个 Docker 镜像。
这是您可以使用的 Dockerfile
FROM ibm-semeru-runtimes:open-21-jdk
RUN mkdir -p /app/_cache
COPY build/libs/openj9-howto-all.jar /app/app.jar
VOLUME /app/_cache
EXPOSE 8080
CMD ["java", "-Xvirtualized", "-Xshareclasses", "-Xshareclasses:name=sum", "-Xshareclasses:cacheDir=/app/_cache", "-jar", "/app/app.jar"]
您可以注意到
-
-Xvirtualized
是用于虚拟化/容器环境的标志,这样 OpenJ9 在空闲时可以减少 CPU 消耗 -
/app/_cache
是一个卷,需要挂载它以便容器共享 OpenJ9 类缓存。
镜像可以如下构建
$ docker build . -t openj9-app
然后我们可以从该镜像创建容器
$ docker run -it --rm -v /tmp/_cache:/app/_cache -p 8080:8080 openj9-app
同样,第一个容器启动较慢,而后续容器则受益于缓存。
在某些平台上,
|
总结
-
我们使用 Vert.x 编写了一个微服务。
-
我们在 OpenJ9 上运行了这个微服务。
-
我们使用类数据共享改进了启动时间。
-
我们对微服务施加了工作负载,然后检查了与使用 HotSpot 的 OpenJDK 相比,OpenJ9 的内存占用仍然很低。
-
我们构建了一个包含 OpenJ9 的 Docker 镜像,通过类数据共享实现了快速容器启动时间,并在空闲时降低了 CPU 使用率。