遥测与 OpenTelemetry
telemetry、OTel SDK 概览。
遥测与 OpenTelemetry
这一课你将能做到
- 用自己的话解释 Telemetry,并对照 OTel 的 Traces / Metrics / Logs 三大 signal,说清每种信号解决什么问题
- 画出 SDK → Collector → Exporter/后端 的数据流,说明各层职责和判题系统怎么接入
- 解释「一套插桩、多种后端」如何降低迁移成本,并给出 Micrometer/旧 APM 与 OTel 的共存策略
为什么要学
过去 Zipkin、Jaeger、各云厂商 APM、Micrometer 各有 API,换后端常等于换埋点——团队被绑死在某个厂商,迁移成本极高。OpenTelemetry(OTel) 是 CNCF 下的开放标准:统一 API/SDK 与传播语义(W3C trace context),后端可换(Prometheus、Jaeger、Tempo、Loki、厂商 APM 等)。
判题链路横跨 Java backend、Python 判题 worker、Nginx 网关、Kafka 消息队列,多语言和多组件的遥测数据如果各自为政,排障时就是「每扇门配一把钥匙」。面试里「OTel 架构是怎样的」「和 Prometheus/Micrometer 是什么关系」是高频题,本课建立概念地图;Spring 落地见 obs-spring,插桩见 obs-instrumentation。
先建立图像
| 组件 | 职责(人话) | 判题系统中的角色 |
|---|
| OTel API/SDK | 应用内产生 span / metric / log 信号 | backend 和 worker 代码里的埋点 |
| Instrumentation | 自动或手动把框架调用变成信号 | Java agent 自动采集 MVC/JDBC/Kafka;Python agent 自动采集 HTTP 请求 |
| OTel Collector | 接收、批处理、采样、路由、格式转换 | 集群内统一收集,再分发给各后端 |
| Exporter | 写出到具体后端 | OTLP → Jaeger;Prometheus remote write;日志文件 → Loki |
判题系统的 OTel 数据流:
浏览器 ──trace context──► Nginx(带 W3C header)
│
▼
┌───────────────────────┐
│ OTel Java Agent │
│ - Spring MVC Span │
│ - JDBC Span │
│ - Kafka Producer Span │
│ - Micrometer bridge │
└───────────┬───────────┘
│ OTLP (gRPC/HTTP)
▼
┌─────────────────┐
│ OTel Collector │
│ receivers: │
│ otlp, jaeger │
│ processors: │
│ batch, tail │
│ exporters: │
│ otlp/jaeger │ → 链路后端
│ prometheus │ → 指标后端
│ otlp/logs │ → 日志后端
└─────────────────┘
│
┌───────────────────┼───────────────────┐
│ │ │
▼ ▼ ▼
┌────────────┐ ┌────────────┐ ┌────────────┐
│ Jaeger │ │ Prometheus │ │ Loki │
│ (traces) │ │ (metrics) │ │ (logs) │
└────────────┘ └────────────┘ └────────────┘
并行现实(常见过渡方案):
Micrometer → /actuator/prometheus → Prometheus scrape
与 OTel traces 并存,逐步收敛为统一 OTLP 出口
跟着做
步骤 1:画判题系统的 OTel 数据流
A. 指标路径:Micrometer → /actuator/prometheus → Prometheus(scrape)
B. 链路路径:OTel Java Agent → OTLP → Collector → Jaeger
C. 日志路径:logback JSON → filebeat → Loki(含 traceId 字段)
应得到的核心结论:过渡期双栈是合理的——不必第一天全部切到 OTLP,先让链路统一标准,指标和日志逐步收敛。
步骤 2:理解 Collector 的价值——tail sampling
Head sampling(应用内):
- 优点:实现简单,不占用网络带宽
- 缺点:应用进程承担策略,每个实例独立采样,无法按「整链是否错误」决定保留
Tail sampling(Collector 层):
- 优点:整链完成后判断——「有错误」或「总耗时 > 3s」的全链路保留,其他丢弃
- 缺点:需要 Collector 缓存整链数据,内存开销增加
判题场景建议:开发环境 head sampling 10% 即可;生产环境用 Collector tail sampling,保留错误链路和慢链路(如判题总耗时 > 10s),正常链路采样 1%。
步骤 3:看一段 Collector 导出配置(可运行概念)
# otel-collector-config.yaml —— 接收 OTLP,同时导出到 Jaeger 和 Prometheus
receivers:
otlp:
protocols:
http:
endpoint: 0.0.0.0:4318
grpc:
endpoint: 0.0.0.0:4317
processors:
batch:
timeout: 1s
send_batch_size: 1024
tail_sampling:
policies:
- name: errors
type: status_code
status_code: {status_codes: [ERROR]}
- name: slow
type: latency
latency: {threshold_ms: 10000}
exporters:
otlp/jaeger:
endpoint: jaeger-collector:4317
tls:
insecure: true
prometheusremotewrite:
endpoint: http://prometheus:9090/api/v1/write
# 生产需加 tls 和 auth
service:
pipelines:
traces:
receivers: [otlp]
processors: [tail_sampling, batch]
exporters: [otlp/jaeger]
metrics:
receivers: [otlp]
processors: [batch]
exporters: [prometheusremotewrite]
应得到:管道按 signal 类型分开;同一接收可扇出到多个后端;tail sampling 策略集中管理。
步骤 4:验证 W3C trace context 跨边界传播
判题链路涉及多个进程,trace context 靠 W3C 标准头 traceparent 传播。检查清单:
1. Nginx → backend:检查 Nginx 是否透传 traceparent header
# nginx.conf
proxy_pass_request_headers on;
# 或使用 opentelemetry-nginx-module
2. backend → Kafka:OTel Kafka producer 自动在消息头里塞 trace context
消费端需确认:consumer 是否从 header 恢复 context
3. backend → 外部 HTTP:RestTemplate/WebClient 自动传播(OTel agent 支持)
手写 HTTP client 时需手动加 header:traceparent=..."
应得到:链路断在哪,就查哪个环节的 header 有没有透传。
步骤 5:制定迁移一句话策略
「新服务(如新语言 worker)强制 OTel agent/SDK 统一接入;
现有 Java backend dual-write 三个月(Micrometer + OTel traces 并存);
Collector 统一扇出到 Prometheus/Jaeger/Loki;
验证数据一致性后,再逐步下线旧 agent。」
讲清楚要点
- 遥测含三类信号,OTel 不是「只做 Tracing 的库」——Traces、Metrics、Logs 统一在同一项目下演进,共享 context 和语义约定。
- 应用不应绑死后端,所以 SDK 产出 + Collector 路由是推荐分层——换后端只需改 Collector exporter,不动应用代码。
- 传播格式已有 W3C trace context 标准,优先用标准头——减少网关和多语言间的摩擦,避免各团队「发明自己的头」。
- Micrometer 在 Spring 生态很强,所以指标路径常与 OTel 链路并存——用 bridge 或双写过渡,不要搞宗教战争。
- Collector 能处理批处理、采样、扇出,所以重逻辑上移可降低每个服务重复造轮子——采样策略、数据 enrich(如加 k8s 元数据)、格式转换都在 Collector 做。
- 语义约定让仪表盘可复用,自定义属性要克制且文档化——HTTP span 名用
http.method + http.route,属性用 http.status_code 而不是 code 或 status。
- K8s 常部署 Collector 为 Deployment 或 DaemonSet,所以遥测方案含集群运维面——见 kubernetes-essentials。
你可能会懵的地方
-
OTel 和 Jaeger 是什么关系?
OTel 是「怎么产生和传输信号」的标准;Jaeger 是一种「追踪后端」,接收并存储追踪数据。OTel SDK/Collector 可以把 trace 发到 Jaeger,也可以发到 Tempo、Zipkin、云厂商 APM。
-
没有 Collector 能用吗?
能。SDK 可以直接 export 到后端(如 OTEL_EXPORTER_OTLP_ENDPOINT 指向 Jaeger)。规模上升后 Collector 的优势才显现:统一采样策略、尾部采样、多后端扇出、数据 enrich。
-
Logs 在 OTel 里成熟吗?
相对 traces 和 metrics,logs 集成仍在快速演进。实践上常先「日志系统(Loki/ELK)+ traceId 关联」先行,再逐步接入 OTel logs pipeline。成熟顺序:traces > metrics > logs。
-
这和 prometheus-essentials 冲突吗?
不冲突。Prometheus 仍是指标后端与告警主力;OTel 解决「如何统一产生和运输信号」。OTel Collector 可以导出 Prometheus remote write,也可以让 Micrometer 继续走 /actuator/prometheus scrape,两条路并存。
-
Python worker 和 Java backend 的 trace 能连起来吗?
能,只要都遵循 W3C trace context。Kafka 消息头里的 traceparent 会被各语言的 OTel SDK 识别,span 会挂到同一 traceId 下。前提是:消费端也要集成 OTel SDK(Python agent 或手动恢复 context)。
错法对照
| 错法 | 问题 | 正解 |
|---|
| 每个后端换一次应用埋点 API | 迁移成本极高,各服务口径不一致 | OTel API + Collector 扇出 |
| 应用内写死厂商 agent 配置散落各处 | 改配置要改几十处 | 配置集中到 Collector,应用只发 OTLP |
| 拒绝 Micrometer,强行一天切完 OTLP | 业务风险高、排障断档 | 分信号、分阶段收敛,双写验证 |
| 自定义属性名完全随心 | 各服务「方言」不通,仪表盘无法复用 | 遵循 semantic conventions |
| 以为上了 OTel 就不用告警设计 | 信号标准 ≠ 告警产品自动变好 | 告警设计仍是人工工程(见 obs-monitoring) |
卡点急救
- OTel agent 启动后没有 trace 数据:先确认
OTEL_EXPORTER_OTLP_ENDPOINT 指向 Collector 地址且网络可达;再检查 agent 版本与 JDK/Spring Boot 版本的兼容矩阵;最后看 Collector 日志是否有接收错误或 exporter 失败。
- 跨服务 trace 断成多截:逐层检查 traceparent header 是否透传。Nginx 层用 curl
-v 看 response 有没有带 traceparent;Kafka 层检查消息头里有没有 traceparent 字段;消费端日志打印 Span.current().getSpanContext().getTraceId() 确认是否恢复成功。
- Collector 内存持续增长:多半是 tail sampling 缓存未设上限,或 batch processor 的队列堆积。调整方向:减小 tail sampling 的等待窗口;限制 batch size;检查 exporter 后端是否阻塞(网络慢或后端不可用会导致内存堆积)。
面试常问
-
OTel 的架构是怎样的?SDK、Collector、Exporter 各干什么?
背这个版本:SDK 在应用进程内,负责产生 spans/metrics/logs,管理 context 传播。Collector 是独立进程,负责接收(receivers)、处理(processors,如 batch、采样、enrich)、导出(exporters)到各种后端。Exporter 是 SDK 或 Collector 里写出到具体后端的模块,如 OTLP exporter、Prometheus remote write exporter。分层好处:应用只关心「产生标准信号」,后端切换只在 Collector 层改配置。
-
为什么「一套插桩、多种后端」能降低迁移成本?
没有 OTel 时,换后端(如从 Jaeger 切到云厂商 APM)等于重写所有服务的埋点代码——API 不同、传播格式不同、标签语义不同。OTel 把埋点层统一为标准 API,后端通过 Collector exporter 配置切换,应用代码不动。这符合「可移植观测契约」原则,避免被单一厂商锁定。
-
Micrometer 指标和 OTel 链路怎么共存?
两种合理方案:A. 过渡期双栈——Micrometer 继续走 /actuator/prometheus scrape 到 Prometheus,OTel agent 负责 traces 发到 Collector → Jaeger,互不干扰。B. 收敛方案——Micrometer 通过 OTel bridge 把指标转成 OTLP,统一走 Collector 扇出到 Prometheus。选哪种取决于团队节奏,不要硬切。
自测三问
- SDK、Collector、Exporter 各干什么?数据流是怎样的?
- 为什么「一套插桩、多种后端」能降低迁移成本?用判题系统举例说明。
- 说明一种 Micrometer 指标与 OTel 链路并存的合理架构,并指出过渡期风险。
小结一句话
OTel 统一「怎么采怎么传」,后端各自「怎么存怎么查」——用 Collector 与语义约定保住可移植性。
延伸