4. Trace数据采集:手动Instrumentation与自动Instrumentation

Trace数据的采集是分布式链路追踪的基石。OpenTelemetry提供了手动Instrumentation(通过SDK在代码中显式埋点)和自动Instrumentation(通过Agent或库机制无侵入注入)两种方式。本章将深入讲解Java/Python SDK的手动埋点、Agent模式的自动注入,以及Span与Context的传播机制。

4.1 手动Instrumentation:Java SDK示例

手动Instrumentation要求开发者在业务代码中显式创建Span、设置属性、记录事件。这种方式提供了最大的灵活性和控制力,适合需要精细追踪的关键路径。

适用场景: 核心交易链路、自定义业务逻辑、需要额外标签或事件的复杂流程。

4.1.1 核心步骤

  1. 获取Tracer: 从OpenTelemetry全局实例获取Tracer对象。
  2. 创建Span: 使用spanBuilder创建并启动Span。
  3. 设置属性/事件: 添加业务标签、错误标记、自定义事件。
  4. 结束Span: 在finally块中关闭Span,确保上报。

4.1.2 Java代码示例

import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.context.Scope;

public class OrderService {
    private final Tracer tracer =
            OpenTelemetry.getGlobal().getTracer("order-service");

    public void createOrder(String orderId) {
        // 1. 创建Span,命名遵循语义约定
        Span span = tracer.spanBuilder("createOrder")
                .setAttribute("order.id", orderId)
                .setAttribute("order.type", "normal")
                .startSpan();

        // 2. 将Span放入Context,使子Span自动继承
        try (Scope scope = span.makeCurrent()) {
            // 业务逻辑:数据库写入、调用下游
            processPayment(orderId);
            updateInventory(orderId);

            // 3. 记录事件
            span.addEvent("order.created",
                    Attributes.of(AttributeKey.stringKey("order.id"), orderId));
        } catch (Exception e) {
            // 4. 记录错误
            span.recordException(e);
            span.setStatus(StatusCode.ERROR, "订单创建失败");
            throw e;
        } finally {
            // 5. 必须结束Span
            span.end();
        }
    }
}

4.2 手动Instrumentation:Python SDK示例

Python SDK的用法与Java高度一致,但语法更简洁。使用with语句可以自动管理Span的生命周期。

4.2.1 Python代码示例

from opentelemetry import trace
from opentelemetry.trace import Status, StatusCode

tracer = trace.get_tracer(__name__)

def create_order(order_id: str):
    # 1. 创建Span
    with tracer.start_as_current_span("createOrder") as span:
        span.set_attribute("order.id", order_id)
        span.set_attribute("order.type", "normal")

        try:
            # 业务逻辑
            process_payment(order_id)
            update_inventory(order_id)

            # 2. 记录事件
            span.add_event("order.created", {"order.id": order_id})
        except Exception as e:
            # 3. 记录异常
            span.record_exception(e)
            span.set_status(Status(StatusCode.ERROR, "订单创建失败"))
            raise
常见陷阱: 忘记在finally中调用span.end()(Java)或离开with块(Python),会导致Span泄漏,内存占用上升且Trace不完整。

4.3 自动Instrumentation:Agent模式

自动Instrumentation通过字节码增强(Java Agent)或猴子补丁(Python)的方式,在运行时自动拦截常见框架的调用,无需修改业务代码。适合快速接入、覆盖范围广的场景。

对比维度 手动Instrumentation 自动Instrumentation
侵入性 高,需修改代码 低,零代码修改
灵活性 高,可自定义任意Span 中,仅支持预置框架
覆盖范围 按需埋点 全量拦截(HTTP、gRPC、DB等)
性能开销 可控,仅埋点处 略高,需字节码增强
适用阶段 核心链路、定制需求 快速接入、通用组件

4.3.1 Java Agent配置

在JVM启动参数中添加Agent jar包,即可自动拦截Tomcat、Spring Boot、gRPC、JDBC等框架。

# JVM启动参数
-javaagent:path/to/opentelemetry-javaagent.jar
-Dotel.service.name=order-service
-Dotel.traces.exporter=otlp
-Dotel.exporter.otlp.endpoint=http://otel-collector:4317

