Spring Boot 中 OpenTelemetry 跟踪

这篇文章中比较这三种不同的方式: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 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_ENDPOINT和SPRING_APPLICATION_NAME变量控制。

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

无需任何定制,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 来管理度量和跟踪。

更新后代码:

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()
    }
}

新增的观察呼叫反映了生成的轨迹.

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                   
      OTEL_RESOURCE_ATTRIBUTES: service.name=agent-1x                   
      OTEL_METRICS_EXPORTER: none                                       
      OTEL_LOGS_EXPORTER: none                                          
    ports:
      -
"8081:8080"
  • 设置协议、域和端口。该库附加/v1/traces
  • 设置 OpenTelemetry 的服务名称
  • OTEL_METRICS_EXPORTER: none  不导出指标也不导出日志

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

现在,我们可以使用 @WithSpan 注解来注解以前未跟踪的函数。

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

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

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

与之前的版本一样,我们可以使用 @WithSpan 注解添加跟踪。

唯一不同的是,我们还必须注解 entry() 函数。 默认情况下不会跟踪该函数。

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

在 Java 代理方面,OpenTelemetry 配置在所有技术堆栈和库中都是相似的 - 环境变量。当我从 v1 升级到 v2 时,我有点失望,因为新代理不支持 Spring:默认情况下不会跟踪带 Spring 注释的函数。最后,这是一个明智的决定。明确说明您想要的跨度比删除一些您不想看到的跨度要好得多。

完整源代码可以在Github上找到。