使用OpenTelemetry/Micrometer实现Java应用的全链路追踪与可观测性

使用 OpenTelemetry/Micrometer 实现 Java 应用的全链路追踪与可观测性

大家好,今天我们来聊聊如何使用 OpenTelemetry 和 Micrometer 实现 Java 应用的全链路追踪与可观测性。在微服务架构日益流行的今天,服务之间的调用关系变得越来越复杂,问题排查和性能优化也变得更加困难。全链路追踪和可观测性就显得尤为重要。

1. 什么是全链路追踪和可观测性?

  • 全链路追踪 (Distributed Tracing): 记录一次请求从进入系统到最终完成的整个调用链,包括每个服务、组件的耗时、状态等信息。这使得我们能够快速定位性能瓶颈和错误发生的环节。

  • 可观测性 (Observability): 指的是通过观察系统的外部输出,来推断系统内部状态的能力。它包括三个核心要素:

    • Metrics (指标): 数值型数据,用于衡量系统的性能、资源利用率等。例如:CPU 使用率、内存使用率、请求响应时间等。
    • Logs (日志): 记录系统运行时的事件信息,用于诊断问题和审计。
    • Traces (追踪): 记录请求在不同服务间的调用链,用于定位性能瓶颈和错误。

2. 为什么选择 OpenTelemetry 和 Micrometer?

  • OpenTelemetry: 是一个 CNCF (Cloud Native Computing Foundation) 孵化的可观测性框架,提供了一套标准化的 API、SDK 和工具,用于生成、收集和导出遥测数据。它的主要优点包括:

    • 标准化: 避免了厂商锁定,可以灵活地选择不同的后端存储和分析工具。
    • 语言无关: 支持多种编程语言,可以构建统一的可观测性平台。
    • 可扩展性: 可以自定义 SpanProcessor、Sampler 等组件,满足不同的需求。
  • Micrometer: 是一个 Java 应用的指标收集库,它提供了一套统一的 API,可以方便地将指标数据导出到不同的监控系统。它的主要优点包括:

    • 简化指标收集: 通过注解或 API,可以轻松地收集 JVM、Tomcat、Spring 等框架的指标。
    • 多监控系统支持: 支持 Prometheus、InfluxDB、Datadog 等多种监控系统。
    • 与 Spring Boot 集成良好: Spring Boot 提供了自动配置,可以快速集成 Micrometer。

3. 如何使用 OpenTelemetry 和 Micrometer?

下面我们将以一个简单的 Spring Boot 应用为例,演示如何使用 OpenTelemetry 和 Micrometer 实现全链路追踪和可观测性。

3.1 项目准备

