文章 601
评论 5
浏览 236339
OpenTelemetry + Java:10 分钟给你的微服务加上分布式链路追踪

OpenTelemetry + Java:10 分钟给你的微服务加上分布式链路追踪

一、引言

分布式链路追踪是微服务架构中不可或缺的监控手段。本文将带你从零开始,用 OpenTelemetry 为你的 Java 微服务添加分布式链路追踪能力。

核心目标:10 分钟内完成从零到可视化的完整链路追踪搭建。


二、5 分钟快速入门

2.1 启动 Jaeger

创建 docker-compose.yml

version: '3.8'
services:
  jaeger:
    image: jaegertracing/all-in-one:1.51
    ports:
      - "16686:16686"
      - "4317:4317"
      - "4318:4318"
    environment:
      - COLLECTOR_OTLP_ENABLED=true

启动:

docker-compose up -d

打开浏览器访问 http://localhost:16686,你会看到 Jaeger 界面。

2.2 启动你的应用(Java Agent 方式)

java -javaagent:opentelemetry-javaagent.jar \
  -Dotel.service.name=order-service \
  -Dotel.traces.exporter=otlp \
  -Dotel.exporter.otlp.endpoint=http://localhost:4317 \
  -Dotel.metrics.exporter=none \
  -jar your-app.jar

效果:无需修改任何代码,自动追踪所有 HTTP 请求、数据库调用、消息队列等。


三、10 分钟深入:手动埋点

3.1 添加依赖

<dependency>
    <groupId>io.opentelemetry</groupId>
    <artifactId>opentelemetry-api</artifactId>
    <version>1.32.0</version>
</dependency>
<dependency>
    <groupId>io.opentelemetry</groupId>
    <artifactId>opentelemetry-instrumentation-annotations</artifactId>
    <version>1.32.0</version>
    <scope>provided</scope>
</dependency>

3.2 @WithSpan 注解

@Slf4j
@Service
public class OrderService {
    
    @WithSpan("createOrder")
    public OrderDTO createOrder(OrderCreateRequest request) {
        // 业务逻辑
        Order order = new Order();
        order.setUserId(request.getUserId());
        order.setProductId(request.getProductId());
        
        // 调用其他服务
        paymentService.pay(order.getId(), order.getAmount());
        
        return OrderDTO.from(order);
    }
    
    @WithSpan("calculateAmount")
    public BigDecimal calculateAmount(Long productId, Integer quantity) {
        Product product = productService.getById(productId);
        return product.getPrice().multiply(BigDecimal.valueOf(quantity));
    }
}

3.3 自定义属性

@Slf4j
@Service
public class OrderService {
    
    @Autowired
    private Tracer tracer;
    
    @WithSpan("createOrder")
    public OrderDTO createOrder(OrderCreateRequest request) {
        Span span = Span.current();
        span.setAttribute("userId", request.getUserId());
        span.setAttribute("productId", request.getProductId());
        span.setAttribute("quantity", request.getQuantity());
        
        // 业务逻辑
        Order order = orderRepository.save(buildOrder(request));
        
        span.addEvent("订单创建成功", Attributes.of(
            AttributeKey.stringKey("orderId"), order.getId().toString()
        ));
        
        return OrderDTO.from(order);
    }
}

3.4 手动创建 Span

@Slf4j
@Service
public class OrderService {
    
    @Autowired
    private Tracer tracer;
    
    public OrderDTO createOrder(OrderCreateRequest request) {
        Span span = tracer.spanBuilder("createOrder")
                .setAttribute("userId", request.getUserId())
                .setAttribute("productId", request.getProductId())
                .startSpan();
        
        try (Scope scope = span.makeCurrent()) {
            // 业务逻辑
            Order order = orderRepository.save(buildOrder(request));
            
            Span childSpan = tracer.spanBuilder("sendNotification")
                    .setParent(Context.current().with(span))
                    .startSpan();
            try (Scope childScope = childSpan.makeCurrent()) {
                notificationService.send(order.getId());
            } finally {
                childSpan.end();
            }
            
            return OrderDTO.from(order);
        } catch (Exception e) {
            span.recordException(e);
            span.setStatus(StatusCode.ERROR, e.getMessage());
            throw e;
        } finally {
            span.end();
        }
    }
}

四、跨服务传递 TraceId

4.1 HTTP 调用自动传递

使用 Java Agent 后,HTTP 客户端(RestTemplate、WebClient、HttpClient)会自动传递 TraceId:

@Service
public class OrderService {
    
    @Autowired
    private RestTemplate restTemplate;
    
    @WithSpan("callPaymentService")
    public void callPaymentService(Long orderId, BigDecimal amount) {
        PaymentRequest request = new PaymentRequest(orderId, amount);
        // TraceId 会自动通过 HTTP 头传递
        restTemplate.postForObject(
            "http://payment-service/api/pay", 
            request, 
            PaymentResponse.class
        );
    }
}

