OpenTelemetry 采样更新

简介

OpenTelemetry 四年多前发布了其追踪规范的 1.0 版本,同年 W3C TraceContext Level 1 发布并获得 W3C 推荐状态。我们作为一个社区和可观测性行业,拥有了两个新的分布式追踪标准。当然,我们并未止步于此。

采样是追踪 SDK 规范中的一个主要主题,原始规范包含了一组内置的采样器:AlwaysOnAlwaysOffParentBasedTraceIdRatioBased,以及一个允许实现新采样器的接口,主要是 Jaeger Remote

然而,1.0 追踪规范中留下了一个 显眼的“TODO”,涉及概率采样,影响了 TraceIdRatioBased 采样器。该 TODO 警告规范用户“不一致”的结果,即 TraceIdRatioBased 采样器只能安全地配置用于根跨度。

这意味着 OpenTelemetry 用户无法安全地在分布式系统中配置独立的概率采样策略,因为规范并未涵盖如何实现一致性。用户期望此功能——在跟踪中配置不等概率采样策略并仍期望获得完整跟踪的能力——它允许服务所有者配置收集数据量的独立限制。

以示例为证的一致性

为了理解一致性的重要性,请考虑一个包含前端、缓存和存储两个后端服务的系统。前端处理高价值的用户请求,因此前端请求的采样率为 100%。根跨度很重要,因为错误对最终用户可见,因此在此示例中它构成了 SLO 测量的基础,系统操作员愿意收集每个跨度。

缓存服务接收到相对大量的请求,为了节省可观测性成本,该服务被配置为对 1000 个追踪进行 1 个采样。由于请求率很高,此 0.1% 的策略确保了缓存服务产生足够多的追踪以满足许多可观测性场景。

与缓存服务器相比,存储服务接收到的请求量相对较少,但与前端服务相比仍然很多;存储被配置为对 10 个追踪进行 1 个采样。

当我们要求分布式追踪的一致性时,目标是确保当最小概率采样器(此处为 0.1%)选择采样时,更高概率的采样器也做出相同的决定。以下是我们在此配置中可依赖的属性:

  • 100% 的前端跨度将被收集
  • 10 个追踪中有 1 个将包含前端和存储跨度
  • 1000 个追踪中有 1 个将是完整的。

TraceIdRatioBased 的问题

OpenTelemetry 的 TraceIdRatioBased 概率采样器从一开始就旨在实现一致性,但工作组在一些细节上难以达成一致。由于根跨度采样是当时开源追踪系统的常态,并且是 Jaeger 所采用的模型,因此采样一致性的 TODO 问题得到了缓解。

名称中的“基于比例”部分暗示了解决一致性采样问题的方案形式。

  1. 将 TraceID 值视为 N 位随机值
  2. 计算二的 N 次幂
  3. 将幂次方乘以比例,得到一个“阈值”值
  4. 将 TraceID 与阈值进行比较,从而做出一致的决定。

我们在这种解决方案的形式上存在分歧,因为存在一个更大的问题:TraceID 的哪些位可以被信任为随机? 没有关于随机性的基础要求,OpenTelemetry 无法指定一致的采样决策。

在缺乏明确的随机性要求时,一种常见的方法是使用哈希函数。如果哈希函数够好,使用 Hash(TraceID) 来生成 N 位随机性效果还不错,但在跨语言 SDK 规范中不适用。

这里的细节很棘手。TraceID 需要多少位才能足够?每个语言 SDK 能否高效地实现所需的逻辑?

引入 W3C TraceContext Level 2

OpenTelemetry 带着这个更大的问题找到了 W3C Trace Context 工作组。我们(包括 OpenTelemetry 和非 OpenTelemetry 追踪系统)能否就 TraceID 的多少位是随机的达成一致?

W3C TraceContext Level 2 规范,目前是一个 候选推荐草案,通过一个新的 Random 追踪标志值 回答了这个问题。有了这个标志,新的 W3C 规范要求 TraceID 的最低有效 56 位是“足够”随机的。这意味着,例如,当我们 将 TraceID 表示为 32 位十六进制数字 时,最后(最右边)14 位是随机的。表示为 16 字节时,最后(最右边)7 个字节是随机的。

