线上链路采样率需配置sdktrace.parentbased才能传递trace上下文,否则下游服务无法看到span;异步逻辑必须显式传ctx,否则断链;采样率应支持运行时热更新,避免硬编码;otlp exporter需确保collector可达且正确初始化tracerprovider。

线上服务的链路采样率不是设个数字就完事——不配 sdktrace.ParentBased,下游服务根本看不到 trace;不显式传 ctx 到 goroutine,异步逻辑直接断链;otel.SetTracerProvider 漏掉,整个服务就“静默失明”。这些坑都踩过,才敢说采样率控制这事不能靠猜。
为什么设置了 TraceIDRatioBased 还是看不到下游 Span
因为没包一层 sdktrace.ParentBased。Go SDK 默认的 sdktrace.TraceIDRatioBased(0.1) 只对 root span(比如 HTTP 入口)做采样决策,一旦这个入口被跳过,B 服务和 C 服务哪怕自己想采,也收不到 trace context,整条链路就只剩第一个点。
- 正确写法是:
sdktrace.ParentBased(sdktrace.TraceIDRatioBased(0.01)) - 错误写法:
sdktrace.TraceIDRatioBased(0.01)或sdktrace.ParentBased(sdktrace.AlwaysSample()) -
ParentBased的逻辑是:有父 span 就继承,没父 span 才按比率决定——这才是生产环境能连上的基础
HTTP handler 里 Span 断在第一次外部调用之后
本质是 context 没传下去。Go 的 OpenTelemetry 不自动跨 goroutine 传递 span,哪怕你用了 req.Context(),进 go func() {} 就丢。
- HTTP handler 中启动异步任务时,必须显式传参:
go doWork(ctx),而不是go doWork(context.Background()) - 用
otelhttp.NewHandler包裹 handler,它会自动注入traceparentheader;但你自己写的http.Client请求,得手动带 ctx:req = req.WithContext(ctx) - 如果用了 Gin/Echo,确认中间件是否调用了
otel.GetTextMapPropagator().Inject(),否则 header 格式错(比如大小写、空格)也会导致下游解析失败
采样率写死在代码里上线就出问题
硬编码 sdktrace.TraceIDRatioBased(0.05) 看似省事,但大促时没法临时提采样率抓问题,流量突增时又可能压垮 collector。SDK 本身不支持运行时热更新,但可以绕过。
- 用一个可变变量包一层自定义
sdktrace.Sampler,例如内部读取atomic.Value存当前 rate - 暴露一个 HTTP endpoint(如
/debug/otel/sampling)或监听SIGHUP,触发 rate 重载 - 环境变量注入时加校验:
if rate 1.0 { rate = 0.001 },避免配置失误打爆链路系统
OTLP exporter 配好了却收不到数据
不是代码没跑,而是 collector 连不上——OpenTelemetry Go SDK 默认失败静默,超时 5 秒后直接丢 trace,不报错也不 log。
- 先用
curl -v http://localhost:4318/v1/traces测试 collector 是否可达,注意协议和端口:4318是 HTTP,4317是 gRPC - 检查是否漏了
otel.SetTracerProvider(tp),漏掉这句所有Tracer().Start()返回空 span,日志里完全空白 - 本地开发建议先用
stdouttrace.NewExporter,看到输出再切 OTLP,避免过早陷入网络配置漩涡
真正难的不是写几行初始化代码,而是理解 ParentBased 怎么救链路、ctx 在哪一级必须显式透传、以及采样率背后那个“上游不采,下游白忙”的传播逻辑。这些地方一松懈,Jaeger 里就只剩孤零零的入口 Span。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











