OTEL 协议入门
2026/8/29 4:45:49 网站建设 项目流程

目录

0.1 先记住这张图

0.2 四类对象分别是什么

A. 信号(Signals)——数据长什么样

B. 规范(Specification)——大家必须遵守的契约

C. 实现(Implementations)——规范的落地代码

D. 后端产品(Backends)—— 存和查

0.3 每个信号的「四件套」(官方结构)

0.4 数据走完一整条路,OTLP 只负责其中一跳

0.5 用一个具体场景把对象钉死

0.6 常见混用


0.1 先记住这张图

人们说「OTEL 协议」时,嘴里通常混了四样东西:

┌─────────────────────────────────────┐
│ OpenTelemetry(OTel)= 一整套开源标准与实现 │
│ │
│ ① 信号:Traces / Metrics / Logs / Baggage / Profiles │
│ ② 规范:API + SDK + 语义约定 + OTLP 怎么编码 │
│ ③ 实现:各语言 SDK、Collector、Operator… │
└──────────┬──────────────────────────┘
│ 其中真正「在网上跑」的那一段

④ OTLP(传输协议)


OpenObserve / Jaeger / Prometheus …
(后端产品,不是 OTel 的一部分)

一句话:

  • OTel 是标准 + 工具箱
  • OTLP 是工具箱里负责「把数据从 A 送到 B」的那份传输协议
  • OpenObserve 是把数据存起来给你查的产品
  • Logs / Traces / Metrics 是数据种类(信号),不是产品,也不是三套协议

必看文档

  1. ​编辑Instrumentation
  2. ​编辑Collector
  3. 按你工作语言选一篇 Getting Started:
    ​编辑Dev 入门 → 点进 Java / Go / Python / Node
  4. OpenObserve:​编辑Ingestion
  5. 中文长文(选读):​编辑OpenTelemetry 2026 深度解析

操作

  1. ​编辑LFS148 Getting Started with OpenTelemetry
    约 10 小时,含自动/手动埋点、Collector lab,Linux Foundation 平台,免费。
  2. B 站可搜「OpenTelemetry 入门」「OTel Collector」,当辅助
  3. 七米 Go 云原生课里有 OTel 几集(偏 Go,语言对口再看)

OpenTelemetry(OTEL)是 CNCF 的开源项目,官方源码都在 GitHub 组织 open-telemetry 下,许可证一般为 Apache 2.0。

常用官方仓库:

用途仓库

官网 / 文档

opentelemetry.io · opentelemetry.io

规范

opentelemetry-specification

协议(OTLP protobuf)

opentelemetry-proto

Collector 核心

opentelemetry-collector

Collector 社区组件

opentelemetry-collector-contrib

语言 SDK

Go · Java · Python · JS · .NET 等


0.2 四类对象分别是什么

A. 信号(Signals)——数据长什么样

信号 =遥测数据的种类。系统对外「说」的内容分这几类:

信号回答的问题是不是「一次请求」

Traces

这一次请求走过哪些服务、哪一段慢/错

Metrics

一段时间里 QPS、错误率、延迟分布

否(聚合)

Logs

某个时刻发生了什么、细节是什么

不一定绑在某次请求上

Baggage

沿请求传递的业务键值(租户、实验组)

跟着请求走,但不是观测数据

Profiles

代码级 CPU/内存占用(较新)

另一类信号

它们在 OTel 出现之前就有。OTel 没有发明日志或指标,只规定:怎么生成、怎么命名、怎么互相关联、怎么运走。

所以不能说Logs 是 OTel 的产品。正确说法是:Logs 是 OTel 支持的一种信号


Baggage是随这次请求一起“捎带”的键值上下文,不是 Trace 本身,也不会自动出现在 OpenObserve 的 Span 树上。

名字就这个意思:请求像一次旅行,traceparent是身份证(这次调用是谁、父子怎么接),Baggage 是随身行李——下游服务可能用得上的业务信息,例如tenant=acmeuser.id=42region=cn

跨服务时通常走 HTTP 头:

traceparent: 00-<trace_id>-<span_id>-01

baggage: tenant=acme,user.id=42

traceparent负责把链路拼成同一棵树;baggage只是把这些键值原样传到下一跳。

和另外两个容易混的东西对比:

干什么后端能不能直接看见

Trace Context

身份:trace_id/span_id

能,用来拼树

Span Attribute

记在某个 span 上,给观测用

能,在该 span 上

Baggage

给下游代码读(路由、计费、采样决策)

默认不能,除非业务显式写进 Attribute