OpenTelemetry 正在采用 W3C TraceContext Level 2 草案推荐作为一致性采样基础。所有 SDK 将默认设置 Random 标志,并确保它们生成的 TraceID 具有所需的 56 位随机性。

一致的拒绝采样阈值

回到一致的“基于比例”逻辑,现在我们能够从 TraceID 中获得 56 位的随机性,而上面概述的决策过程需要一个阈值进行比较。

我们团队还希望在概率采样规范中添加另一项功能:一种 SDK 之间能够通信其采样决策的方式,既能在 TraceContext 中互相通信,也能在跨度完成后进行收集路径上的通信。

新规范允许 OpenTelemetry 组件就“对跨度应用了多少采样”进行通信。这支持了许多高级采样架构:

  • 可靠的跨度计数估计
  • 一致的速率限制采样
  • 自适应采样
  • 一致的多阶段采样。

我们的设计要点将在下面总结,但好奇的读者可以查看 完整规范

考虑到位数的数量,剩余要指定的内容不多了。但是,我们想要一种方法,它能

  • 支持字典序和数值比较
  • 最小化 TraceContext 开销
  • 对高级 OpenTelemetry 用户来说是易读的。

我们的方法基于我们称之为拒绝采样阈值的概念。给定随机值 R 和拒绝阈值 T,当 T <= R 时,我们做出正采样决策。反之,当 T > R 时,我们做出负采样决策。

根据设计,阈值 0 对应 100% 采样,因此用户可以轻松识别此配置。抽象地说,RT 都有 56 位的范围,可以表示为无符号整数、7 字节切片或 14 位十六进制字符串。

OpenTelemetry TraceState

W3C TraceContext 规范定义了用于分布式追踪系统的两个 HTTP 头:tracecontext 头,其中包含版本、TraceID、SpanID 和标志,以及 tracestate,它支持对上下文的“供应商特定”添加。OpenTelemetry Tracing SDK 将开始在 tracestate 头中添加一个键为“ot”的条目。示例如下:

tracestate: ot=th:0

在 100% 采样配置中,OpenTelemetry Tracing SDK 将在 TraceState 中插入 ot=th:0。TraceState 值一旦进入上下文,就会被传播并在 OpenTelemetry 跨度数据中记录。根据设计,新的 OpenTelemetry TraceState 值仅在做出正采样决策时进行编码和传输;负采样决策不会导致 tracestate 头出现。

在此表示法中,采样阈值在逻辑上代表 14 个十六进制数字或 56 位信息。

但是,为了有效地通信采样阈值,我们删除了尾随零(除了 0 本身)。这使我们将阈值精度限制在少于 56 位,从而减少了每个上下文的字节数。下面是一个表示 1% 采样,限制为 12 位精度的 tracestate 示例:

tracestate: ot=th:fd7

我们对向后兼容性进行了大量考虑,但我们也希望确保我们始终能够以可靠的统计意义使用声明的采样阈值进行外推。考虑到这一点,我们的规范中还有另一个 OpenTelemetry TraceState 值:一种在 tracestate 头中提供显式随机性的方法。

为了实现一致性采样并继续使用非随机 TraceIDs,例如,用户可以选择显式随机性:

tracestate: ot=rv:abcdef01234567

显式随机值还有其他几种用途,例如:

  • 通过将相同的显式随机值应用于独立的跟踪根,实现跨多个跟踪的一致性采样。
  • 将外部一致性采样决策(例如,基于哈希函数的)转换为 OpenTelemetry 的一致性采样决策。

作为演示,我们升级了 OpenTelemetry Collector-Contrib 的 probabilisticsampler 处理器,使其保持其原始的一致性采样决策,同时仍将采样概率编码到 OpenTelemetry TraceState 中。它通过合成哈希函数使用的显式随机值来实现这一点。

展望未来

这篇博文介绍了一个对 OpenTelemetry Tracing 规范至关重要的升级,它为 OpenTelemetry SDK 和 Collector 组件带来了新一代的采样器。

以下是一些有用的参考资料,包括为我们规划了路线的四个 OpenTelemetry 增强提案:

以下是我们的主要规范文档: