← 中文资源

简体中文版本

链路采样

链路采样保留分布式链路的一个子集,在控制可观测成本的同时保留下足以推理系统的数据。

阅读英文原文

概念定义

链路采样是决定记录与保留哪些分布式链路的策略集合。方法分为两类:头部采样在根节点、结果未知时决定;尾部采样在收齐整条链路后决定。无论何种方法,采样器都必须保留足够的总量并记录采样率,使聚合的延迟与错误统计保持无偏。

为什么重要

繁忙服务每秒产生数千条链路,全部存储既不可承受又大多冗余。采样让链路追踪可持续,但必须谨慎:如果错误或某个服务被系统性地采样不足,得出的指标会悄悄出错并把排查引向歧途。做得对时,小样本仍能揭示结构、热点与失败路径。

如何选择

把头部采样当作廉价的默认,再叠加尾部采样以保留头部决定会丢弃的稀有错误与慢链路。在每条保留的链路上记录采样率,并把采样与指标结合——指标携带精确计数,链路携带少数请求的细节。定期复核保留比例:随着流量与成本变化,策略也应随之调整。

代码示例

from dataclasses import dataclass, field
import random

@dataclass
class Span:
    name: str
    duration_ms: float
    error: bool = False

@dataclass
class Policy:
    slow_ms: float = 500.0
    keep_errors: bool = True,
    fallback_rate: float = 0.01

def decide(spans: list['Span'], policy: Policy, rng: random.Random | None = None) -> tuple[bool, str]:
    rng = rng or random.Random()
    if not spans:
        return False, 'empty'
    if policy.keep_errors and any(s.error for s in spans):
        return True, 'error'
    slowest = max(s.duration_ms for s in spans)
    if slowest >= policy.slow_ms:
        return True, 'slow'
    if rng.random() < policy.fallback_rate:
        return True, 'sampled'
    return False, 'dropped'
const HEX = { trace: 32, span: 16 };

export function parseTraceparent(value) {
  if (typeof value !== 'string') throw new TypeError('traceparent must be a string');
  const parts = value.trim().split('-');
  if (parts.length !== 4) throw new SyntaxError('traceparent must have 4 fields');
  const [version, traceId, spanId, flags] = parts;
  if (!/^[0-9a-f]{2}$/.test(version)) throw new SyntaxError('invalid version: ' + version);
  if (version === 'ff') throw new SyntaxError('version ff is invalid');
  if (!new RegExp('^[0-9a-f]{' + HEX.trace + '}$').test(traceId)) {
    throw new SyntaxError('invalid trace id: ' + traceId);
  }
  if (traceId === '0'.repeat(HEX.trace)) throw new SyntaxError('trace id must not be all zeros');
  if (!new RegExp('^[0-9a-f]{' + HEX.span + '}$').test(spanId)) {
    throw new SyntaxError('invalid span id: ' + spanId);
  }
  if (spanId === '0'.repeat(HEX.span)) throw new SyntaxError('span id must not be all zeros');
  if (!/^[0-9a-f]{2}$/.test(flags)) throw new SyntaxError('invalid flags: ' + flags);
  return { version, traceId, spanId, sampled: (parseInt(flags, 16) & 1) === 1 };
}