paint-brush
Spring Boot 中的 OpenTelemetry 跟踪:在 Java Agent 和 Micrometer 之间进行选择经过@nfrankel
193 讀數

Spring Boot 中的 OpenTelemetry 跟踪:在 Java Agent 和 Micrometer 之间进行选择

经过 Nicolas Fränkel8m2024/08/11
Read on Terminal Reader

太長; 讀書

这篇文章比较了 Spring Boot 应用程序中使用 Java Agent v1、Java Agent v2 和 Micrometer Tracing 的 OpenTelemetry 跟踪解决方案。它重点介绍了配置、功能和跟踪能力方面的差异,并深入了解了每种方法在可观察性方面的有效性和应用。
featured image - Spring Boot 中的 OpenTelemetry 跟踪:在 Java Agent 和 Micrometer 之间进行选择
Nicolas Fränkel HackerNoon profile picture

我的 OpenTelemetry Tracing 演示包含两个 Spring Boot 组件。其中一个使用 Java 代理,最近我将其从 v1.x 升级到 v2.x 时注意到了不同的行为。在另一个中,我使用 Micrometer Tracing,因为我编译为 GraalVM 原生,它无法处理 Java 代理。



在这篇文章中,我想比较这三种方法:Java agent v1、Java agent v2 和 Micrometer Tracing。

基础应用程序及其基础设施

我将使用相同的基础应用程序:一个用 Kotlin 编码的简单 Spring Boot 应用程序。它提供单个端点。


  • 端点之外的函数名为entry()
  • 它调用另一个名为intermediate()的函数
  • 后者使用WebClient实例( RestTemplate的替代品)来调用上述端点
  • 为了避免无限循环,我传递了一个自定义请求标头:如果entry()函数找到它,它就不会继续执行


示例应用程序序列图


翻译过来就是下面的代码:


 @SpringBootApplication class Agent1xApplication @RestController class MicrometerController { private val logger = LoggerFactory.getLogger(MicrometerController::class.java) @GetMapping("/{message}") fun entry(@PathVariable message: String, @RequestHeader("X-done") done: String?) { logger.info("entry: $message") if (done == null) intermediate() } fun intermediate() { logger.info("intermediate") RestClient.builder() .baseUrl("http://localhost:8080/done") .build() .get() .header("X-done", "true") .retrieve() .toBodilessEntity() } }


对于每个设置,我都会检查两个阶段:启用 OpenTelemetry 的主要阶段和创建额外内部跨度的自定义阶段。

微米追踪

Micrometer Tracing 源自Micrometer ,一种“与供应商无关的应用程序可观察性外观”。


Micrometer Tracing 为最流行的跟踪器库提供了一个简单的外观,让您可以对基于 JVM 的应用程序代码进行插桩,而无需锁定供应商。它旨在几乎不增加跟踪收集活动的开销,同时最大限度地提高跟踪工作的可移植性。


--微米追踪站点


要开始使用 Micrometer Tracing,需要添加一些依赖项:


  • Spring Boot 执行器, org.springframework.boot:spring-boot-starter-actuator
  • Micrometer Tracing 本身, io.micrometer:micrometer-tracing
  • 通往目标跟踪后端 API 的“桥梁”。在我的例子中,它是 OpenTelemetry,因此是io.micrometer:micrometer-tracing-bridge-otel
  • 后端的具体导出器, io.opentelemetry:opentelemetry-exporter-otlp


我们不需要 BOM,因为版本已经在 Spring Boot 父级中定义。


然而,我们需要两个运行时配置参数:跟踪应该发送到哪里,以及组件的名称是什么。它们由MANAGEMENT_OTLP_TRACING_ENDPOINTSPRING_APPLICATION_NAME变量控制。


 services: jaeger: image: jaegertracing/all-in-one:1.55 environment: - COLLECTOR_OTLP_ENABLED=true #1 ports: - "16686:16686" micrometer-tracing: build: dockerfile: Dockerfile-micrometer environment: MANAGEMENT_OTLP_TRACING_ENDPOINT: http://jaeger:4318/v1/traces #2 SPRING_APPLICATION_NAME: micrometer-tracing #3
  1. 为 Jaeger 启用 OpenTelemetry 收集器
  2. Jaeger OpenTelemetry gRPC 端点的完整 URL
  3. 设置 OpenTelemetry 的服务名称


结果如下:

Jaeger 上的微米线迹无需定制


无需任何定制,Micrometer 在接收和发送 HTTP 请求时会创建跨度。


框架需要为RestClient注入魔力才能进行发送。我们必须让前者实例化后者:


 @SpringBootApplication class MicrometerTracingApplication { @Bean fun restClient(builder: RestClient.Builder) = builder.baseUrl("http://localhost:8080/done").build() }