所以:网关往 Baggage 里塞了tenant=acme,订单服务能读到并按租户查库;OpenObserve 里却不一定有这个字段。要在 Trace 里看见,需要再span.set_attribute("tenant", ...)


拼链路只靠traceparent(外加可选的tracestate)。Baggage 不是固定字段表,只传「下游代码真的会读」的业务标签。

传给下一跳的,一共就这几类:

HTTP 头要不要带干什么

traceparent

要拼成一棵树就必须带

身份:同一trace_id、本段span_id、是否采样

tracestate

可选

厂商私货,例如某 APM 的采样决策;W3C Trace Context 的第二部分

baggage

可选

业务键值,给下游代码用,不是给 OpenObserve 拼树用

这三样是服务 A → 服务 B 的传播(Propagator 写入 HTTP/gRPC metadata)。

不会随下一跳服务请求传过去的:

  • Span 的http.methoddb.statement等 Attribute
  • service.name等 Resource
  • Span Event / Status

那些走的是另一跳:进程 → 接收端/OpenObserve(OTLP/v1/traces。下游服务收不到,也不该靠它们来知道「这次请求是哪个租户」。

若环境不是 W3C,可能看到b3uber-trace-id等替代身份头。双方必须同一套,否则 Extract 失败、下游自己成根。

如果代码用的是TraceContextTextMapPropagator(),即使 Context 里有 Baggage,出站请求也不会写出baggage头。SDK 默认常用组合是 Trace Context + Baggage 两个 Propagator。


Baggage 要传什么键值对?

规范不定死键名。只约定格式:逗号分隔的key=value,值要百分号编码。放什么由业务决定。

该放的原则就一条:下一跳(或再下一跳)的代码要读它,才能把这次请求做对。

常见、合理的例子:

键(示例)下游拿来干什么

tenant/tenant.id

选库、鉴权、配额

region/cell

路由到对应机房

experiment/feature_flag

同一请求整条链走同一套实验

client.version

兼容逻辑

priority

降级、限流档位

不该放:

  • 密码、Token、Cookie、身份证号(会传到所有下游,还可能进日志)
  • 已经能从traceparent得到的东西(trace_id再塞一遍没有意义)
  • 只为了在 OpenObserve 里好看的字段(应写成 Span Attribute)
  • 又大又长的 JSON(每个出站请求都带,代理还可能截断)

头看起来像这样:

traceparent: 00-d333b8c15d39b13483cdb2a07c550203-87bedaf081d1c67b-01

tracestate: congo=t61rcWkgMzE

baggage: tenant=acme,region=cn-east,experiment=checkout-v2

订单服务 Extract 后可以baggage.get("tenant")去查对应租户库。若还想在 Trace 里看见tenant,必须再span.set_attribute("tenant", "acme")——Baggage 不会自动变成 OpenObserve 上的字段。

注意两点:

  1. 别放密钥、Token、身份证号。 Baggage 会传到所有下游,日志和中间件都可能打出来。
  2. 体积要小。 每个出站请求都会带上,太大既浪费带宽,也可能被代理截断。

Trace 回答“这次调用怎么走”;Baggage 回答“这次调用随身带了哪些业务标签”。

Baggage 不是协议必选项;没有租户/实验/路由这类跨服务业务上下文,就可以不传。有了,再选上面那些「下游真会读」的键,而不是把整个用户对象塞进去。


Profiles(性能剖析)是“代码在忙什么”的采样快照,和 Trace / Metrics / Logs 并列,是遥测里的第四类信号。

它不回答这次请求怎么走(那是 Trace),也不回答每秒多少次(那是 QPS 这类 Metrics)。它回答:CPU / 内存花在哪些函数上。

它长什么样:

运行时按固定频率(例如每秒几百次)打断正在执行的线程,记下当时的调用栈:

main

└ handle_checkout

└ query_inventory

└ pg_query ← 采样时经常停在这里

把成千上万次这样的栈叠在一起,就得到一份 Profile:哪个函数出现次数多,就说明它占的 CPU(或分配的内存)多。常见形态是火焰图(Flame Graph):横轴是占比,纵轴是调用深度。

常见类型:

类型看什么

CPU

时间花在哪些函数

Heap / 内存

谁在分配、泄漏嫌疑

Allocations

分配次数/大小

Goroutine / 线程

阻塞、等待

Go 的pprof、Java 的 JFR、Linux 的perf、eBPF 连续剖析,导出的都是这类数据。OpenTelemetry 也在把 Profiles 做成和 Traces 一样可上报的信号(OTLP Profiles)。

和另外三类怎么配合:

信号粒度典型问题

Metrics

整服务、一段时间

QPS 高了、CPU 90%

Logs

一条事件

报错写了什么

Traces

一次请求、跨服务

checkout 慢在订单服务

Profiles

进程内函数级

订单服务慢在pg_query还是 JSON 序列化

典型用法:Metrics 发现 CPU 高 → Trace 定位到某个 span 很慢 → Profile 指出是哪一个函数。有的实现还能把 Profile 和trace_id/ span 对齐,叫做Trace-associated profiling

和 Baggage / traceparent 无关:

Profiles 不是传给下一跳的 HTTP 头。它是本进程自己采的栈,经 OTLP 或 pprof 发到 OpenObserve 这类后端。链路传播仍是traceparent+ 可选tracestate/baggage

Trace 告诉你慢在哪一跳,Profile 告诉你那一跳里慢在哪几行代码。


B. 规范(Specification)——大家必须遵守的契约

规范分三块,排障时对应不同问题:

规范块管什么遇到的典型问题

API

代码里怎么打点(start span、记 attribute)

根本没生成数据

SDK

怎么采样、批处理、重试、加 Resource

生成了但没发出去 / 被采样丢掉

Data

语义约定(字段名)+ OTLP(怎么编码传输)

发出去了但对不上字段 / 对端收不下

API 是「插座形状」,SDK 是「插头实现」,OTLP 是「电怎么从这根线送到对面」。


API 是打点口令,SDK 是落地实现,DATA 是网上真正跑的那份遥测。这三层就是 OpenTelemetry 规范的骨架。

业务代码
│ 只调用 API(Tracer.start_span …)

SDK(采样、批处理、Processor、Exporter)
│ 把内存里的 Span 编成约定格式

DATA ── OTLP JSON/protobuf ──► 接收端 / OpenObserve
字段名走语义约定(service.name、http.route …)

跨服务的traceparent不在这三层里,它是另一条线:把 Context 身份复制到下一跳。三层管的是「本进程怎么产生、处理、交出去」。

(1)API:业务只该依赖这一层

API 规定怎么说话,不规定数据去哪。Python 里就是opentelemetry-apiTracerSpanContextset_attribute

例如 gateway 里这段是 API:

with tracer.start_as_current_span("GET /checkout", kind=SpanKind.SERVER) as span: span.set_attribute("http.request.method", "GET") span.set_attribute("http.route", "/checkout") headers = {"Content-Type": "application/json"} PROPAGATOR.inject(headers) req = urllib.request.Request( "http://127.0.0.1:18081/orders", data=b"{}", headers=headers, method="POST", )

特点:

  • 没装 SDK 时,这些调用是 No-Op(空操作),线上零开销、不会报错。
  • 框架(Django、FastAPI、HTTP 客户端)只依赖 API,换导出后端不用改业务。
  • API 不采样、不组包、不联网。

类比:插座标准。设备只认插孔形状,不管后面接的是哪家电厂。

(2)SDK:把口令变成真的 Span

SDK 是 API 的实现:opentelemetry-sdk。例如_provider()整段都是 SDK:

def _provider(service: str, sampler=ALWAYS_ON) -> TracerProvider: provider = TracerProvider( resource=Resource.create({"service.name": service, "lab.source": "official-sdk"}), sampler=sampler, ) exporter = OTLPSpanExporter(endpoint=OTLP_ENDPOINT, timeout=5) provider.add_span_processor(SimpleSpanProcessor(exporter))

它负责四件业务不该自己写的事:

职责你们实验里的对应

生成 ID、维护当前 Context

start_as_current_span,子 span 自动挂父

采样

ALWAYS_ON/ 步骤里的ALWAYS_OFF

Resource(谁发出的)

service.name=gateway

Processor + Exporter

Span End 后立刻走 OTLP HTTP

没有 SDK,API 再怎么start_span,接收端也收不到任何东西。

生产里还常加批处理(BatchSpanProcessor)、尾采样、多导出器。那都是换 SDK 配置,不是改 API。


API 是菜单,SDK 是后厨。 点菜时只看菜单;菜从哪炒、油多油少、怎么装盒送走,是后厨的事。

a. API:在代码里喊的那几句话

就是「开始记一笔」「给这笔加个标签」「结束」。
比如:开始记GET /checkout,记下这是 GET、路径是/checkout

不管后面有没有人真的记下来。没装后厨时,这几句话等于对空气说——不报错,也不产生数据。业务、框架只该跟菜单打交道,这样换观测后端不用改业务代码。

b. SDK:真去干活的那套程序

听到开始记,它才:编一个 ID、决定采不采、记开始 / 结束时间、请求一结束就打包,通过网线发给 OpenObserve。

没装 SDK,再怎么喊,接收端也是空的。采样开还是关、批量发还是立刻发、发到哪,都是调 SDK,不是改菜单上的那几句。

例如写start_span是在用 API;能把这次 checkout 真的变成 OpenObserve 里的一条链路,靠的是 SDK。


(3)DATA:字段叫什么 + 网上怎么运

规范把语义约定OTLP合称 DATA:后端只认这份契约,不认 用的是 Python SDK 还是手写 JSON。

1. 语义约定(名字)

同一件事必须用同一个键,OpenObserve 才能按服务、按路由聚合:

  • service.name:谁
  • http.request.method/http.route:哪条 HTTP
  • db.system/db.operation:哪类数据库操作

例如 order 服务写的db.system=postgresql就是在遵守 DATA 的命名,不是随便起的属性名。

2. OTLP(运输)
包结构固定为resourceSpans → scopeSpans → spans,编码可以是 protobuf 或 JSON。simulate.py不经过 SDK,自己组的就是 DATA:

payload = { "resourceSpans": [ { "resource": { "attributes": [ {"key": "service.name", "value": {"stringValue": service}}, ] }, "scopeSpans": [ { "scope": {"name": "otel-lab.protocol", "version": "1.0.0"}, "spans": [span], } ], } ] }

所以两条实验线能打进同一个:4318并拼成一棵树:接收端只解析 DATA,不管上游是 SDK 还是手写。

DATA 里一份 Span 至少要有:traceIdspanId、可选parentSpanIdname、起止时间、status、attributes。Metrics / Logs / Profiles 各有自己的 DATA 形状,但分层关系一样。


4317 和 4318是官方写进 OTLP 规范的默认端口,但不是世界上只能用这两个口,也不是像 80 那样由 IANA 独占。

OpenTelemetry 协议(OTLP)里写明:

端口默认干什么

4317

OTLP/gRPC(二进制 protobuf,一条连接可发 traces/metrics/logs)

4318

OTLP/HTTP(POST 到/v1/traces/v1/metrics等)

Collector、各语言 SDK 不配 endpoint 时,通常就连localhost:4317(gRPC)或localhost:4318(HTTP)。如果走 HTTP、本机没开 gRPC,那么就使用4318。

需要分清三件事:

  1. 默认,不是唯一。 可以改成任意端口。OpenObserve 云上常见是https://…:443/api/default/v1/traces,根本不是 4318。
  2. 不是这个端口天生就是 OTLP。 谁先监听谁用。在操作前要先检查 4318 空闲,就是怕被别的程序占了。
  3. URL 里不写端口时,不会自动变 4318。 普通 HTTP 仍是 80、HTTPS 仍是 443。SDK 只有在用 OTLP 默认 endpoint 时才会带上 4317/4318。

4317/4318 属于 DATA 怎么运出去 的约定。业务调的是 API,SDK 组包后往这个默认门口送;门口换了,只要路径还是/v1/traces、格式还是 OTLP,协议本身不变。


(4)三层怎么叠在一次 checkout 上

GET /checkout
API start_span("GET /checkout") + set_attribute(...)
SDK 采样通过 → End → SimpleSpanProcessor → OTLPSpanExporter
DATA protobuf POST /v1/traces
resource.service.name = gateway
span.name = GET /checkout
span.http.route = /checkout

另:Inject 写出 traceparent(身份,不是 DATA 包体)
↓ HTTP
order Extract → 又一套 API/SDK → 再一份 DATA(service.name=order-service)

对应装的三个包:

opentelemetry-api

API

opentelemetry-sdk

SDK

opentelemetry-exporter-otlp-proto-http

把 SDK 内存对象编成 DATA(OTLP)

simulate.py跳过前两层,直接写 DATA,用来证明:拼树只认 DATA +traceparent,不认某一家 SDK。


(5)和前面几个词的关系

  • 遥测数据 = DATA 交出去的那批 Metrics / Logs / Traces / Profiles
  • traceparent/ Baggage = 传给下一跳服务的头,不是 OTLP 包
  • QPS = DATA 里 Metrics 的一种
  • Profiles = DATA 的第四种信号,不是 API 里多了一个函数那么简单,要另有 Profiling API/SDK

业务写 API,进程里跑 SDK,OpenObserve 只吃 DATA。换语言、换 SDK、甚至手写 JSON,只要 DATA 合规,后端看到的是同一类遥测。


C. 实现(Implementations)——规范的落地代码

  • 各语言 SDK(Java / Go / Python …)
  • Collector(接收、处理、再导出)
  • Operator / Helm(K8s 里管 Collector 和自动插桩)
  • 各种插桩库(HTTP、DB、gRPC 自动打点)

这些是 OTel 项目产出的软件,仍然不是「Logs / Traces / Metrics 三个产品」。

D. 后端产品(Backends)—— 存和查

Jaeger、Prometheus、Zipkin、OpenObserve、各类商业 APM。

它们消费 OTLP(或兼容格式),负责存储和界面。
OTel 不规定 必须用哪家后端;换产品通常不用改业务打点,只改 exporter 的地址。


0.3 每个信号的「四件套」(官方结构)

官方把每个信号拆成四层(见 Specification Status):

API → SDK → OTLP → Collector

打点 处理导出 传输 中转(可选但生产常用)

对照:

信号APISDKOTLPCollector

Tracing

Stable

Stable

Stable

同协议,Stable

Metrics

Stable

mixed

Stable

同协议

Logging

Bridge API Stable

Stable

Stable

同协议

Baggage

Stable

Stable

没有

没有

Profiles

演进中

演进中

Development

同协议

这里有两个容易错的点:

1. Baggage 为什么没有 OTLP?
Baggage 不是给后端画图用的,它是 顺着请求往下游传的键值(走baggageheader 等),下游代码自己读。它不导出到 OpenObserve,所以没有 OTLP、也没有 Collector 管道。

2. Collector 的稳定度和 OTLP 绑在一起
Collector 能稳定收某种信号,前提是这种信号的 OTLP 已经稳定。Profiles 的协议还是 Development,生产上不要当主力。


0.4 数据走完一整条路,OTLP 只负责其中一跳

排障时最有用的是这张生命周期,请能默画:

① 生成 代码 / 自动插桩调用 API,造出 Span / Metric / Log

② 处理 SDK:加 Resource、采样、批量、队列

③ 导出 SDK 用 OTLP 发给下一跳(本机 Collector 或直接后端)

④ 中转(可选) Collector 再处理,再 OTLP 发给后端

⑤ 存储 OpenObserve 等收下、建索引

⑥ 查询 在界面 / SQL 里搜 trace_id

OTLP 规范写得很明确:它只保证 ③ 或 ④ 里某一对 client↔server 之间交割清楚(成功、部分成功、或明确失败)。
它不保证从应用一直到你点开瀑布图端到端不丢。多一跳 Collector,就多一次独立的 Export 确认

所以:

  • 应用日志里已经有trace_id,OpenObserve 没有这条 Trace
    → 不一定是产品坏了,可能停在 ② 采样、③ 导出失败、④ Collector 丢掉、⑤ 写失败、⑥ 查错 stream/时间。
  • 这正是要建立的习惯:先问停在第几格,再打开对应的那一层,而不是先猜界面。

0.5 用一个具体场景把对象钉死

假设:checkout服务处理一笔下单,数据进 OpenObserve 的test_otel

看到的东西属于哪一类对象

瀑布图上的一条链路

信号:一条 Trace(许多 Span)

Span 上的http.route

语义约定(规范 Data)

服务里的 Java Agent / SDK

实现

OTEL_EXPORTER_OTLP_ENDPOINT=http://collector:4318

告诉 SDK 用 OTLP/HTTP 发到下一跳

Collector 的otlpreceiver + exporter

实现,中间又走一次 OTLP

OpenObserve 里 streamtest_otel

后端产品 的存储单元

日志里的trace=...

仍是 Logs 信号,用同一个 Context 里的trace_id对齐

没有「OTel Trace 产品」这种东西。Trace 是信号;OTLP 是运货协议;OpenObserve 是仓库。


0.6 常见混用

容易说错更准确

「OTel 就是链路追踪」

OTel 管三类主力信号 + 传播;链路只是其中一种

「学 OTel 协议就是学 OpenObserve」

产品是查询层;协议是生成和运输层

「Logs、Trace、Metric 是 OTel 三个产品」

三种信号

「OTLP 保证数据一定到得了界面」

只保证这一跳 Export;后面每跳、采样、查询都可能让你「看不见」

「没数据就是 OTel 坏了」

先分:没生成 / 被采样 / 没导出 / 中转丢 / 后端拒 / 查错

「Baggage 也是一种监控」

它是跨服务传上下文,不进 OTLP,不能当指标用


  1. Logs、Traces、Metrics 是产品、协议,还是信号?
  2. OTLP 保证的是端到端不丢,还是一跳 client↔server?
  3. Baggage 为什么没有 OTLP?
  4. OpenObserve 在 A/B/C/D 里属于哪一类?
  5. 日志里有 trace_id,界面没有这条 Trace可能停在生命周期的哪几格?

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询