自动传递的 HTTP 头

traceparent: 00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01
tracestate: vendor1=value1,vendor2=value2

4.2 手动传递(非 HTTP 场景)

@Service
public class OrderService {
    
    @Autowired
    private Tracer tracer;
    
    @Autowired
    private RabbitTemplate rabbitTemplate;
    
    @WithSpan("sendOrderCreatedEvent")
    public void sendOrderCreatedEvent(Long orderId) {
        Span span = Span.current();
        
        // 获取当前 TraceContext
        SpanContext context = span.getSpanContext();
        String traceId = context.getTraceId();
        String spanId = context.getSpanId();
        boolean isSampled = context.isSampled();
        
        // 手动传递到消息体
        OrderEvent event = new OrderEvent();
        event.setOrderId(orderId);
        event.setTraceId(traceId);
        event.setSpanId(spanId);
        event.setSampled(isSampled);
        
        rabbitTemplate.convertAndSend("order-exchange", "order.created", event);
    }
}

@Service
public class OrderEventListener {
    
    @Autowired
    private Tracer tracer;
    
    @RabbitListener(queues = "order-queue")
    public void handleOrderEvent(OrderEvent event) {
        // 重建 SpanContext
        SpanContext parentContext = SpanContext.createFromRemoteParent(
            event.getTraceId(),
            event.getSpanId(),
            event.isSampled() ? TraceFlags.getSampled() : TraceFlags.getDefault(),
            TraceState.getDefault()
        );
        
        // 创建子 Span
        Span span = tracer.spanBuilder("handleOrderEvent")
                .setParent(Context.current().with(parentContext))
                .startSpan();
        
        try (Scope scope = span.makeCurrent()) {
            // 业务逻辑
            orderService.processOrderEvent(event);
        } finally {
            span.end();
        }
    }
}

五、采样策略配置

5.1 环境变量配置

# 采样率 100%
java -javaagent:opentelemetry-javaagent.jar \
  -Dotel.service.name=order-service \
  -Dotel.traces.sampler=always_on \
  -jar your-app.jar

# 采样率 10%
java -javaagent:opentelemetry-javaagent.jar \
  -Dotel.service.name=order-service \
  -Dotel.traces.sampler=traceidratio \
  -Dotel.traces.sampler.arg=0.1 \
  -jar your-app.jar

# 从不采样(生产环境默认)
java -javaagent:opentelemetry-javaagent.jar \
  -Dotel.service.name=order-service \
  -Dotel.traces.sampler=always_off \
  -jar your-app.jar

5.2 基于属性的采样

import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.context.Context;
import io.opentelemetry.sdk.trace.samplers.Sampler;
import io.opentelemetry.sdk.trace.samplers.SamplingDecision;
import io.opentelemetry.sdk.trace.samplers.SamplingResult;

import java.util.List;

public class CustomSampler implements Sampler {
    
    @Override
    public SamplingResult shouldSample(
            Context context,
            String traceId,
            String name,
            SpanKind spanKind,
            Attributes attributes,
            List<io.opentelemetry.api.trace.Link> parentLinks) {
        
        // 对关键业务的请求进行全量采样
        if (name.contains("createOrder") || name.contains("payment")) {
            return SamplingResult.create(SamplingDecision.RECORD_AND_SAMPLE);
        }
        
        // 对错误请求进行全量采样
        String statusCode = attributes.get(AttributeKey.stringKey("http.status_code"));
        if ("500".equals(statusCode)) {
            return SamplingResult.create(SamplingDecision.RECORD_AND_SAMPLE);
        }
        
        // 默认采样 10%
        return Math.random() < 0.1 
                ? SamplingResult.create(SamplingDecision.RECORD_AND_SAMPLE) 
                : SamplingResult.create(SamplingDecision.DROP);
    }
    
    @Override
    public String getDescription() {
        return "CustomSampler";
    }
}

六、完整示例:Spring Boot 应用

6.1 pom.xml

<project>
    <groupId>com.example</groupId>
    <artifactId>order-service</artifactId>
    <version>1.0.0</version>
    
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.0</version>
    </parent>
    
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>io.opentelemetry</groupId>
            <artifactId>opentelemetry-api</artifactId>
            <version>1.32.0</version>
        </dependency>
        <dependency>
            <groupId>io.opentelemetry</groupId>
            <artifactId>opentelemetry-instrumentation-annotations</artifactId>
            <version>1.32.0</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>
</project>

6.2 application.yml

server:
  port: 8080

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/example_db
    username: admin
    password: password
  jpa:
    hibernate:
      ddl-auto: update

logging:
  pattern:
    level: "%5p [${spring.application.name:},%X{traceId:-},%X{spanId:-}]"

6.3 启动脚本

#!/bin/bash

AGENT_VERSION="1.32.0"
AGENT_JAR="opentelemetry-javaagent.jar"

