OpenTelemetry:三个信号的统一语言

前面八篇,我们把可观测性的”能力”讲完了:四类信号让你看见一切,SLO 让你判断好坏。但有一个一直被绕过的、极其务实的问题:这些信号,到底是怎么从你的代码里被采集出来、送到那些后端去的?

这个问题听起来像管道工的活,不性感。但它藏着一个能让整个可观测性投资打水漂的陷阱——厂商锁定。你为追踪、为指标、为日志辛辛苦苦插满了一身的仪器化代码,结果发现这些代码全都绑死在某一家后端的 SDK 上;有一天你想换个更便宜或更好用的后端,才发现要把所有插桩推倒重写一遍

OpenTelemetry(OTel)就是来拆这个陷阱的。它不增加任何新的观测能力,它做的是一件更基础、也更解放人的事:把”仪器化”和”后端”彻底解耦,让一次插桩成为永久可迁移的资产。 这是”标准化”子带的核心一篇。

一、不是看什么,是怎么采上来

信号都有了,数据怎么进来还没说

回看前八篇,我们一直在讨论”看什么”——指标看多少、追踪看因果、SLO 看好坏。但所有这些的前提,是数据已经在后端里了。数据是怎么进去的? 谁在你的代码里产生了那条 span、那个指标、那条日志?又是谁把它们送到了 Jaeger、Prometheus、Loki?这条从”代码”到”后端”的链路,前八篇默认它存在,这一篇专门拆开看。

仪器化锁定的痛

设想一个真实的处境。三年前,你的团队选了某商业可观测性平台(叫它 V 厂)。你们照着 V 厂的文档,在每个服务里引入了 V 厂的 SDK,手动加了几千处插桩——vendor.tracer.startSpan()vendor.metrics.count(),撒满了整个代码库。

今天,V 厂涨价了,或者你们发现了一个更适合的方案。你想换。然后你绝望地发现:那几千处插桩,每一处都是 V 厂的 API。 换后端,等于把这几千处全部重写、重测、重新发布——成本高到让”换”在事实上变得不可能。你不是选了一个后端,你是被一个后端绑架了。 这就是仪器化锁定:你的代码和某家厂商,被插桩这件事焊死了。

OTel 的野心

OTel 的野心,是从根上消灭这种绑架。它的核心主张只有一句:

你的代码,不该知道数据最终去哪个后端。

代码只负责”产生遥测数据”这件纯粹的事,至于这些数据被送去 Jaeger 还是 Datadog 还是同时送给三家——那是部署配置的事,不是代码的事。把这两件事分开,仪器化就从”对某家厂商的承诺”,变成了”一笔中立的、可随时改变流向的资产”。怎么做到的?看它的架构。

二、解耦的机制:四层架构与 OTLP

OTel 实现解耦,靠的是一套清晰的分层。理解这四层各自的职责,就理解了”一次插桩、后端自由”是怎么成立的。

API:代码唯一依赖的中立接口

最上面一层是 API——一组厂商中立的接口。你的业务代码,只 import 这一层

from opentelemetry import trace          # 只依赖中立的 OTel API
tracer = trace.get_tracer("checkout")
 
with tracer.start_as_current_span("process_payment") as span:
    span.set_attribute("user.tier", "premium")
    # ... 业务逻辑

注意这里没有任何厂商的名字——不是 jaeger.startSpan,也不是 datadog.trace,就是中立的 opentelemetry你的几千处插桩,从此只认这一个中立接口。 这是解耦的第一块基石:代码依赖的是标准,不是实现。

SDK:可插拔的实现

API 只是接口,得有人实现它。这就是 SDK——API 背后的实际干活的那层:它真正地创建 span、采样、批处理、然后交给下一层导出。关键在于,SDK 是可配置、可替换的:用哪种采样策略、往哪导出,都在 SDK 的初始化配置里决定,而这部分不在你的业务代码里。业务代码调 API,SDK 在背后按配置执行——接口和实现的经典分离。

OTLP:三类信号的统一传输协议

数据要离开应用、流向后端,需要一个传输协议。OTel 定义了 OTLP(OpenTelemetry Protocol)——一个统一的协议,指标、日志、追踪全都用它传输。

这个”统一”是有分量的。在 OTel 之前,每类信号、每家厂商都有自己的协议:Prometheus 有自己的格式,Jaeger 有 Thrift,Zipkin 有自己的 JSON……OTLP 把它们收拢成一个。一种协议,承载三类信号——这是后面 Collector 能统一处理一切的前提。

