一、可观测性三大支柱
可观测性(Observability)包含三个维度:追踪(Traces)、指标(Metrics)、日志(Logs)。分布式追踪是排查跨服务调用链路问题的核心工具。
1.1 追踪模型:OpenTelemetry
// OpenTelemetry(OTel)的核心概念:
//
// Trace(追踪):一次完整的分布式请求
// ├── Span(跨度):一个服务/组件的工作单元
// │ ├── span.kind:client / server / producer / consumer / internal
// │ ├── span.name:操作名(如 HTTP GET / DB query)
// │ ├── span.attributes:键值对元数据
// │ └── span.status:OK / ERROR
// │
// └── SpanContext(跨度上下文):
// ├── trace_id:全局唯一(决定属于哪个Trace)
// ├── span_id:当前Span唯一
// └── trace_flags:采样标志
// OpenTelemetry Go SDK使用:
import (
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/exporters/jaeger"
"go.opentelemetry.io/otel/sdk/trace"
)
func initTracer() (*trace.TracerProvider, error) {
exp, err := jaeger.New(jaeger.WithCollectorEndpoint(
jaeger.WithEndpoint("http://jaeger:14268/api/traces"),
))
if err != nil {
return nil, err
}
tp := trace.NewTracerProvider(
trace.WithBatcher(exp),
// 采样策略:
// - AlwaysOn:全部采样
// - AlwaysOff:全部不采样
// - TraceIdRatioBased:按比例采样
// - ParentBased:继承父Span的采样决策
trace.WithSampler(trace.TraceIdRatioBased(0.1)), // 10%采样
)
otel.SetTracerProvider(tp)
return tp, nil
}
二、Span的创建与传播
2.1 自动注入与手动创建
// 方法一:Gin中间件自动追踪
import "go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin"
func main() {
r := gin.Default()
r.Use(otelgin.Middleware("my-service",
otelgin.WithTracerProvider(tp),
))
r.GET("/api/orders/:id", func(c *gin.Context) {
// 自动创建Span,追踪整个HTTP请求
order, err := getOrder(c.Param("id"))
c.JSON(200, order)
})
r.Run(":8080")
}
// 方法二:手动Span创建(精确控制范围)
func processOrder(ctx context.Context, orderID string) error {
// 从context中提取父Span
tracer := otel.Tracer("order-service")
ctx, span := tracer.Start(ctx, "processOrder",
trace.WithAttributes(
attribute.String("order.id", orderID),
attribute.String("order.service", "order-processor"),
),
trace.WithSpanKind(trace.SpanKindServer),
)
defer span.End() // 结束时自动记录duration
// 业务逻辑
if err := validateOrder(ctx, orderID); err != nil {
span.RecordError(err) // 记录错误
span.SetStatus(codes.Error, err.Error())
return err
}
// 创建子Span(DB调用)
ctx, dbSpan := tracer.Start(ctx, "db.query",
trace.WithAttributes(
attribute.String("db.system", "mysql"),
attribute.String("db.statement", "SELECT * FROM orders WHERE id=?"),
),
)
order, err := db.QueryOrder(ctx, orderID)
dbSpan.End()
if err != nil {
dbSpan.RecordError(err)
}
span.SetAttributes(attribute.String("order.status", order.Status))
return nil
}
2.2 TraceContext传播
// TraceContext在HTTP Header中的传播
// W3C TraceContext标准:
// traceparent: 00-{trace_id}-{span_id}-{trace_flags}
// traceparent: 00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01
// ↑ ↑ ↑
// trace_id span_id flags(01=采样)
func callDownstream(ctx context.Context, url string) (*http.Response, error) {
// 从当前context提取span
tracer := otel.Tracer("http-client")
ctx, span := tracer.Start(ctx, "HTTP GET",
trace.WithSpanKind(trace.SpanKindClient),
)
defer span.End()
req, _ := http.NewRequestWithContext(ctx, "GET", url, nil)
// 注入TraceContext到HTTP Header
// otel.GetTextMapPropagator().Inject(ctx, carrier)
// carrier实现了TextMapCarrier接口(就是http.Request.Header)
otel.GetTextMapPropagator().Inject(ctx, req.Header)
span.SetAttributes(
attribute.String("http.url", url),
attribute.String("http.method", "GET"),
)
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
span.RecordError(err)
return resp, err
}
span.SetAttributes(
attribute.Int("http.status_code", resp.StatusCode),
)
return resp, nil
}
// 在接收端恢复Trace:
func receiveRequest(c *gin.Context) {
// 从HTTP Header提取TraceContext
ctx := otel.GetTextMapPropagator().Extract(
c.Request.Context(),
propagation.HeaderCarrier(c.Request.Header),
)
// 继续创建子Span,自动关联到原始Trace
tracer := otel.Tracer("receiver-service")
_, span := tracer.Start(ctx, "handleRequest",
trace.WithSpanKind(trace.SpanKindServer),
)
defer span.End()
// 整个调用链的trace_id保持一致
}
三、采样策略与性能优化
// 采样策略选择指南:
//
// ① Head-based Sampling(请求入口采样)
// 优点:简单,入口决定,资源可控
// 缺点:低频请求可能被丢弃(重要请求恰好没采到)
//
// ② Tail-based Sampling(尾部采样)
// 优点:可以根据结果决策(错误请求必采)
// 缺点:需要额外的Collector(如OpenTelemetry Collector)
// Head-based采样配置(适配不同环境):
func newTracerProvider(serviceName string, samplingRate float64) (*trace.TracerProvider, error) {
var sampler trace.Sampler
switch {
case os.Getenv("ENV") == "production":
// 生产:极低采样率 + 错误请求全采
sampler = trace.ParentBased(
trace.TraceIdRatioBased(0.01), // 1%采样
)
case os.Getenv("ENV") == "staging":
sampler = trace.TraceIdRatioBased(0.1) // 10%采样
default:
sampler = trace.AlwaysSample() // 开发环境全采样
}
return trace.NewTracerProvider(
trace.WithSampler(sampler),
), nil
}
// OpenTelemetry Collector配置(尾部采样):
// otel-collector-config.yaml
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
processors:
tail_sampling:
decision_wait: 10s
policies:
- name: errors
type: status_code
status_code: { status_codes: [ERROR] }
- name: slow-traces
type: latency
latency: { threshold_ms: 1000 }
- name: sampled-traces
type: probabilistic
probabilistic: { sampling_percentage: 10 }
exporters:
jaeger:
endpoint: jaeger:14250
service:
pipelines:
traces:
receivers: [otlp]
processors: [tail_sampling]
exporters: [jaeger]