跳转至

分布式追踪采样实战:从头采样到尾采样,把成本压下来又不丢异常

分布式追踪一旦铺开,第一个让你肉疼的不是接入难度,而是成本。每来一个请求就生成几十条甚至上百条 span,全量上报到后端,存储和流量费用按月跳着涨。更要命的是,绝大多数 span 是健康请求的正常路径,你其实并不需要它们全部。采样就是回答一个问题:在海量 span 里,留下哪些才既看得清系统、又不被账单压垮。

但采样最怕两件事:一是把偶发的报错链路采没了,出事时追踪里查无此链;二是采得支离破碎,父 span 在、子 span 不在,图谱拼不起来。这篇文章把头采样、尾采样和各种采样策略讲透,并给你一套能直接落地的取舍原则。

1. 为什么必须采样,不能全量

先算笔账。一个中等流量服务,QPS 2000,每请求平均 30 条 span,一天就是约 52 亿条 span。全量存下来,光存储就要按 TB 计,查询也慢。而其中 95% 以上是正常的、重复的、没分析价值的路径。追踪的本质目的是"在异常时定位问题",不是"给每根健康请求建档"。

所以行业共识是:对高流量系统,采样不是可选项,是必选项。难点在于怎么采得"聪明"——把采样率压到个位数百分比,同时让错误、慢、异常的链路几乎不丢。

2. 头采样:在起点就决定

头采样(head-based sampling)在 span 被创建的那一刻就决定这条链路采不采,通常是抛个硬币(概率采样)或看固定规则。它的好处很直接:

  • 零缓冲、零额外内存,决定立刻生效。
  • 不引入决策延迟,对延迟敏感的服务友好。
  • 实现简单,SDK 里一个 sampler 配置就完事。

坏处也明显:它"没看到结局"。一条最终 500 错误的链路,在采样点可能恰好被概率丢掉;一条跨十个服务的慢链路,头采样无法预知它后面会变慢。所以头采样适合流量低、或者只关心"有没有"而非"具体哪条错了"的场景。

3. 尾采样:看完全程再拍板

尾采样(tail-based sampling)反过来:先把一条链路相关的 span 收齐,等请求跑完(甚至等多久看配置),再根据"最终表现"决定保留与否。它可以基于这些信号决策:

  • 这条链路最终报错了吗(HTTP 5xx、gRPC error、异常)?
  • 耗时是否超过某阈值(P99 之外)?
  • 这是不是罕见路径(低频接口、特定用户)?
  • 是否触发了自定义业务规则(如金额大于某值)?

于是你能做到"错误和慢链路全留、正常链路按比例抽",在极低总采样率下保住最大价值。代价是要有一层组件把 span 缓冲起来、占用内存、引入一点点决策延迟。OpenTelemetry Collector 的 tail_sampling 处理器就是干这个的。

4. 常见采样策略拆解

单一策略不够用,实战里是组合拳:

  • 概率采样:每条链路按固定比例(如 1%)保留。简单、流量平稳时够用,但会随机丢掉偶发错误。
  • 限流采样(rate-limited):每秒最多保留 N 条,超出丢弃。控制总量上限很有效,适合防突发。
  • 自适应采样:根据当前流量和错误率动态调采样率,错误率高时自动调高。需要额外实现或平台支持。
  • 父级采样:下游跟随上游的采样决定,保证链路完整性(见 FAQ)。
  • 基于属性的采样:按服务名、接口、用户标签等做不同采样率,核心业务高、旁路低。

生产环境通常是"尾采样 + 错误/慢强制保留 + 普通链路概率/限流"的组合。

5. OpenTelemetry Collector 尾采样配置

下面是 tail_sampling 处理器的骨架,逻辑是:先看是否错误、是否够慢,命中就 100% 保留;否则走概率采样把总量压住:

processors:
  tail_sampling:
    decision_wait: 10s          # 等整条链路到齐的窗口
    num_traces: 50000          # 内存里最多攒多少条链路
    policies:
      - name: errors
        type: status_code
        status_code:
          status_codes: [ERROR]
      - name: slow
        type: latency
        latency:
          threshold_ms: 500
      - name: probabilistic
        type: probabilistic
        probabilistic:
          sampling_percentage: 5

注意顺序:前面的 policy 命中即保留,未命中的才落到概率策略。再配一个 batch 处理器和导出器即可。这套配置下,错误和慢链路几乎不丢,整体采样率只有约 5%,成本直接砍掉九成多。

6. 内存与等待窗口的权衡

尾采样的命门是内存。它在决策前要把 span 缓存在 Collector 里,所以两个参数要盯紧:

  • decision_wait:等太久,内存堆得多、延迟大;等太短,长链路没到齐就被迫决策,慢请求被误丢。从 2 秒起,跨多服务调到 5-10 秒。
  • num_traces:并发缓存的链路数上限,防内存爆。超了会触发淘汰,可能丢还在等待的链路。

