概念定义
头部采样在请求入口做一次保留或丢弃的决定,并通过 trace 上下文向下传播,使每个下游服务一致地记录或跳过。由于决定先于执行,它无法优先保留错误或慢请求,只能随机、按速率,或依据请求进入时已知的属性来采样。其优势是简单且几乎不需要缓冲。
为什么重要
在请求量很高时,记录每条链路既不划算也无用;有代表性的样本足以回答聚合性问题。头部采样以极小的运行时开销产生该样本,且不显著改变 collector 拓扑,因此它仍是默认选择。它也易于容量规划:链路量是请求量的已知比例。
工程实践
选择一个足以调试常见问题的采样率,并按端点区分——把健康检查与下单同等采样会浪费预算。始终保留携带显式标志的链路,以便按需捕获关键流程。在错误与长尾延迟最重要的地方,应在头部决定之后叠加尾部采样,而不是靠提高速率来弥补。
代码示例
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'
export class LogSampler {
constructor({ rate = 0.01, alwaysKeep = () => false } = {}) {
this.rate = rate;
this.alwaysKeep = alwaysKeep;
this.seen = new Set();
}
shouldEmit(signature) {
if (this.alwaysKeep(signature)) return true;
if (!this.seen.has(signature)) {
this.seen.add(signature);
return true;
}
return Math.random() < this.rate;
}
emit(signature, write) {
if (!this.shouldEmit(signature)) return false;
write({ signature, sampledRate: this.rate });
return true;
}
}