使用 Java Flight Recorder
本指南介绍如何扩展 Java Flight Recorder (JFR) 以提供对 Quarkus 应用程序的更多见解。 JFR 将来自 Java 标准 API 和 JVM 的各种信息记录为事件。 通过添加 Quarkus JFR 扩展,您可以将自定义 Quarkus 事件添加到 JFR。 这将帮助您解决应用程序中潜在的问题。
本文档是 Quarkus 可观察性参考指南的一部分,该指南介绍了此组件和其他可观察性相关组件。
可以预先配置 JFR 以转储文件,当应用程序退出时,JFR 将输出该文件。 该文件将包含 JFR 事件流的内容,其中也添加了 Quarkus 自定义事件。 当然,您可以随时获取此文件,即使您的应用程序意外退出也是如此。
先决条件
要完成本指南,您需要
-
大约 15 分钟
-
一个 IDE
-
已安装 JDK 17+ 并正确配置了
JAVA_HOME
-
Apache Maven 3.9.9
-
如果您想使用它,可以选择 Quarkus CLI
-
如果您想构建本机可执行文件(或者如果您使用本机容器构建,则为 Docker),可以选择安装 Mandrel 或 GraalVM 并进行适当的配置
创建 Maven 项目
首先,我们需要一个新项目。使用以下命令创建一个新项目
对于 Windows 用户
-
如果使用 cmd,(不要使用反斜杠
\
并将所有内容放在同一行上) -
如果使用 Powershell,请将
-D
参数括在双引号中,例如"-DprojectArtifactId=jfr-quickstart"
此命令生成 Maven 项目并导入 Quarkus JFR 扩展,其中包括默认的 JFR 支持。
如果您已经配置了 Quarkus 项目,则可以通过在项目基本目录中运行以下命令将 JFR 扩展添加到您的项目中
quarkus extension add quarkus-jfr
./mvnw quarkus:add-extension -Dextensions='quarkus-jfr'
./gradlew addExtension --extensions='quarkus-jfr'
这会将以下内容添加到您的构建文件中
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-jfr</artifactId>
</dependency>
implementation("io.quarkus:quarkus-jfr")
检查 Jakarta REST 资源
创建一个包含以下内容的 src/main/java/org/acme/jfr/JfrResource.java
文件
package org.acme.jfr;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
@Path("/hello")
public class JfrResource {
@GET
@Produces(MediaType.TEXT_PLAIN)
public String hello() {
return "hello";
}
}
请注意,应用程序中没有包含任何 JFR 特定代码。 默认情况下,发送到此端点的请求将被记录到 JFR 中,而无需任何代码更改。
运行 Quarkus 应用程序和 JFR
现在我们准备好运行我们的应用程序了。 我们可以启动应用程序,并将 JFR 配置为从 Java 虚拟机的启动开始启用。
quarkus dev -Djvm.args="-XX:StartFlightRecording=name=quarkus,dumponexit=true,filename=myrecording.jfr"
./mvnw quarkus:dev -Djvm.args="-XX:StartFlightRecording=name=quarkus,dumponexit=true,filename=myrecording.jfr"
./gradlew --console=plain quarkusDev -Djvm.args="-XX:StartFlightRecording=name=quarkus,dumponexit=true,filename=myrecording.jfr"
在 Java Flight Recorder 和应用程序运行的情况下,我们可以向提供的端点发出请求
$ curl https://:8080/hello
hello
这就是将信息写入 JFR 所需的全部操作。
打开 JFR 转储文件
我们可以使用两个工具打开 JFR 转储:jfr
CLI 和 JDK Mission Control (JMC)。 也可以使用 JFR API 读取它们,但我们不会在本指南中对此进行深入探讨。
jfr CLI
jfr
CLI 是 OpenJDK 中包含的工具。 可执行文件是 $JAVA_HOME/bin/jfr
。 我们可以使用 jfr CLI 查看事件列表,该列表仅限于转储文件中与 Quarkus 相关的事件,方法是运行以下命令
jfr print --categories quarkus myrecording.jfr
JDK Mission Control
JMC 本质上是 JFR 的 GUI。 某些 Java 发行版包含 JMC,但如果没有,您需要手动下载它。
要使用 JMC 查看事件列表,首先我们按如下方式在 JMC 中加载 JFR 文件。
jmc -open myrecording.jfr
打开 JFR 文件后,我们有两个选择。 一种是将事件视为表格列表,另一种是在事件发生的线程中按时间顺序查看事件。
要以表格样式查看 Quarkus 事件,请选择 JMC 左侧的 Event Browser,然后打开 JMC 右侧的 Quarkus 事件类型树。

