OpenTelemetry 采样策略
采样是控制 OpenTelemetry 数据采集量的重要机制。合理的采样策略可以在保证观测性的同时,降低存储成本和性能开销。
什么是采样?
采样决定哪些 Trace 应该被记录和导出。OpenTelemetry 支持多种采样策略,可以根据业务需求选择合适的策略。
采样策略类型
1. AlwaysOn(总是采样)
采样所有 Trace,适用于:
- 开发环境
- 低流量应用
- 需要完整数据的场景
配置:
export OTEL_TRACES_SAMPLER=always_on
代码配置 (Go):
sdktrace.WithSampler(sdktrace.AlwaysSample())
代码配置 (Python):
TracerProvider(sampler=AlwaysOnSampler(),)
代码配置 (Java):
SdkTracerProvider.builder().setSampler(Sampler.alwaysOn())
代码配置 (JavaScript):
new NodeTracerProvider({sampler: new AlwaysOnSampler(),})
代码配置 (.NET):
.WithTracing(tracing => tracing.SetSampler(new AlwaysOnSampler()))
2. AlwaysOff(从不采样)
不采样任何 Trace,适用于:
- 禁用追踪
- 性能测试
- 故障排查时临时禁用
配置:
export OTEL_TRACES_SAMPLER=always_off
3. TraceIDRatioBased(基于 Trace ID 的概率采样)
基于 Trace ID 的概率采样,确保同一 Trace 的所有 Span 采样决策一致。
采样率: 0.0 ~ 1.0
0.0: 不采样0.1: 10% 采样1.0: 100% 采样
配置:
export OTEL_TRACES_SAMPLER=traceidratioexport OTEL_TRACES_SAMPLER_ARG=0.1 # 10% 采样
代码配置 (Go):
sdktrace.WithSampler(sdktrace.TraceIDRatioBased(0.1))
代码配置 (Python):
TracerProvider(sampler=TraceIdRatioBased(0.1),)
代码配置 (Java):
SdkTracerProvider.builder().setSampler(Sampler.traceIdRatioBased(0.1))
代码配置 (JavaScript):
new NodeTracerProvider({sampler: new TraceIdRatioBasedSampler(0.1),})
代码配置 (.NET):
.WithTracing(tracing => tracing.SetSampler(new TraceIdRatioBasedSampler(0.1)))
4. ParentBased(父级采样)
根据父 Span 的采样决策来决定是否采样,保证 Trace 的完整性。
配置:
export OTEL_TRACES_SAMPLER=parentbased_traceidratioexport OTEL_TRACES_SAMPLER_ARG=0.1
代码配置 (Go):
sdktrace.WithSampler(sdktrace.ParentBased(sdktrace.TraceIDRatioBased(0.1),))
代码配置 (Python):
TracerProvider(sampler=ParentBased(ROOT=TraceIdRatioBased(0.1)),)
代码配置 (Java):
SdkTracerProvider.builder().setSampler(Sampler.parentBased(Sampler.traceIdRatioBased(0.1)))
代码配置 (JavaScript):
new NodeTracerProvider({sampler: new ParentBasedSampler({root: new TraceIdRatioBasedSampler(0.1),}),})
代码配置 (.NET):
.WithTracing(tracing => tracing.SetSampler(new ParentBasedSampler(new TraceIdRatioBasedSampler(0.1))))
推荐: 这是默认和推荐的采样策略,确保分布式 Trace 的完整性。
采样率选择指南
根据流量选择
| 流量级别 | 推荐采样率 | 说明 |
|---|---|---|
| 低流量 (< 100 req/s) | 50-100% | 可以高采样率 |
| 中等流量 (100-1000 req/s) | 10-50% | 平衡成本和观测性 |
| 高流量 (1000-10000 req/s) | 1-10% | 降低采样率 |
| 极高流量 (> 10000 req/s) | 0.1-1% | 最小采样率 |
根据环境选择
| 环境 | 推荐采样率 | 说明 |
|---|---|---|
| 开发环境 | 100% | 完整数据用于调试 |
| 测试环境 | 50% | 足够的测试数据 |
| 预发布环境 | 10% | 接近生产环境 |
| 生产环境 | 1-10% | 根据流量调整 |
根据业务重要性选择
对于关键业务路径,可以使用更高的采样率:
# 关键路径:100% 采样# 普通路径:10% 采样# 健康检查:0% 采样
自定义采样策略
基于 Span 名称的采样
Go 示例:
type CustomSampler struct {defaultSampler sdktrace.SampleralwaysSample []stringneverSample []string}func (s *CustomSampler) ShouldSample(p sdktrace.SamplingParameters) sdktrace.SamplingResult {spanName := p.Name// 总是采样for _, name := range s.alwaysSample {if strings.Contains(spanName, name) {return sdktrace.SamplingResult{Decision: sdktrace.RecordAndSample,Tracestate: p.ParentContext.TraceState(),}}}// 从不采样for _, name := range s.neverSample {if strings.Contains(spanName, name) {return sdktrace.SamplingResult{Decision: sdktrace.Drop,Tracestate: p.ParentContext.TraceState(),}}}return s.defaultSampler.ShouldSample(p)}
Python 示例:
class CustomSampler(Sampler):def __init__(self, default_rate=0.1):self.default_sampler = TraceIdRatioBased(default_rate)self.always_sample = ['/checkout', '/payment']self.never_sample = ['/health', '/metrics']def should_sample(self, context, trace_id, name, attributes, links):# 总是采样if any(name in span_name for span_name in self.always_sample):return SamplingResult(SamplingDecision.RECORD_AND_SAMPLE)# 从不采样if any(name in span_name for span_name in self.never_sample):return SamplingResult(SamplingDecision.DROP)# 默认采样return self.default_sampler.should_sample(context, trace_id, name, attributes, links)
基于属性的采样
Go 示例:
func (s *AttributeBasedSampler) ShouldSample(p sdktrace.SamplingParameters) sdktrace.SamplingResult {// 检查重要属性for _, attr := range p.Attributes {if string(attr.Key) == "important" && attr.Value.AsBool() {return sdktrace.SamplingResult{Decision: sdktrace.RecordAndSample,Tracestate: p.ParentContext.TraceState(),}}// 错误总是采样if string(attr.Key) == "error" && attr.Value.AsBool() {return sdktrace.SamplingResult{Decision: sdktrace.RecordAndSample,Tracestate: p.ParentContext.TraceState(),}}}return s.defaultSampler.ShouldSample(p)}
基于 HTTP 状态码的采样
func (s *HTTPStatusSampler) ShouldSample(p sdktrace.SamplingParameters) sdktrace.SamplingResult {for _, attr := range p.Attributes {if string(attr.Key) == "http.status_code" {statusCode := attr.Value.AsInt64()// 错误状态码总是采样if statusCode >= 400 {return sdktrace.SamplingResult{Decision: sdktrace.RecordAndSample,Tracestate: p.ParentContext.TraceState(),}}}}return s.defaultSampler.ShouldSample(p)}
动态采样
某些后端系统支持动态采样,可以根据实时流量和错误率自动调整采样率。
配置示例
# 使用后端动态采样export OTEL_TRACES_SAMPLER=parentbased_traceidratioexport OTEL_TRACES_SAMPLER_ARG=0.1 # 初始采样率
后端系统会根据以下因素动态调整:
- 当前流量
- 错误率
- 存储容量
- 成本预算
采样最佳实践
1. 使用 ParentBased 采样
确保分布式 Trace 的完整性:
export OTEL_TRACES_SAMPLER=parentbased_traceidratioexport OTEL_TRACES_SAMPLER_ARG=0.1
2. 关键路径高采样
对关键业务路径使用更高采样率:
sampler := NewCustomSampler(0.1, // 默认 10%[]string{"/checkout", "/payment"}, // 100% 采样[]string{"/health", "/metrics"}, // 0% 采样)
3. 错误总是采样
确保所有错误都能被追踪:
// 在采样器中检查错误属性if hasError(attributes) {return RecordAndSample}
4. 监控采样效果
定期检查采样率是否合适:
- 存储成本是否在预算内
- 是否有足够的错误样本
- 关键路径是否被充分采样
5. 渐进式调整
不要一次性大幅调整采样率:
- 从较高采样率开始(如 50%)
- 观察存储成本和性能
- 逐步降低到合适水平(如 10%)
采样对性能的影响
采样开销
- CPU 开销: < 1%
- 内存开销: 可忽略
- 网络开销: 与采样率成正比
优化建议
- 使用批量导出: 减少网络请求
- 启用压缩: 降低网络带宽
- 合理采样率: 平衡成本和观测性
故障排查
采样率过低
症状: 看不到足够的 Trace 数据
解决方案:
# 临时提高采样率export OTEL_TRACES_SAMPLER_ARG=1.0 # 100%
采样率过高
症状: 存储成本过高,性能下降
解决方案:
# 降低采样率export OTEL_TRACES_SAMPLER_ARG=0.01 # 1%
Trace 不完整
症状: Trace 中间断,缺少部分 Span
原因: 没有使用 ParentBased 采样
解决方案:
export OTEL_TRACES_SAMPLER=parentbased_traceidratio
下一步
- 自定义埋点 - 添加业务特定的追踪信息
- 性能优化 - 降低监控开销
- Kubernetes 部署(如果使用 Kubernetes):
- OpenTelemetry Operator 概述 - 了解如何在 Kubernetes 中使用 Operator
- 自动注入 - 在 Instrumentation 中配置采样