链路追踪
问题
分布式系统中如何追踪一个请求的完整调用链?Go 中如何集成?
答案
核心概念
| 概念 | 说明 |
|---|---|
| Trace | 一个完整请求的调用链,全局唯一 TraceID |
| Span | 调用链中的一个操作,有 SpanID 和 ParentSpanID |
| Context Propagation | 在服务间传递 TraceID / SpanID |
OpenTelemetry 集成
OpenTelemetry(OTel)是现在的标准:
import (
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
"go.opentelemetry.io/otel/sdk/trace"
)
func initTracer() func() {
exporter, _ := otlptracehttp.New(context.Background(),
otlptracehttp.WithEndpoint("jaeger:4318"),
otlptracehttp.WithInsecure(),
)
tp := trace.NewTracerProvider(
trace.WithBatcher(exporter),
trace.WithResource(resource.NewWithAttributes(
semconv.SchemaURL,
semconv.ServiceName("user-service"),
)),
)
otel.SetTracerProvider(tp)
otel.SetTextMapPropagator(propagation.TraceContext{})
return func() { tp.Shutdown(context.Background()) }
}
gRPC 集成
import "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
// 服务端
server := grpc.NewServer(
grpc.StatsHandler(otelgrpc.NewServerHandler()),
)
// 客户端
conn, _ := grpc.Dial(target,
grpc.WithStatsHandler(otelgrpc.NewClientHandler()),
)
Gin 集成
import "go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin"
r := gin.Default()
r.Use(otelgin.Middleware("api-gateway"))
手动创建 Span
func GetUserProfile(ctx context.Context, userID string) (*Profile, error) {
// 创建子 Span
ctx, span := otel.Tracer("user-service").Start(ctx, "GetUserProfile")
defer span.End()
// 添加属性
span.SetAttributes(attribute.String("user.id", userID))
// 调用数据库(也会创建子 Span)
profile, err := queryDB(ctx, userID)
if err != nil {
span.RecordError(err)
span.SetStatus(codes.Error, err.Error())
return nil, err
}
return profile, nil
}
Context 传播
TraceID 通过 HTTP Header / gRPC Metadata 在服务间传递:
HTTP Header:
traceparent: 00-{traceId}-{spanId}-01
gRPC Metadata:
traceparent → 00-abc123-def456-01
关键
所有函数调用都要传递 ctx context.Context,这样 Span 才能形成父子关系。丢失 ctx = 断链。
常见面试问题
Q1: TraceID 是怎么跨服务传递的?
答案:通过 Context Propagation。HTTP 服务在 Header 中注入 traceparent;gRPC 在 Metadata 中注入。下游服务提取 Header,恢复 Trace 上下文,创建新 Span 时自动成为子 Span。
Q2: OpenTelemetry vs Jaeger vs Zipkin?
答案:
- OpenTelemetry:统一标准(API + SDK + 协议),现在的首选
- Jaeger:Trace 后端存储和 UI,接收 OTel 数据
- Zipkin:类似 Jaeger 的后端
推荐组合:OTel SDK → Jaeger Backend → Jaeger UI