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=traceidratio
export 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_traceidratio
export 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.Sampler
alwaysSample []string
neverSample []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_traceidratio
export OTEL_TRACES_SAMPLER_ARG=0.1 # 初始采样率

后端系统会根据以下因素动态调整:

  • 当前流量
  • 错误率
  • 存储容量
  • 成本预算

采样最佳实践

1. 使用 ParentBased 采样

确保分布式 Trace 的完整性:

export OTEL_TRACES_SAMPLER=parentbased_traceidratio
export 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. 渐进式调整

不要一次性大幅调整采样率:

  1. 从较高采样率开始(如 50%)
  2. 观察存储成本和性能
  3. 逐步降低到合适水平(如 10%)

采样对性能的影响

采样开销

  • CPU 开销: < 1%
  • 内存开销: 可忽略
  • 网络开销: 与采样率成正比

优化建议

  1. 使用批量导出: 减少网络请求
  2. 启用压缩: 降低网络带宽
  3. 合理采样率: 平衡成本和观测性

故障排查

采样率过低

症状: 看不到足够的 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

下一步

参考资源

在Gitlab上编辑本文档