4.3.2 Python自动Instrumentation

通过opentelemetry-instrument命令启动应用,自动注入Flask、Django、Requests等库的追踪能力。

# 安装自动Instrumentation包
pip install opentelemetry-distro opentelemetry-exporter-otlp

# 使用自动Instrumentation启动应用
opentelemetry-instrument \
    --service_name order-service \
    --traces_exporter otlp \
    python app.py
最佳实践: 推荐自动 + 手动混合模式。自动Instrumentation覆盖通用组件(HTTP、DB、消息队列),手动Instrumentation补充核心业务逻辑的自定义Span,实现全面且精细的链路追踪。

4.4 Span与Context传播

Context传播是分布式追踪的核心机制,确保跨进程、跨线程的Span能够串联成完整的Trace。OpenTelemetry通过W3C TraceContext标准协议实现传播。

4.4.1 传播原理

  • TraceId: 全局唯一标识,贯穿整个请求链路。
  • SpanId: 当前Span的唯一标识。
  • TraceFlags: 采样标记,控制是否记录Trace。
  • Propagator: 负责将Context序列化到载体(HTTP Header、消息队列属性)中。

4.4.2 HTTP传播示例

服务A调用服务B时,OpenTelemetry自动将TraceContext注入HTTP Header:

# 服务A发出的HTTP请求Header
traceparent: 00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01
tracestate: rojo=00f067aa0ba902b7

服务B收到请求后,从Header中提取Context,创建子Span并关联到同一个Trace。

4.4.3 手动传播(跨线程/消息队列)

当需要手动传递Context时,使用Context.export和Context.import:

// Java:手动传播Context到另一个线程
Context currentContext = Context.current();
TextMapPropagator propagator = OpenTelemetry.getGlobal().getPropagators()
        .getTextMapPropagator();

// 导出到Map
Map<String, String> carrier = new HashMap<>();
propagator.inject(currentContext, carrier, Map::put);

// 在另一个线程中导入
Context extractedContext = propagator.extract(
        Context.current(), carrier, Map::get);
try (Scope scope = extractedContext.makeCurrent()) {
    // 在此作用域内创建的Span会自动关联到原Trace
    Span childSpan = tracer.spanBuilder("async-task").startSpan();
    // ...
}
Span与Context传播流程 服务A(上游) 创建Span A HTTP Header / 消息 traceparent / tracestate 服务B(下游) 创建Span B(子Span) Context内部结构 TraceId(全局唯一) SpanId(当前Span) TraceFlags(采样) Tracestate(扩展) 传播方式 HTTP(W3C TraceContext) 自动注入Header 消息队列(Kafka/RabbitMQ) 注入消息属性 gRPC(Metadata) 自动注入二进制Context

4.5 实战:手动 + 自动混合采集

以一个典型的订单服务为例,展示如何结合两种方式实现完整的Trace采集:

组件 Instrumentation方式 采集内容
HTTP入口(Spring Boot) 自动(Agent) 请求路径、状态码、耗时
数据库访问(JDBC) 自动(Agent) SQL语句、执行时间、影响行数
Redis缓存 自动(Agent) 命令、Key、耗时
核心业务逻辑(订单校验) 手动(SDK) 业务属性、自定义事件、异常详情
消息发送(Kafka) 自动(Agent) Topic、Partition、Offset
效果: 自动Instrumentation覆盖了80%的通用组件,手动Instrumentation补充了20%的核心业务细节。最终在Jaeger或Grafana Tempo中可以看到从HTTP入口到数据库、缓存、消息队列的完整Trace,且每个Span都携带了丰富的业务标签,便于故障定位。

4.6 避坑指南

  • Span泄漏: 确保每个Span都调用了end(),使用try-with-resources(Java)或with语句(Python)自动管理。
  • Context丢失: 异步回调、线程池、消息队列消费端需要手动传播Context,否则Trace断裂。
  • 采样率冲突: 自动Instrumentation和手动Instrumentation使用同一个采样器,避免重复采样或漏采。
  • Agent版本兼容: 确保Java Agent版本与使用的框架版本匹配,避免字节码增强失败。