OpenTelemetry 采样详解:头部采样与尾部采样怎么选

全量采集链路在高并发下成本高企,采样是控制可观测性支出的核心手段。本文系统对比头部采样(Head-based)与尾部采样(Tail-based)的原理、优劣与 OpenTelemetry 实现方式,并给出结合观测云控制链路成本的落地建议。

最佳实践
OpenTelemetry 采样详解:头部采样与尾部采样怎么选封面

采样(Sampling)是在成本与问题定位能力之间取舍,只保留一部分链路数据的策略——它的本质是一道经济题:链路数据的价值密度极不均匀(正常与异常请求的比例依实际业务而异),而全量存储的成本随流量线性上涨。

核心要点速览

  • 采样的动机:控制存储与传输成本,同时保住排障所需的关键链路;
  • 头部采样在请求入口处决策,简单高效但"盲选"——可能丢掉后来出错的链路;
  • 尾部采样等待一定窗口后按已收到的数据(错误、延迟、路径)决策,精准但需要缓存与汇聚;
  • OTel 的尾部采样依赖 Collector 的 tail_sampling 处理器,且要求同一条链路的所有 Span 汇聚到同一实例。

为什么必须认真对待采样?

一个中等规模的微服务系统,每秒产生数万 Span 稀松平常。以每秒 10,000 Span、每条 1–2 kB(十进制)估算,一天约为 864 GB–1.728 TB,尚未考虑压缩与额外开销。更要命的是,这些数据里绝大多数是重复的健康请求——存它们几乎不产生洞察,却实打实地消耗存储与带宽。

但采样不是"砍数据"这么简单,采错了就是事故:排障时发现报错链路偏偏没采到,可观测性体系瞬间失去信任。因此采样策略的核心问题是——在什么时候、依据什么信息做丢弃决策。

头部采样:请求起点处拍板

头部采样在请求进入系统的第一站就决定是否采集,决策随上下文一路传播,下游服务"听指挥":在使用遵循父级决策的采样配置时按该决策处理,独立配置仍可能产生分叉。

优点:决策早、开销小——可减少 Span 记录和导出开销,但仍有上下文传播等成本,指标和日志也不会因此自动停止;实现简单,SDK 内置概率采样器开箱即用。

缺点:决策是盲目的。入口时谁也不知道这个请求会不会在第八跳报错、会不会慢到突破 SLO。用 10% 概率头部采样,错误链路也只有 10% 被留下——恰恰是你最需要的那些。

尾部采样:看清结果再决定

尾部采样在决策等待窗口后基于已收到的 Span(有没有错误、耗时多少、走了哪些服务)再做决策,通常由 Collector 先缓存 Span、再按策略筛选:

  • 保留所有含 5xx 错误的链路;
  • 保留耗时超过 SLO 阈值的慢链路;
  • 对核心业务路径提高保留比例;
  • 付费客户的链路多留,免费用户的少留。

优点:决策有依据,可按已收到的信息提高关键链路保留率,但晚到、丢失、重启或容量压力仍可能导致遗漏。
代价:所有 Span 得先收集缓存起来(内存开销),且同一条 Trace 的 Span 必须汇聚到同一个 Collector 实例才能拼出"完整链路"做判断——这要求按 Trace ID 做一致性路由,多实例部署时复杂度显著上升。

OpenTelemetry 中如何配置采样?

头部采样在 SDK 侧一行配置搞定:

from opentelemetry.sdk.trace.sampling import ParentBased, TraceIdRatioBased
# 根链路采样25%,子Span遵循父级决策;需传入TracerProvider
sampler = ParentBased(TraceIdRatioBased(0.25))

也支持父级采样(ParentBased,跟随上游决策,微服务场景必选)、支持相应环境配置的 SDK 通常在启动/初始化时读取 OTEL_TRACES_SAMPLER;不是修改外部环境即可热更新。

尾部采样在 Collector 配置:

processors:
  tail_sampling:
    decision_wait: 10s          # 从首次见到Trace起的决策等待,不保证已收完整
    num_traces: 50000           # 缓存Trace数量上限,不是Span数或字节数
    policies:
      - name: errors
        type: status_code
        status_code: {status_codes: [ERROR]}
      - name: slow
        type: latency
        latency: {threshold_ms: 2000}
      - name: baseline
        type: probabilistic
        probabilistic: {sampling_percentage: 10}

本例普通正向策略是 OR 关系,并非按顺序排他的兜底规则。status_code: ERROR 匹配 Span 状态而不是直接匹配 HTTP 5xx。样本有选择偏差,不能直接当总体错误率或延迟分布。

尾部采样的落地难点

除了汇聚路由问题,还有两点要留意:decision_wait 设太短会误判未完成的链路,设太长则内存膨胀;容量耗尽时可能驱逐尚未完成决策的 Trace;具体阻塞/丢弃行为依版本与配置,不能保证提前得到正确决策。生产部署需要给采样实例留足内存并监控其丢弃指标。

验证采样后的数据

将采样结果送入观测云时,可让 Collector 的导出器连接 DataKit 的 OTLP 接收端。先保留一组已知的正常、慢请求和错误请求,逐个检查采样后的 Trace 是否符合策略,再逐步扩大流量。

同时观察采样前后的数量、导出错误和缓存丢弃。头部已经丢弃的请求无法由尾部采样恢复;告警仍然命中,也不足以证明排障所需的 Span 都在。

常见问题(FAQ)

Q:采样会不会影响指标统计的准确性? 独立记录的 Metrics 不一定受 Trace 采样影响;若指标从采样后的 Span 派生,就会受采样率与选择偏差影响。需要总体统计时应在相关丢弃步骤前聚合,并核对完整数据范围。

Q:头部采样率改起来麻烦吗? 初始化时环境变量可免代码设置,但变更通常需重启/重新配置 SDK;真正热更新需实现或使用专门支持的机制。

Q:尾部采样需要多少内存? 取决于 QPS 与 decision_wait:峰值 Span 速率 × 等待时长 × 单 Span 大小,还要计入对象开销、队列、Trace长度分布及并发,并做负载验证;不能用固定50%余量保证容量。num_traces 限制Trace数,也需按新Trace速率和等待时长估算。

Q:网关层已经做了采样,应用内还要采吗? 建议统一在链路最上游决策(配合 ParentBased 采样器),多层各自采样会导致链路残缺——上游没采下游采了,拼不出完整 Trace。

系列阅读

获取专属方案

联系我们

加入社区

微信扫码
加入官方交流群

立即体验

在线开通,按量计费,真正的云服务!

立即开始

选择观测云版本

代码托管平台