创建一个简单的 Spring Boot 项目。可以使用 Spring Initializr (https://start.spring.io/) 创建项目,添加以下依赖:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <dependency>
        <groupId>io.micrometer</groupId>
        <artifactId>micrometer-registry-prometheus</artifactId>
    </dependency>
    <dependency>
        <groupId>io.opentelemetry</groupId>
        <artifactId>opentelemetry-api</artifactId>
        <version>1.35.0</version>
    </dependency>
    <dependency>
        <groupId>io.opentelemetry</groupId>
        <artifactId>opentelemetry-sdk</artifactId>
        <version>1.35.0</version>
    </dependency>
    <dependency>
        <groupId>io.opentelemetry</groupId>
        <artifactId>opentelemetry-exporter-jaeger</artifactId>
        <version>1.35.0</version>
    </dependency>
    <dependency>
        <groupId>io.opentelemetry</groupId>
        <artifactId>opentelemetry-instrumentation-annotations</artifactId>
        <version>1.35.0</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
</dependencies>

依赖说明:

  • spring-boot-starter-web: Spring Web 依赖,用于构建 RESTful API。
  • spring-boot-starter-actuator: Spring Boot Actuator 依赖,提供监控和管理端点。
  • micrometer-registry-prometheus: Micrometer Prometheus 注册表,用于将指标数据导出到 Prometheus。
  • opentelemetry-api: OpenTelemetry API,提供trace api.
  • opentelemetry-sdk: OpenTelemetry SDK,用于配置和初始化 OpenTelemetry。
  • opentelemetry-exporter-jaeger: OpenTelemetry Jaeger 导出器,用于将追踪数据导出到 Jaeger。
  • opentelemetry-instrumentation-annotations: OpenTelemetry Instrumentation Annotations,提供注解方式创建span.
  • spring-boot-starter-aop: Spring AOP 依赖,用于拦截方法调用并创建 Span。

3.2 配置 OpenTelemetry

创建一个配置类来初始化 OpenTelemetry SDK。

import io.jaegertracing.Configuration;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.exporter.jaeger.JaegerGrpcSpanExporter;
import io.opentelemetry.sdk.OpenTelemetrySdk;
import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.sdk.trace.SdkTracerProvider;
import io.opentelemetry.sdk.trace.export.BatchSpanProcessor;
import io.opentelemetry.semconv.resource.attributes.ResourceAttributes;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

import java.util.concurrent.TimeUnit;

@Configuration
@ComponentScan
public class OpenTelemetryConfig {

    @Value("${spring.application.name}")
    private String applicationName;

    @Value("${jaeger.endpoint}")
    private String jaegerEndpoint;

    @Bean
    public OpenTelemetry openTelemetry() {
        Resource resource = Resource.getDefault().merge(Resource.create(Attributes.of(ResourceAttributes.SERVICE_NAME, applicationName)));

        SdkTracerProvider sdkTracerProvider = SdkTracerProvider.builder()
                .addSpanProcessor(BatchSpanProcessor.builder(jaegerGrpcSpanExporter()).build())
                .setResource(resource)
                .build();

        OpenTelemetrySdk openTelemetrySdk = OpenTelemetrySdk.builder().setTracerProvider(sdkTracerProvider).build();

        // 优雅关闭钩子
        Runtime.getRuntime().addShutdownHook(new Thread(sdkTracerProvider::close));

        return openTelemetrySdk;
    }

    @Bean
    public JaegerGrpcSpanExporter jaegerGrpcSpanExporter() {
        return JaegerGrpcSpanExporter.builder().setEndpoint(jaegerEndpoint).setTimeout(30, TimeUnit.SECONDS).build();
    }

    @Bean
    public Tracer tracer(OpenTelemetry openTelemetry) {
        return openTelemetry.getTracer(applicationName);
    }

}

配置说明:

  • applicationName: 应用程序名称,用于在 Jaeger UI 中区分不同的服务。
  • jaegerEndpoint: Jaeger 收集器的地址,例如:grpc://localhost:14250
  • Resource: 资源信息,包括服务名称、版本等。
  • SdkTracerProvider: Tracer 提供者,用于创建 Tracer。
  • BatchSpanProcessor: Span 处理器,用于批量导出 Span 数据。
  • JaegerGrpcSpanExporter: Jaeger 导出器,用于将 Span 数据导出到 Jaeger。

3.3 配置 Micrometer

Spring Boot Actuator 默认集成了 Micrometer,只需要添加 micrometer-registry-prometheus 依赖,并配置 Prometheus 端点即可。

application.propertiesapplication.yml 中添加以下配置:

management.endpoints.web.exposure.include=prometheus
management.metrics.export.prometheus.enabled=true

配置说明:

  • management.endpoints.web.exposure.include=prometheus: 暴露 Prometheus 端点。
  • management.metrics.export.prometheus.enabled=true: 启用 Prometheus 指标导出。

3.4 创建 Controller

创建一个简单的 Controller,模拟服务之间的调用。

import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.instrumentation.annotations.WithSpan;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Random;

@RestController
public class HelloController {

    private static final Logger logger = LoggerFactory.getLogger(HelloController.class);

    @Autowired
    private Tracer tracer;

    @GetMapping("/hello")
    @WithSpan("hello-endpoint")
    public String hello() throws InterruptedException {
        logger.info("Handling /hello request");
        String result = processRequest();
        logger.info("Request processed successfully");
        return "Hello, " + result;
    }

    @WithSpan
    private String processRequest() throws InterruptedException {
        logger.info("Processing request...");
        // 模拟一些耗时操作
        Thread.sleep(new Random().nextInt(200));
        anotherMethod();
        logger.info("Request processed.");
        return "World!";
    }

    @WithSpan("anotherMethod")
    private void anotherMethod() throws InterruptedException {
        logger.info("Executing anotherMethod...");
        // 模拟一些耗时操作
        Thread.sleep(new Random().nextInt(100));
        logger.info("anotherMethod executed.");
    }
}

代码说明:

  • @WithSpan: 使用 OpenTelemetry Instrumentation Annotations 创建 Span。可以指定 Span 的名称,也可以使用方法名作为 Span 的名称。
  • tracer.spanBuilder("manualSpan").startSpan(): 手动创建 Span。
  • span.setAttribute("key", "value"): 设置 Span 的属性。
  • span.end(): 结束 Span。

3.5 运行 Jaeger 和 Prometheus

  • Jaeger: 可以使用 Docker 运行 Jaeger。

    docker run -d -p 16686:16686 -p 14268:14268 -p 14250:14250 jaegertracing/all-in-one:latest

    访问 http://localhost:16686 查看 Jaeger UI。

  • Prometheus: 可以使用 Docker 运行 Prometheus。首先,创建一个 prometheus.yml 文件,配置 Prometheus 抓取 Spring Boot 应用的指标。

    global:
      scrape_interval:     15s
      evaluation_interval: 15s
    
    scrape_configs:
      - job_name: 'spring-boot'
        metrics_path: '/actuator/prometheus'
        scrape_interval: 5s
        static_configs:
          - targets: ['localhost:8080'] # 替换为你的 Spring Boot 应用的地址

    然后,运行 Prometheus。

    docker run -d -p 9090:9090 -v $(pwd)/prometheus.yml:/etc/prometheus/prometheus.yml prom/prometheus

    访问 http://localhost:9090 查看 Prometheus UI。

3.6 测试应用

启动 Spring Boot 应用,访问 http://localhost:8080/hello

  • Jaeger: 在 Jaeger UI 中可以看到请求的调用链,包括每个服务的耗时、状态等信息。
  • Prometheus: 在 Prometheus UI 中可以查询 Spring Boot 应用的指标,例如:jvm_memory_used_byteshttp_server_requests_seconds_count 等。

4. 进阶用法

  • 自定义 SpanProcessor: 可以使用自定义 SpanProcessor 对 Span 数据进行处理,例如:添加标签、过滤 Span 等。
  • 采样 (Sampling): 可以通过配置 Sampler 控制 Span 的采样率,减少数据量。
  • Baggage: 可以使用 Baggage 在服务之间传递上下文信息。
  • Metrics Filter: 可以使用 MetricsFilter 过滤不需要的指标。
  • 自定义 Metrics: 可以使用 Micrometer API 创建自定义指标。

5. 代码示例

5.1 自定义 SpanProcessor

import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.context.Context;
import io.opentelemetry.sdk.trace.ReadWriteSpan;
import io.opentelemetry.sdk.trace.ReadableSpan;
import io.opentelemetry.sdk.trace.SpanProcessor;

public class CustomSpanProcessor implements SpanProcessor {

    @Override
    public void onStart(Context parentContext, ReadWriteSpan span) {
        // 在 Span 开始时添加属性
        span.setAttribute("custom.attribute", "custom.value");
    }

    @Override
    public boolean isStartRequired() {
        return true;
    }

    @Override
    public void onEnd(ReadableSpan span) {
        // 在 Span 结束时进行处理
        if (span.getKind() == SpanKind.SERVER) {
            // 对于服务端 Span,添加额外的属性
            span.toSpanData().getAttributes().put("server.processed", true);
        }
    }

    @Override
    public boolean isEndRequired() {
        return true;
    }
}

将自定义 SpanProcessor 添加到 SdkTracerProvider 中:

SdkTracerProvider sdkTracerProvider = SdkTracerProvider.builder()
                .addSpanProcessor(BatchSpanProcessor.builder(jaegerGrpcSpanExporter()).build())
                .addSpanProcessor(new CustomSpanProcessor()) // 添加自定义 SpanProcessor
                .setResource(resource)
                .build();

5.2 采样 (Sampling)

import io.opentelemetry.sdk.trace.samplers.Sampler;

SdkTracerProvider sdkTracerProvider = SdkTracerProvider.builder()
                .addSpanProcessor(BatchSpanProcessor.builder(jaegerGrpcSpanExporter()).build())
                .setSampler(Sampler.traceIdRatioBased(0.5)) // 设置采样率为 50%
                .setResource(resource)
                .build();

5.3 Baggage

import io.opentelemetry.api.baggage.Baggage;
import io.opentelemetry.api.baggage.BaggageEntryMetadata;
import io.opentelemetry.context.Scope;

// 设置 Baggage
Baggage baggage = Baggage.builder()
        .put("user.id", "123", BaggageEntryMetadata.create("propagation=enabled"))
        .build();

try (Scope scope = baggage.makeCurrent()) {
    // 在该作用域内的所有 Span 都会携带 Baggage
    // ...
}

// 获取 Baggage
Baggage currentBaggage = Baggage.current();
String userId = currentBaggage.getEntryValue("user.id");

5.4 自定义 Metrics

import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.MeterRegistry;
import org.springframework.stereotype.Component;

@Component
public class CustomMetrics {

    private final Counter customCounter;

    public CustomMetrics(MeterRegistry registry) {
        customCounter = Counter.builder("custom.counter")
                .description("A custom counter")
                .register(registry);
    }

    public void increment() {
        customCounter.increment();
    }
}

在 Controller 中使用自定义 Metrics:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {

    @Autowired
    private CustomMetrics customMetrics;

    @GetMapping("/hello")
    public String hello() {
        customMetrics.increment();
        return "Hello, World!";
    }
}

6. 其他说明

  • 本示例使用 Jaeger 作为追踪数据的存储和分析工具,也可以选择其他的工具,例如:Zipkin、SkyWalking 等。
  • 本示例使用 Prometheus 作为指标数据的存储和分析工具,也可以选择其他的工具,例如:InfluxDB、Datadog 等。
  • 在实际项目中,需要根据业务需求选择合适的采样率、指标和标签。
  • 可以使用 OpenTelemetry Collector 集中收集和处理遥测数据。

一些想法

通过上述步骤,我们基本完成了使用 OpenTelemetry 和 Micrometer 实现 Java 应用的全链路追踪和可观测性的过程。这能够帮助我们更好地了解系统的运行状况,快速定位问题,并进行性能优化。希望今天的分享对大家有所帮助。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注