要按时间顺序在线程上查看 Quarkus 事件,请选择 JMC 左侧的 Java application
和 Threads
。

标准配置不显示 Quarkus 事件。 我们必须执行三个任务才能看到 Quarkus 事件。
-
右键单击并选择
Edit Thread Activity Lanes…
。 -
选择加号按钮以在左侧添加一个新通道,然后选中以显示该通道。
-
选择 Quarkus 作为该通道将显示的事件类型,然后按 OK。

现在我们可以看到每个线程的 Quarkus 事件。

此扩展能够同时记录多个 JFR 事件(由不同线程发出或在反应式执行模型中从同一线程发出)。 因此,事件可能会在 JMC 中重叠。 这可能会使您难以看到想要看到的事件。 为了避免这种情况,我们建议使用 请求 ID 来筛选事件,以便您仅看到有关您想要查看的请求的信息。 |
事件
识别请求
此扩展与 OpenTelemetry 扩展协作。 此扩展记录的事件具有跟踪 ID 和跨度 ID。 这些分别用 OpenTelemetry ID 记录。
这意味着在从 OpenTelemetry 实现提供的 UI 中识别出感兴趣的跟踪和跨度 ID 后,我们可以立即使用这些 ID 跳转到 JFR 中的详细信息。
如果我们尚未启用 OpenTelemetry 扩展,则此扩展会为每个请求创建一个 ID,并将其作为 traceId 链接到 JFR 事件。 在这种情况下,跨度 ID 将为空。
目前,Quarkus 仅记录 REST 事件,但我们计划在将来添加更多事件时使用此 ID 将每个事件相互链接。
事件实施策略
当 JFR 开始记录事件时,该事件尚未记录到 JFR,但是当提交该事件时,该事件将被记录。 因此,在转储时已开始记录但尚未提交的事件不会被转储。 由于 JFR 的设计,这是不可避免的。 这意味着如果存在长时间的处理,事件将不会被永久记录。 因此,您不会意识到长时间的处理。
为了解决这个问题,Quarkus 还可以记录处理开始和结束时的开始和结束事件。 这些事件默认情况下处于禁用状态。 但是,我们可以在 JFR 中启用这些事件,如下所述。
REST API 事件
当启用 Quarkus REST 或 RESTEasy 扩展时,将记录此事件。 以下三个 JFR 事件将在 REST 服务器处理完成后立即记录。
- REST
-
记录从 REST 服务器进程开始到 REST 服务器进程结束的时间段。
- RestStart
-
记录 REST 服务器进程的开始。
- RestEnd
-
记录 REST 服务器进程的结束。
这些事件具有以下信息。
-
HTTP 方法
-
URI
-
资源类
-
资源方法
-
客户端
HTTP 方法记录客户端访问的 HTTP 方法。 这将记录 HTTP 方法的字符串,例如 GET、POST、DELETE 和其他常规 HTTP 方法。
URI 记录客户端访问的 URI 路径。 这不包括主机名或端口号。
资源类记录已执行的类。 我们可以通过 HTTP 方法和 URI 检查资源类是否按预期执行。
资源方法记录已执行的方法。 我们可以通过 HTTP 方法和 URI 检查资源方法是否按预期执行。
客户端记录有关访问客户端的信息。
Native Image
Native 可执行文件支持 Java Flight Recorder。 此扩展也支持 Native 可执行文件。
要在 Native 可执行文件上启用 JFR,通常使用 --enable-monitoring
构建它。 但是,我们可以通过将 jfr
添加到配置属性 quarkus.native.monitoring
中,在 Quarkus Native 可执行文件中启用 JFR。 有两种方法可以设置此配置:将其包含在 application.properties
中或在构建时指定它。
第一种方法是首先在 application.properties
中配置设置。
quarkus.native.monitoring=jfr
然后像往常一样构建您的 Native 可执行文件
quarkus build --native
./mvnw install -Dnative
./gradlew build -Dquarkus.native.enabled=true
第二种方法是在构建时传递 -Dquarkus.native.monitoring=jfr
quarkus build --native -Dquarkus.native.monitoring=jfr
./mvnw install -Dnative -Dquarkus.native.monitoring=jfr
./gradlew build -Dquarkus.native.enabled=true -Dquarkus.native.monitoring=jfr
完成构建 Native 可执行文件后,您可以按如下方式运行带有 JFR 的 Native 应用程序
target/your-application-runner -XX:StartFlightRecording=name=quarkus,dumponexit=true,filename=myrecording.jfr
请注意,此时,Mandrel 和 GraalVM 无法为 Windows Native 可执行文件记录 JFR。 |
JFR 配置
我们可以使用 JFR CLI 配置 JFR 将记录的事件。 配置文件 JFC 文件为 XML 格式,因此我们可以使用文本编辑器修改它。 但是,建议使用 jfr configure
,它默认包含在 OpenJDK 中。
在这里,我们创建一个配置文件,其中记录了 REST Start 和 REST End 事件(默认情况下未记录)
jfr configure --input default.jfc +quarkus.RestStart#enabled=true +quarkus.RestEnd#enabled=true --output custom-rest.jfc
这将创建 custom-rest.jfc
作为配置文件,并为 RestStart 和 RestEnd 启用记录。
现在我们准备好使用新设置运行我们的应用程序了。 我们启动应用程序,并将 JFR 配置为从 Java 虚拟机的启动开始启用。
quarkus dev -Djvm.args="-XX:StartFlightRecording=name=quarkus,settings=./custom-rest.jfc,dumponexit=true,filename=myrecording.jfr"
./mvnw quarkus:dev -Djvm.args="-XX:StartFlightRecording=name=quarkus,settings=./custom-rest.jfc,dumponexit=true,filename=myrecording.jfr"
./gradlew --console=plain quarkusDev -Djvm.args="-XX:StartFlightRecording=name=quarkus,settings=./custom-rest.jfc,dumponexit=true,filename=myrecording.jfr"
添加新事件
本节适用于希望向此扩展添加新事件的用户。
我们建议将新事件与现有事件相关联。 在查看长时间运行的进程的详细信息时,关联非常有用。 例如,通用的 REST 应用程序从数据存储中检索处理所需的数据。 如果 REST 事件未与数据存储事件相关联,则不可能知道每个 REST 请求中处理了哪些数据存储事件。 当两个事件相关联时,我们会立即知道每个 REST 请求中处理了哪些数据存储事件。
数据存储事件尚未实现。 |
Quarkus JFR 扩展为事件关联提供请求 ID。 有关请求 ID 的更多信息,请参见 识别请求。
在特定代码中,需要执行以下两个步骤。 首先,在新的事件上实现 traceId
和 spanId
,如下所示:附加到这些事件的 @TraceIdRelational
和 @SpanIdRelational
将提供关联。
import io.quarkus.jfr.runtime.SpanIdRelational;
import io.quarkus.jfr.runtime.TraceIdRelational;
import jdk.jfr.Description;
import jdk.jfr.Event;
import jdk.jfr.Label;
public class NewEvent extends Event {
@Label("Trace ID")
@Description("Trace ID to identify the request")
@TraceIdRelational
protected String traceId;
@Label("Span ID")
@Description("Span ID to identify the request if necessary")
@SpanIdRelational
protected String spanId;
// other properties which you want to add
// setters and getters
}
然后,您从 IdProducer
对象的 getTraceId()
和 getSpanId()
获取要存储在事件中的信息。
import io.quarkus.jfr.runtime.IdProducer;
public class NewInterceptor {
@Inject
IdProducer idProducer;
private void recordedInvoke() {
NewEvent event = new NewEvent();
event.begin();
// The process you want to measure
event.end();
if (endEvent.shouldCommit()) {
event.setTraceId(idProducer.getTraceId());
event.setSpanId(idProducer.getSpanId());
// call other setters which you want to add
endEvent.commit();
}
}
}
配置参考
构建时固定的配置属性 - 所有其他配置属性都可以在运行时覆盖
配置属性 |
类型 |
默认 |
---|---|---|
如果为 false,即使启用了 JFR,也不会记录 quarkus-jfr 事件。 在这种情况下,将根据设置记录 Java 标准 API 和虚拟机信息。 默认值为 环境变量: 显示更多 |
布尔值 |
|
如果为 false,即使启用了 JFR,也不会记录 quarkus-jfr 中的 REST 事件。 在这种情况下,将根据设置记录其他 quarkus-jfr、Java 标准 API 和虚拟机信息。 默认值为 环境变量: 显示更多 |
布尔值 |
|