使用 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.properties 或 application.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_bytes、http_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 应用的全链路追踪和可观测性的过程。这能够帮助我们更好地了解系统的运行状况,快速定位问题,并进行性能优化。希望今天的分享对大家有所帮助。