经验法则:链路平均跨度越长、服务越多,decision_wait 越大;Collector 内存越小,num_traces 越要收。上线后监控 Collector 的内存和"被淘汰未决策链路数",这是尾采样最该看的指标。

7. 父级采样保证链路完整

微服务里如果每个服务独立抛硬币采样,会出现"父 span 没采、子 span 采了"的残缺链路,追踪图谱全是断点。父级采样让下游跟随上游决定:上游说采,下游就采;上游说不采,下游也不采。在 OTel 里默认开启,和头采样组合使用时尤其重要。别为了"更细粒度"把它关掉,否则你会得到一堆拼不起来的碎片。

8. 怎么验证采样没丢关键链路

采样率数字好看不等于没丢东西。建议三条验证线:

  • 监控尾采样各 policy 的命中率,特别是 errors / slow 的命中,确认异常链路确实进了保留池。
  • 对核心业务(支付、登录)走独立 100% 采样通道,不依赖通用采样,确保这部分永远可查。
  • 周期性用已知故障做探针:故意触发一次错误,确认能在追踪后端查到完整链路。

把"关键链路可查到率"当成 SLO 来盯,比盯"总采样率"更有意义。

9. 上线后的巡检与防退化纪律

采样配置也会悄悄退化:一次 Collector 配置回滚可能让尾采样变头采样、一次内存超限可能让 num_traces 被调小丢链路。建立纪律:

  • 每周:看尾采样各 policy 命中率趋势,错误链路保留率应稳定接近 100%;若骤降,查是不是规则被改。
  • 每周:核对 Collector 内存与"淘汰未决策链路数",内存持续高位说明 decision_wait/num_traces 该调或该扩容。
  • 每次变更前:在测试环境用已知错误链路验证"采到率",再上生产;变更后立刻复核。
  • 红线:禁止"为省成本把错误链路也纳入概率采样"。错误和慢链路必须强制保留,这是采样配置不可动的底线。
  • 每次扩服务:新接入的服务默认继承统一采样策略,别让某个服务"裸奔"全量上报拉高成本。

FAQ

头采样和尾采样到底差在哪,为什么大家都推尾采样? 头采样在 span 一产生就决定是否保留,简单、零延迟、不占内存,但它"没看到结局就做了决定"——一条最终报错的链路可能在采样点被丢掉了。尾采样把 span 攒到请求结束(或更久),根据这条链路最终是否出错、是否够慢、是否罕见来做决定,能保住真正有价值的异常链路。代价是要缓冲一批 span、占用内存、引入决策延迟。所以高价值但高流量的系统多用尾采样,低流量或边缘采集点用头采样更划算。 尾采样要等多久才拍板?等太短会不会漏掉慢请求? 关键参数是"等待窗口"(OTel 里是 wait_for)。它决定 Collector 收到第一个 span 后最多等多久才对整条链路做决策。窗口太短,跨服务的长链路还没到齐就被迫决策,慢请求会被误判为"普通"而丢掉;窗口太长,内存里堆的 span 越多、延迟越大。经验值从 2 秒起步,跨多服务或长事务的系统调到 5-10 秒,同时配合 num_traces 上限防止内存爆。这是个"内存 vs 保真度"的权衡,按你的链路长度实测调。 概率采样设多少合适?会不会把偶发的错误采样没了? 纯概率采样(如 1%)对平稳流量很省,但偶发的错误链路有概率被整个丢掉,而这恰恰是最该看的。正确做法是在尾采样里叠规则:对"错误链路"和"超过阈值的高延迟链路"强制 100% 保留,对普通成功链路才走概率。这样整体采样率可能只有几个点,但异常链路几乎不丢。也可以按服务重要度分桶:核心支付链路高采样、健康检查类低采样。 父级采样(parent-based)是什么意思,什么时候用? 父级采样是一种"跟随上游决定"的策略:如果上游已经决定采这条链路,下游服务就跟着采,保证一条链路要么整条都在、要么整条都不在,避免出现"父 span 不在、子 span 孤零零"的残缺链路。它常和头采样搭配:服务收到一个已被上游标记为采样的请求时,自己不再独立抛硬币。在微服务里基本是必开的,否则追踪图谱会碎片化。 采样之后,我怎么确认没把关键错误采丢了? 三个办法:第一,在尾采样规则里把"错误/高延迟"设为强制保留并单独计数,监控这两条规则的命中率;第二,对极少数核心业务(如支付、登录)做 100% 采样或独立高采样通道,不依赖通用采样;第三,周期性抽几条已知故障做"能否在追踪系统里查到"的探针验证。别只信采样率数字,要看"关键链路的可查到率"。

相关文章