Exporter:往各家后端吐

最后一层是 Exporter——把 OTLP 数据翻译成某个具体后端能接受的格式,发过去。要发 Jaeger 就用 Jaeger exporter,要发 Prometheus 就用 Prometheus exporter,要同时发三家就配三个 exporter。

全部的”后端相关性”,被压缩进了这一层。 你的代码(API)、你的处理逻辑(SDK),统统不知道后端是谁;只有最末端这薄薄的 Exporter 一层知道。于是换后端 = 换 Exporter 配置,业务代码一行不动。这就是前面那句”代码不该知道数据去哪”的技术兑现。

OTLP vs Prometheus scrape

把 OTLP 和 03 里的 Prometheus scrape 对比,能看清两种数据流哲学:

Prometheus scrapeOTLP
方向pull——后端主动来拉 /metricspush——应用/Collector 主动推
覆盖只管指标指标 + 日志 + 追踪统一
目标发现靠服务发现找目标应用主动上报,无需被发现

这不是谁取代谁——Prometheus 的 pull 模型在指标领域依然极其好用(03 讲过它配合服务发现的优势),OTel 也提供了和 Prometheus 兼容的桥。但对追踪、日志这类事件驱动、源头才知道全部上下文的信号,push 式的 OTLP 更自然。两者并存,是当下的常态。

战略意义:一次插桩,后端永久自由

把四层连起来看,OTel 的战略价值就清楚了。它把 仪器化代价这根标尺,做了一件前所未有的事——它没有降低插桩的一次性成本,但它消灭了这笔成本的”重复支付”。

过去,仪器化的成本是”每换一个后端,付一次”。OTel 之后,仪器化是”付一次,永久有效”。你的几千处中立插桩,是一笔一劳永逸的资产:后端可以随业务、随预算、随技术演进自由更换,那笔最贵的插桩投入再也不用重做。这就是把”仪器化”和”后端锁定”两个问题分离开来的全部意义。

三、Collector:可观测性的数据管道

四层架构解决了”代码侧”的解耦。但 OTel 还有一个独立的、同样重要的组件——Collector,它解决的是”数据在路上”的所有问题。

把”数据怎么处理、发去哪”从应用里拿出来

即便应用用 OTel API 产生了中立的数据,仍有一堆问题:要不要批量发以省网络?要不要把日志里的敏感字段脱敏?要不要做尾部采样?要不要同时发给生产后端和一个分析后端?

如果这些都写在应用里,应用又被这些”治理逻辑”污染了。OTel 的答案是:起一个独立的中间进程——Collector——把这一切从应用里拿出来。 应用只管用 OTLP 把原始数据吐给 Collector,剩下的处理和路由,全归 Collector。应用重新变回”只产生数据”的纯粹角色。

receive → process → export

Collector 内部是一条清晰的三段流水线,用配置描述:

receivers:                      # 1. 收:从哪接收数据
  otlp:
    protocols: { grpc: {}, http: {} }
 
processors:                     # 2. 处理:在路上做什么
  batch: {}                     #    批量打包,省网络
  attributes:                   #    脱敏:抹掉敏感字段
    actions:
      - key: user.email
        action: delete
  tail_sampling:                #    尾部采样(见下)
    policies:
      - { name: errors, type: status_code, status_code: { status_codes: [ERROR] } }
 
exporters:                      # 3. 发:送去哪些后端(可多个)
  otlp/tempo:    { endpoint: tempo:4317 }
  prometheus:    { endpoint: "0.0.0.0:8889" }
 
service:
  pipelines:
    traces:                     # 把上面的组件串成一条管道
      receivers:  [otlp]
      processors: [batch, attributes, tail_sampling]
      exporters:  [otlp/tempo]

收、处理、发——一条数据从进到出,中间能被任意加工、复制、分流。

它能干的活:包括那个”有状态采样层”

Collector 这条管道能干的活,正是可观测性治理的全部动作:批处理、脱敏、过滤、降采样、格式转换、扇出到多个后端。

特别值得点出的是尾部采样(tail_sampling)。还记得 06 讲尾部采样时说,它需要”一个专门的、有状态的采样层,先缓存所有 span、等 trace 完成再决定留不留”——那个神秘的有状态采样层,真身就是 Collector。 上面配置里的 tail_sampling processor,就在做这件事:缓存 trace、判断它是否出错/超时、只留下有价值的。06 埋的伏笔,在这里落了地。