# 下载 agent(如果不存在)
if [ ! -f "$AGENT_JAR" ]; then
    curl -L -O "https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/download/v$AGENT_VERSION/$AGENT_JAR"
fi

# 启动应用
java -javaagent:$AGENT_JAR \
  -Dotel.service.name=order-service \
  -Dotel.traces.exporter=otlp \
  -Dotel.metrics.exporter=none \
  -Dotel.exporter.otlp.endpoint=http://localhost:4317 \
  -Dotel.traces.sampler=always_on \
  -jar target/order-service-1.0.0.jar

七、附录:Spring Boot 3.x + Micrometer Tracing 对比

7.1 什么是 Micrometer Tracing

Micrometer Tracing 是 Spring Boot 3.x 中引入的分布式追踪抽象层,它可以使用 OpenTelemetry、Zipkin、Jaeger 等作为后端实现。

7.2 快速接入(Spring Boot 3.x)

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-tracing-bridge-otel</artifactId>
</dependency>
<dependency>
    <groupId>io.opentelemetry</groupId>
    <artifactId>opentelemetry-exporter-otlp</artifactId>
</dependency>
spring:
  application:
    name: order-service
  tracing:
    sampling:
      probability: 1.0

management:
  tracing:
    sampling:
      probability: 1.0
  metrics:
    export:
      prometheus:
        enabled: true

7.3 特性对比

特性OpenTelemetry Java AgentMicrometer Tracing
自动插桩深度深(数据库、MQ、HTTP 等)中等(主要是 Spring 生态)
配置复杂度高(环境变量/配置文件)低(Spring Boot 自动配置)
自定义能力强(API 丰富)中等(通过 Tracer 接口)
厂商锁定无(标准协议)无(可切换后端)
迁移路径从 Java Agent 到手动埋点从 Micrometer 切换后端
学习曲线较陡较平缓(Spring 生态)

7.4 如何选择

选择建议:

┌─────────────────────────────────────────────────────┐
│                                                     │
│  场景:                                              │
│                                                     │
│  1. 快速接入,零代码修改 → OpenTelemetry Java Agent    │
│     ├── 优点:无需修改代码,自动追踪                    │
│     └── 缺点:配置复杂,灵活性有限                      │
│                                                     │
│  2. Spring Boot 3.x 项目 → Micrometer Tracing         │
│     ├── 优点:与 Spring 生态无缝集成,配置简单           │
│     └── 缺点:自动插桩深度有限                          │
│                                                     │
│  3. 需要深度自定义 → 手动埋点 + OpenTelemetry API       │
│     ├── 优点:灵活性最高,完全可控                      │
│     └── 缺点:需要手动编写代码                          │
│                                                     │
└─────────────────────────────────────────────────────┘

7.5 混合使用

混合使用方案:

┌─────────────────────────────────────────────────────┐
│                                                     │
│  Java Agent(自动追踪基础链路)                        │
│  +                                                  │
│  Micrometer Tracing(Spring 生态集成)                 │
│  +                                                  │
│  手动埋点(关键业务逻辑)                              │
│                                                     │
│  优点:自动 + 手动相结合,兼顾效率和灵活性               │
│                                                     │
└─────────────────────────────────────────────────────┘

八、总结

8.1 关键知识点

关键知识点:

1. Java Agent 方式:零代码修改,自动追踪
2. @WithSpan 注解:手动标记关键方法
3. Span 自定义属性:添加业务上下文(userId、orderId)
4. 跨服务传递:HTTP 自动传递,非 HTTP 手动传递
5. 采样策略:控制追踪数据量,平衡性能和可观测性

8.2 最佳实践

最佳实践:

┌─────────────────────────────────────────────────────┐
│                                                     │
│  1. 生产环境使用采样策略(避免数据量过大)               │
│  2. 关键业务路径添加自定义属性(便于排查问题)            │
│  3. 使用 @WithSpan 代替手动创建 Span(代码更简洁)       │
│  4. 错误场景进行全量采样(便于分析问题)                 │
│  5. 配合日志使用 TraceId(MDC 自动注入)                │
│                                                     │
└─────────────────────────────────────────────────────┘

8.3 下一步

下一步:

1. 在你的应用中尝试 Java Agent 方式
2. 添加几个 @WithSpan 注解
3. 在关键方法中添加自定义属性
4. 配置采样策略
5. 查看 Jaeger 可视化效果

💡 互动话题:你在项目中使用过分布式链路追踪吗?遇到过什么问题?欢迎在评论区分享!


标题:OpenTelemetry + Java:10 分钟给你的微服务加上分布式链路追踪
作者:jiangyi
地址:http://jiangyi.space/articles/2026/07/23/1784448774789.html
公众号:服务端技术精选

服务端开发博客:后端架构、高并发、性能优化与微服务实战教程

取消