从零搭建分布式链路追踪系统:OpenTelemetry实战与Jaeger集成
引言
在微服务架构中,一个请求往往跨越多个服务,定位性能瓶颈和错误变得异常困难。分布式链路追踪系统应运而生,它能记录请求在各服务间的完整调用链路。本文将带你从零开始,使用OpenTelemetry标准采集追踪数据,并集成Jaeger进行可视化展示。无论你是运维还是开发者,都能通过本文快速搭建一套生产级的链路追踪体系。

1. 核心概念
- Trace:代表一个请求的完整调用链,由多个Span组成。
- Span:调用链中的单个操作单元,包含开始时间、结束时间、标签等信息。
- Trace Context:用于在服务间传递Trace ID和Span ID,保证链路连续性。
OpenTelemetry是CNCF的观测性标准,提供统一的API和SDK,支持多种后端(如Jaeger、Zipkin)。
2. 环境准备
2.1 安装Jaeger
使用Docker快速启动Jaeger all-in-one实例:
docker run -d --name jaeger \
-e COLLECTOR_ZIPKIN_HOST_PORT=:9411 \
-p 6831:6831/udp \
-p 6832:6832/udp \
-p 5778:5778 \
-p 16686:16686 \
-p 4317:4317 \
-p 4318:4318 \
-p 14250:14250 \
-p 14268:14268 \
-p 14269:14269 \
-p 9411:9411 \
jaegertracing/all-in-one:1.50访问 http://localhost:16686 即可打开Jaeger UI。
2.2 开发语言与SDK
本文以Go语言为例,其他语言类似。确保Go 1.18+。
3. Go应用集成OpenTelemetry
3.1 添加依赖
go get go.opentelemetry.io/otel \
go.opentelemetry.io/otel/exporters/jaeger \
go.opentelemetry.io/otel/sdk/trace \
go.opentelemetry.io/otel/sdk/resource3.2 初始化TracerProvider
创建一个 tracer.go 文件:
package main
import (
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/exporters/jaeger"
"go.opentelemetry.io/otel/sdk/resource"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
semconv "go.opentelemetry.io/otel/semconv/v1.17.0"
)
func initTracer() (*sdktrace.TracerProvider, error) {
exp, err := jaeger.New(jaeger.WithCollectorEndpoint(jaeger.WithEndpoint("http://localhost:14268/api/traces")))
if err != nil {
return nil, err
}
tp := sdktrace.NewTracerProvider(
sdktrace.WithBatcher(exp),
sdktrace.WithResource(resource.NewWithAttributes(
semconv.SchemaURL,
semconv.ServiceNameKey.String("my-service"),
attribute.String("environment", "development"),
)),
)
otel.SetTracerProvider(tp)
return tp, nil
}3.3 创建Span
假设我们有一个HTTP处理函数:
func handleRequest(w http.ResponseWriter, r *http.Request) {
tracer := otel.Tracer("my-app")
ctx, span := tracer.Start(r.Context(), "handleRequest")
defer span.End()
// 模拟业务逻辑
doWork(ctx)
span.SetAttributes(attribute.String("http.method", r.Method))
w.Write([]byte("Hello, OpenTelemetry!"))
}
func doWork(ctx context.Context) {
_, span := otel.Tracer("my-app").Start(ctx, "doWork")
defer span.End()
// 模拟耗时操作
span.AddEvent("work started")
// ...
span.AddEvent("work finished")
}3.4 传播Context
在HTTP客户端调用时,需要将trace上下文通过HTTP头传递:
import (
"net/http"
"go.opentelemetry.io/otel/propagation"
)
func callAnotherService(ctx context.Context) error {
req, _ := http.NewRequestWithContext(ctx, "GET", "http://other-service/api", nil)
otel.GetTextMapPropagator().Inject(ctx, propagation.HeaderCarrier(req.Header))
client := http.Client{}
resp, err := client.Do(req)
// ...
}4. 配置与启动
在 main.go 中调用初始化:
func main() {
tp, err := initTracer()
if err != nil {
log.Fatal(err)
}
defer func() {
if err := tp.Shutdown(context.Background()); err != nil {
log.Fatal(err)
}
}()
http.HandleFunc("/", handleRequest)
log.Println("Server starting on :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}运行程序,然后访问 http://localhost:8080,再打开Jaeger UI即可看到trace。
5. 验证与排查
在Jaeger UI中可以搜索服务、查看Span详情、火焰图等。如果没看到数据,检查:
- Jaeger collector地址是否正确?
- 网络是否可达?
- 采样率是否为1(默认是1/1000)?
6. 高级用法
6.1 采样策略
可以配置基于头部的采样或概率采样:
import "go.opentelemetry.io/otel/sdk/trace"
tp := sdktrace.NewTracerProvider(
sdktrace.WithSampler(sdktrace.AlwaysSample()),
)6.2 Baggage传递
通过Context传递自定义键值对:
import "go.opentelemetry.io/otel/baggage"
member, _ := baggage.NewMember("user", "alice")
bag, _ := baggage.New(member)
ctx := baggage.ContextWithBaggage(ctx, bag)总结
通过本文,你已从零搭建了一套基于OpenTelemetry和Jaeger的分布式链路追踪系统。从概念到代码,再到验证,每一步都清晰的实践。这套方案兼容性强、社区活跃,适合生产环境。下一步可以探索多语言集成、自定义采样以及与其他后端(如Prometheus)的协同。
觉得内容不错?我要