4. 应用接入Jaeger:在Go/Java/Python应用中通过OpenTracing SDK注入追踪代码,创建根Span与子Span
将应用接入Jaeger分布式追踪体系,核心工作是在业务代码中通过OpenTracing标准SDK创建并管理Span。Span是追踪的最小工作单元,一个完整的请求链路由一个根Span(Root Span)和若干子Span(Child Span)构成。本节将分别以Go、Java、Python三种语言为例,演示如何注入追踪代码,并建立正确的父子Span关系。
前置条件:确保应用已引入对应语言的OpenTracing API及Jaeger Tracer实现依赖。Go使用
opentracing-go + jaeger-client-go;Java使用 opentracing-api + jaeger-core + jaeger-tracerresolver;Python使用 opentracing + jaeger-client。
4.1 根Span与子Span的语义模型
在分布式追踪中,根Span代表整个请求的入口,没有父Span;子Span代表请求内部的一个子操作,必须通过子Span上下文(SpanContext)与父Span建立关联。正确的父子关系是后续链路拓扑与根因定位的基础。
4.2 Go语言接入:手动创建根Span与子Span
Go语言通过 opentracing.StartSpanFromContext 或直接调用 tracer.StartSpan 并手动设置父上下文来创建Span。推荐使用 StartSpanFromContext 自动继承父Span。
package main
import (
"context"
"fmt"
"log"
"time"
opentracing "github.com/opentracing/opentracing-go"
"github.com/opentracing/opentracing-go/ext"
"github.com/uber/jaeger-client-go"
jaegercfg "github.com/uber/jaeger-client-go/config"
)
func main() {
// 初始化Jaeger Tracer
cfg := jaegercfg.Configuration{
ServiceName: "order-service",
Sampler: &jaegercfg.SamplerConfig{
Type: jaeger.SamplerTypeConst,
Param: 1,
},
Reporter: &jaegercfg.ReporterConfig{
LogSpans: true,
LocalAgentHostPort: "127.0.0.1:6831",
},
}
tracer, closer, err := cfg.NewTracer()
if err != nil {
log.Fatal(err)
}
defer closer.Close()
opentracing.SetGlobalTracer(tracer)
// 创建根Span
rootSpan := tracer.StartSpan("create-order")
rootCtx := opentracing.ContextWithSpan(context.Background(), rootSpan)
// 在根Span内执行子操作
processPayment(rootCtx)
updateInventory(rootCtx)
rootSpan.Finish()
fmt.Println("追踪完成")
}
func processPayment(ctx context.Context) {
// 从上下文创建子Span,自动继承父Span
span, _ := opentracing.StartSpanFromContext(ctx, "process-payment")
defer span.Finish()
ext.SpanKindRPCClient.Set(span)
span.SetTag("payment.method", "credit_card")
time.Sleep(50 * time.Millisecond) // 模拟业务耗时
}
func updateInventory(ctx context.Context) {
span, _ := opentracing.StartSpanFromContext(ctx, "update-inventory")
defer span.Finish()
ext.SpanKindRPCServer.Set(span)
span.SetTag("inventory.warehouse", "cn-east-1")
time.Sleep(30 * time.Millisecond)
}
关键点:Go中
StartSpanFromContext 会自动从context中提取父SpanContext,无需手动传递Span对象。如果context中没有Span,则创建新的根Span。
4.3 Java语言接入:使用Tracer构建Span树
Java通过 GlobalTracer 或注入的 Tracer 实例创建Span。使用 tracer.buildSpan("operationName").asChildOf(parentSpan).startActive() 建立父子关系。
import io.opentracing.Tracer;
import io.opentracing.Span;
import io.opentracing.util.GlobalTracer;
import io.jaegertracing.Configuration;
public class OrderApplication {
public static void main(String[] args) {
// 初始化Jaeger Tracer
Configuration config = new Configuration("order-service");
Configuration.SamplerConfiguration samplerConfig =
Configuration.SamplerConfiguration.fromEnv()
.withType("const").withParam(1);
Configuration.ReporterConfiguration reporterConfig =
Configuration.ReporterConfiguration.fromEnv()
.withLogSpans(true)
.withAgentHost("127.0.0.1").withAgentPort(6831);
Tracer tracer = config.getTracer();
GlobalTracer.registerIfAbsent(tracer);
// 创建根Span
Span rootSpan = tracer.buildSpan("create-order")
.withTag("order.id", "ORD-2024-001")
.start();
try {
processPayment(rootSpan);
updateInventory(rootSpan);
} finally {
rootSpan.finish();
}
}
private static void processPayment(Span parentSpan) {
// 创建子Span,显式指定父Span
Span childSpan = GlobalTracer.get()
.buildSpan("process-payment")
.asChildOf(parentSpan)
.withTag("payment.method", "credit_card")
.start();
try {
Thread.sleep(50); // 模拟业务
} catch (InterruptedException e) {
childSpan.setTag("error", true);
} finally {
childSpan.finish();
}
}
private static void updateInventory(Span parentSpan) {
Span childSpan = GlobalTracer.get()
.buildSpan("update-inventory")
.asChildOf(parentSpan)
.withTag("inventory.warehouse", "cn-east-1")
.start();
try {
Thread.sleep(30);
} catch (InterruptedException e) {
childSpan.setTag("error", true);
} finally {
childSpan.finish();
}
}
}
注意:Java中
asChildOf() 方法可以接受Span或SpanContext对象。当跨进程传递时,必须使用 tracer.extract() 从HTTP Header中提取SpanContext,再通过 asChildOf(spanContext) 建立关联。
4.4 Python语言接入:上下文管理器与自动传播
Python的OpenTracing SDK支持上下文管理器(with 语句),自动管理Span的生命周期和父子关系。使用 tracer.start_active_span() 或 tracer.start_span() 配合 child_of 参数。
import time
import opentracing
from jaeger_client import Config
def init_tracer(service_name):
config = Config(
config={
'sampler': {
'type': 'const',
'param': 1,
},
'logging': True,
},
service_name=service_name,
)
return config.initialize_tracer()
def process_payment():
# 获取当前活跃Span并创建子Span
with opentracing.tracer.start_active_span('process-payment') as scope:
scope.span.set_tag('payment.method', 'credit_card')
time.sleep(0.05)
def update_inventory():
with opentracing.tracer.start_active_span('update-inventory') as scope:
scope.span.set_tag('inventory.warehouse', 'cn-east-1')
time.sleep(0.03)
def main():
tracer = init_tracer('order-service')
opentracing.tracer = tracer # 设置为全局Tracer
# 创建根Span
with opentracing.tracer.start_active_span('create-order') as scope:
scope.span.set_tag('order.id', 'ORD-2024-001')
process_payment()
update_inventory()
# 等待Tracer发送数据
time.sleep(2)
tracer.close()
if __name__ == '__main__':
main()
Python最佳实践:使用
start_active_span 会自动将新Span设置为当前活跃Span,后续在同一线程中创建的Span会自动成为其子Span。使用 start_span 时需手动指定 child_of 参数。
4.5 三种语言接入对比
| 特性 | Go | Java | Python |
|---|---|---|---|
| 创建根Span | tracer.StartSpan("name") |
tracer.buildSpan("name").start() |
tracer.start_active_span("name") |
| 创建子Span | StartSpanFromContext(ctx, "name") |
buildSpan("name").asChildOf(parent).start() |
start_active_span("name") 自动继承 |
| 上下文传播 | 通过 context.Context |
通过 Span 或 SpanContext |
通过活跃Span栈 |
| 生命周期管理 | 手动 Finish() |
手动 finish() |
上下文管理器自动 finish() |
| Tag注入 | span.SetTag(key, value) |
span.setTag(key, value) |
scope.span.set_tag(key, value) |
4.6 常见错误与避坑指南
- 忘记Finish Span:未调用
Finish()会导致Span在Jaeger UI中显示为“未完成”,影响延迟统计。Python使用上下文管理器可避免此问题。 - 父子关系断裂:在异步协程或新线程中创建Span时,必须手动传递SpanContext,否则会生成独立的根Span。
- Tag与Log混淆:Tag用于描述Span的元数据(如订单ID、用户ID),Log用于记录事件(如异常堆栈)。不要将业务数据写入Log字段。
- 过度埋点:不要为每个函数调用都创建Span,建议只对跨服务调用、数据库访问、外部API请求等关键路径埋点。
4.7 验证追踪数据
代码注入完成后,启动应用并发送测试请求。打开Jaeger UI(默认 http://localhost:16686),选择对应的Service,即可看到生成的Trace。检查以下要点:
- 根Span是否存在:每个请求应有且仅有一个根Span。
- 父子关系是否正确:子Span的父Span ID应与根Span的Span ID一致。
- Tag是否生效:在Span详情中应能看到注入的Tag键值对。
- 耗时是否合理:Span的持续时间应与模拟的业务耗时匹配。
生产建议:在开发环境使用
const 采样器(采样率=1)验证追踪完整性;上线后切换为 probabilistic 采样器(如0.1),避免过量数据导致存储压力。