Collector 作为治理点

把 Collector 的角色拔高一层看:它是整个可观测性数据流的单一治理点

成本要控制?在 Collector 上降采样、过滤掉没用的指标。合规要满足?在 Collector 上统一脱敏。要从一家后端迁到另一家?改 Collector 的 exporter,应用无感。所有关于”数据怎么流、留多少、去哪里”的策略,都收敛到这一个地方——而不是散落在几百个应用里。这让可观测性的成本治理有了一个可操作的抓手——这正是 MOC 第五节「运维的人与经济」里”遥测成本”那条线的工程落点。

四、统一语言:semantic conventions 与信号关联

到这里,OTel 已经统一了”管道”。但它还有一层更深的统一,常被忽略,却是它最有价值的部分——统一”语言”。

不只统一管道,还统一词汇

设想两个团队都记录 HTTP 请求。A 团队的字段叫 http.method,B 团队叫 httpMethod,C 团队叫 verb。数据都进了同一个后端,但你没法用一条统一的查询同时分析它们——同一个东西,三个名字。

OTel 的 semantic conventions(语义约定) 来终结这件事:它规定了一套标准字段名——HTTP 方法就叫 http.request.method,状态码就叫 http.response.status_code,数据库语句就叫 db.statement……所有遵循 OTel 的系统,对同一个概念用同一个名字。

为什么词汇统一这么重要

词汇统一的价值,是让遥测数据可互操作。因为大家都叫 http.response.status_code,所以:一条”按状态码统计错误率”的查询,能跨所有语言、所有团队、所有服务通用;一套 SLO 告警规则,能套用到任何一个遵循约定的服务上;一个后端的可视化,对任何来源的数据都认得。没有统一词汇,统一管道只是把一堆语言不通的数据汇到了一起;有了统一词汇,它们才真正能被当成一个整体来分析。

共享上下文:四类信号终于能彼此关联

更深一层:因为四类信号都经由 OTel、都携带同一套 trace context06 那个 traceparent),它们之间第一次有了可靠的关联键

回想 05 讲的"三大支柱的痛"——指标、日志、追踪是三个割裂的筒仓,排障时要在三个系统间手工拼线索。割裂的根因,正是它们没有共享的上下文。OTel 让四类信号都带上同一个 trace_id,于是:一条日志能关联到它所在的 span,一个 span 能关联到它影响的指标。筒仓之间,被一根共享的上下文打通了。

Exemplar:从指标一跳跳到追踪

这种关联,最漂亮的体现叫 Exemplar(范例)。它让一个聚合的指标数据点,携带一个指向具体追踪的 trace_id

你在 Grafana 看 P99 延迟曲线,看到一个尖刺。在 OTel 之前,你只能知道”P99 升高了”,然后切去 Jaeger 大海捞针找一条慢 trace。有了 Exemplar,那个尖刺数据点上挂着一个小点,点一下——直接跳到造成这个尖刺的那一次具体的慢请求的完整追踪

这是 "宏观异常发现""微观原因定位"之间,第一次有了一键直达的桥。 指标告诉你”哪里不对”,一点,追踪告诉你”为什么不对”。前面几篇辛苦建立的、各自割裂的信号,被 OTel 缝成了一张能互相跳转的网。

★ 故障剧场:只改配置,不动代码

把 OTel 的全部价值,浓缩进一个最能说明问题的演示——不是一次故障,而是一次迁移

你的系统跑了一年,几千处 OTel 插桩,追踪发往自建的 Jaeger。现在公司决定:追踪迁到成本更低的 Grafana Tempo,同时复制一份给商业平台做高级分析。

在 OTel 之前,这是一个要改几千处代码、跨多个团队、持续数月的大工程。

用 OTel,你做的全部事情是——打开 Collector 配置,把 exporter 从一个改成三个:

exporters:
  otlp/tempo:    { endpoint: tempo:4317 }      # 新增
  otlp/vendor:   { endpoint: vendor-api:4317 }  # 新增
  # jaeger:      原来的,删掉或保留做灰度
 
service:
  pipelines:
    traces:
      exporters: [otlp/tempo, otlp/vendor]      # 一行,扇出到两家

提交,重启 Collector。完成。几千处应用插桩,一行没动;几千次重新发布,一次没有。 后端从 Jaeger 变成了 Tempo + 商业平台,而产生数据的代码完全无感。这就是”一次插桩、永久可迁移”从口号变成现实的样子——也是前面那个战略意义最直白的证明。