我们可以通过多种方式创建手动跨度,其中一种是通过 OpenTelemetry API 本身。但是,设置需要大量样板代码。最直接的方法是 Micrometer 的Observation API 。它的主要好处是使用一个 API 来管理指标跟踪


观察 API 类图

以下是更新后的代码:


 class MicrometerController( private val restClient: RestClient, private val registry: ObservationRegistry ) { @GetMapping("/{message}") fun entry(@PathVariable message: String, @RequestHeader("X-done") done: String?) { logger.info("entry: $message") val observation = Observation.start("entry", registry) if (done == null) intermediate(observation) observation.stop() } fun intermediate(parent: Observation) { logger.info("intermediate") val observation = Observation.createNotStarted("intermediate", registry) .parentObservation(parent) .start() restClient.get() .header("X-done", "true") .retrieve() .toBodilessEntity() observation.stop() } }


添加的观察调用反映了生成的跟踪:


使用 Observation API 在 Jaeger 上进行 Micrometer 跟踪

OpenTelemetry 代理 v1

Micrometer Tracing 的替代方案是通用的OpenTelemetry Java Agent 。 它的主要优点是它既不影响代码也不影响开发人员;代理是一个纯粹的运行时范围关注点。


 java -javaagent:opentelemetry-javaagent.jar agent-one-1.0-SNAPSHOT.jar


代理遵守 OpenTelemetry 的环境变量配置:


 services: agent-1x: build: dockerfile: Dockerfile-agent1 environment: OTEL_EXPORTER_OTLP_ENDPOINT: http://jaeger:4317 #1 OTEL_RESOURCE_ATTRIBUTES: service.name=agent-1x #2 OTEL_METRICS_EXPORTER: none #3 OTEL_LOGS_EXPORTER: none #4 ports: - "8081:8080"
  1. 设置协议、域和端口。库附加/v1/traces
  2. 设置 OpenTelemetry 的服务名称
  3. 不导出指标也不导出日志


无需进行更多配置,我们得到以下跟踪:


Agent v1 在 Jaeger 上进行跟踪,无需任何定制


代理会自动跟踪接收和发送的请求以及标有 Spring 相关注释的函数。根据调用堆栈,跟踪正确地嵌套在一起。要跟踪其他函数,我们需要在代码库中添加依赖项io.opentelemetry.instrumentation:opentelemetry-instrumentation-annotations 。现在,我们可以使用@WithSpan注释注释以前未跟踪的函数。

@WithSpan 类图


value()部分控制跟踪的标签,而kind则转换为span.kind属性。如果将值设置为空字符串(默认值),则会输出函数的名称。就我的目的而言,默认值就足够了。


 @WithSpan fun intermediate() { logger.info("intermediate") RestClient.builder() .baseUrl("http://localhost:8080/done") .build() .get() .header("X-done", "true") .retrieve() .toBodilessEntity() }


它产生了预期的新的intermediate()轨迹:

Jaeger 上的 Agent v1 跟踪,带有注释

OpenTelemetry 代理 v2

OpenTelemetry 于今年 1 月发布了该代理的新主要版本。我用它更新了我的演示;现在只有在应用程序接收和发送请求时才会创建跟踪。

Agent v2 在 Jaeger 上进行跟踪,无需任何定制


与之前的版本一样,我们可以使用@WithSpan注释添加跟踪。唯一的区别是我们还必须注释entry()函数。默认情况下不会跟踪它。

Jaeger 上的 Agent v2 跟踪,带有注释


讨论

Spring 之所以成功,有两个原因:它简化了复杂的解决方案,EJB 2,并提供了一个超越竞争库的抽象层。Micrometer Tracing 最初是 Zipkin 和 Jaeger 上的抽象层,这完全是合理的。由于 OpenTelemetry 得到了大多数跨编程语言和跟踪收集器的库的支持,因此这一论点变得毫无意义。Observation API 仍然是 Micrometer Tracing 的一个相当大的优势,因为它使用了一个超越 Metrics 和 Traces 的 API。


在 Java 代理方面,OpenTelemetry 配置在所有技术堆栈和库中都是相似的 - 环境变量。当我从 v1 升级到 v2 时,我有点失望,因为新代理不支持 Spring:默认情况下不会跟踪带 Spring 注释的函数。


最后,这是一个明智的决定。明确说明您想要的跨度比删除一些您不想看到的跨度要好得多。


感谢Jonatan Ivanov的帮助和评论


本文的完整源代码可以在 GitHub 上找到:

进一步来说:


最初于 2024 年 8 月 3 日在A Java Geek上发布