90%的Jaeger链路断裂源于context未正确传递:跨goroutine需显式传req.Context(),DB要用otelmysql.Wrap(),HTTP调用须用otelhttp.NewClient(),否则span孤立为root节点。

Jaeger UI里看不到完整Trace,链路断在哪了
90%的“链路只有一跳”问题,根源不在Jaeger本身,而在context没传下去。Go不自动继承context,跨goroutine、DB查询、HTTP client调用时漏传req.Context(),span就变成孤立root节点。
关键检查点:
- HTTP handler里是否用
otelgin.Middleware()(Gin)或otelhttp.NewHandler()(net/http)自动提取traceparent头?手动tracer.Extract()容易漏字段 - 发起下游请求前,是否用
otelhttp.NewClient()包装client?直接用http.DefaultClient不会inject trace header - 启动goroutine时是否显式传入带span的context?
go doWork(ctx)不能写成go doWork(context.Background()) - DB操作是否wrap:MySQL用
otelmysql.Wrap(db),PostgreSQL用otelpostgresql.Wrap(db)?裸*sql.DB不参与span生命周期
采样率设太高导致Jaeger UI卡顿或数据丢失
默认jaeger.SamplerTypeConst配Param: 1(100%采样)在QPS > 50的生产环境极易撑爆内存和网络带宽,Collector开始丢包,UI加载缓慢甚至空白。
推荐配置:
立即学习“go语言免费学习笔记(深入)”;
- 开发阶段:用
stdoutexporter.NewExporter()直连控制台输出JSON,绕过UI刷新延迟,验证span结构是否正确 - 测试环境:改用
jaeger.SamplerTypeRateLimiting,Param: 10(每秒最多10个span) - 生产环境:必须上
jaeger.SamplerTypeProbabilistic,Param: 0.01(1%采样),或对接jaeger.RemoteSampler动态调整 - 避免在for循环里
tracer.StartSpan()——改用单个span +span.SetAttributes(attribute.Int("loop_count", n))
服务名写错导致Jaeger UI里搜不到或分组混乱
服务名不是别名,是Jaeger后端做存储索引和UI过滤的唯一键。写错大小写、加空格、混用下划线和连字符,Collector会静默拒绝上报,日志里可能只显示"invalid service name"。
必须统一规范:
- 从环境变量读:
os.Getenv("SERVICE_NAME"),禁止硬编码 - 命名规则:全小写、只含字母数字和短横线(
order-service✅,Order_Service❌) - 确保所有同逻辑服务的实例(K8s Pod、Docker容器)使用完全一致的服务名
- 本地调试时先curl
http://localhost:14268/api/traces看上报是否成功,再查UI
本地调试连不上Jaeger Agent,UDP端口6831被防火墙拦截
旧版jaeger-client-go默认走UDP发到localhost:6831,但Docker容器内localhost指向自己,不是宿主机;Mac/Windows上Docker Desktop的网络NAT也常导致UDP丢包,且无错误日志,span静默消失。
实操方案:
- 新项目一律禁用
jaeger-client-go,改用OpenTelemetry SDK + Jaeger导出器 - 导出器优先选HTTP协议:
otlphttp.NewClient(otlphttp.WithEndpoint("localhost:14268")),避开UDP不可靠性 - Docker Compose部署Jaeger时,确保
ports暴露14268:14268(HTTP接收端口),而非仅6831/udp - 本地验证连通性:
curl -X POST http://localhost:14268/api/traces -H "Content-Type: application/json" -d '{}'应返回202 Accepted
tracer.Start(ctx, "db.query")而用了tracer.Start(context.Background(), ...),它就会脱离当前trace,变成新trace的root。这种断裂肉眼难辨,只能靠traceID在日志里逐段比对。


