五、实践与边界

落地形态:手动 SDK + 自动插桩

OTel 怎么进入你的系统,有两条路,对应 06 讲的传播负担的两个层次:

  • 手动(manual instrumentation):用 SDK 在代码里显式打点,像前面那样。精确,能标注业务语义(“这个 span 是结账”),但要人写。
  • 自动(auto-instrumentation):用一个 agent(Java 的 javaagent、Python 的 monkey-patching)在启动时挂上,自动拦截框架层的调用——HTTP 请求、数据库查询、gRPC——替你生成 span 并传播 context。常见框架的覆盖,几乎零代码。

实践中是两者结合:自动插桩铺好框架层的底,手动插桩补上业务语义的关键点。

信号成熟度:诚实标注

OTel 的三/四类信号,成熟度并不齐:

  • Traces:最成熟、最稳定,OTel 起家于此,已是追踪仪器化的事实标准。
  • Metrics:稳定,和 Prometheus 有成熟的兼容层。
  • Logs:较新,设计上更倾向于”把已有日志桥接进 OTel”而非另起炉灶。
  • Profiling:最年轻,持续分析作为第四类信号刚被正式纳入 OTel——这印证了 07 说的”四类信号正在被同一套标准收编”,但它还在早期。

用 OTel,traces/metrics 可以放心上生产;logs/profiling 要看你的具体栈成熟到什么程度。

边界一:OTel 是标准,不是后端

最重要的一个澄清:OTel 不存储、不查询、不画图。 它管的是”产生、处理、传输”——数据到达后端之前的一切。到了后端,存储和查询是 Prometheus、Jaeger、Tempo、Loki、Grafana 的活。

所以”我们上了 OTel”不等于”我们有了可观测性”。OTel 是把数据中立地、高效地、可迁移地送到后端的管道和语言;可观测性的”看见”,仍然由后端实现。把 OTel 当成后端,是常见的误解。

边界二:自动插桩也只到框架层

06 说过追踪的木桶效应,这里要诚实地接上:auto-instrumentation 覆盖的是框架层——它认得 HTTP 库、数据库驱动、消息队列客户端,但它认不得你的自定义业务逻辑。一段纯内存的复杂计算、一个自研的协议,自动插桩看不见,仍要你手写。

OTel 把仪器化的成本降到了很低,但”很低”不是”零”——总有一部分业务语义,得人去标注。那么,能不能连框架层的插桩都不要、让基础设施替你产出遥测? 这就把我们带到了最后一篇。

六、小结:坐标与去向

OTel 在五维标尺上的坐标

那把尺子给 OTel 定位。和 07 一样,它主要拨动第五根标尺,但角度不同:

维度OTel 的作用
① 故障预判权不直接涉及
② 存储汇率在 Collector 上做降采样/过滤,间接帮成本治理
③ 信号分工不产生新信号,但用统一上下文把四类信号缝成网(Exemplar)
④ 注意力阀门不直接涉及
⑤ 仪器化负担核心——把仪器化从”每换后端付一次”变成”付一次永久有效”

07 用 eBPF 压低了仪器化的"成本",OTel 则消除了仪器化的”重复支付”——一个让它更便宜,一个让它可迁移,两篇从两个角度一起把第五维拨向了人最省力的方向。 那个贯穿全系列的问题——OTel 把天平推向了哪边?它把”仪器化”这笔投入,从一次性消耗品变成了永久资产,让人不必再为后端的更迭,反复支付同一笔插桩的力气。

去向

OTel 把仪器化降到了很低,但前面留了一个尾巴:它仍要你集成 SDK、挂 agent、补业务插桩。最后一篇要去回答那个被逼出来的问题:

  • 能不能连应用代码、连框架插桩都不碰,让基础设施——服务网格、eBPF——在网络层、内核层替你产出遥测?这就是 10-云原生可观测性-零侵入是如何做到的:把仪器化负担,从应用彻底转移到平台。
  • 而 Collector 作为成本治理点的那条线(前面讲过),完整的组织与经济视角,见 MOC 第五节「运维的人与经济」。

下一篇,也是云原生范式的收官篇:当 OTel 把仪器化降到”很低”还嫌不够,云原生给出了它的终极答案——零侵入。让平台替你看,应用一无所知。

返回 可观测性与运维工程 MOC | 上一篇 08-SLO-把可靠性变成数学 | 下一篇 10-云原生可观测性-零侵入是如何做到的