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 核心步骤
- 获取Tracer: 从OpenTelemetry全局实例获取Tracer对象。
- 创建Span: 使用
spanBuilder创建并启动Span。 - 设置属性/事件: 添加业务标签、错误标记、自定义事件。
- 结束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
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();
// ...
}
4.5 实战:手动 + 自动混合采集
以一个典型的订单服务为例,展示如何结合两种方式实现完整的Trace采集:
| 组件 | Instrumentation方式 | 采集内容 |
|---|---|---|
| HTTP入口(Spring Boot) | 自动(Agent) | 请求路径、状态码、耗时 |
| 数据库访问(JDBC) | 自动(Agent) | SQL语句、执行时间、影响行数 |
| Redis缓存 | 自动(Agent) | 命令、Key、耗时 |
| 核心业务逻辑(订单校验) | 手动(SDK) | 业务属性、自定义事件、异常详情 |
| 消息发送(Kafka) | 自动(Agent) | Topic、Partition、Offset |
4.6 避坑指南
- Span泄漏: 确保每个Span都调用了
end(),使用try-with-resources(Java)或with语句(Python)自动管理。 - Context丢失: 异步回调、线程池、消息队列消费端需要手动传播Context,否则Trace断裂。
- 采样率冲突: 自动Instrumentation和手动Instrumentation使用同一个采样器,避免重复采样或漏采。
- Agent版本兼容: 确保Java Agent版本与使用的框架版本匹配,避免字节码增强失败。