概念定义
链路采样是决定记录与保留哪些分布式链路的策略集合。方法分为两类:头部采样在根节点、结果未知时决定;尾部采样在收齐整条链路后决定。无论何种方法,采样器都必须保留足够的总量并记录采样率,使聚合的延迟与错误统计保持无偏。
为什么重要
繁忙服务每秒产生数千条链路,全部存储既不可承受又大多冗余。采样让链路追踪可持续,但必须谨慎:如果错误或某个服务被系统性地采样不足,得出的指标会悄悄出错并把排查引向歧途。做得对时,小样本仍能揭示结构、热点与失败路径。
如何选择
把头部采样当作廉价的默认,再叠加尾部采样以保留头部决定会丢弃的稀有错误与慢链路。在每条保留的链路上记录采样率,并把采样与指标结合——指标携带精确计数,链路携带少数请求的细节。定期复核保留比例:随着流量与成本变化,策略也应随之调整。
代码示例
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 };
}