医院里曾经有三个互不相通的科室:体检科负责量血压、拍片子,护士站负责写护理记录,导诊台负责追踪病人在各楼层间的流转路径。病人明明只有一位,病历却是三份各写各的。后来院长拍板:把三份单子统一成一份电子病历,所有科室按同一种格式采集、同一种编号关联、同一套系统下发——这套“标准”,就是可观测性世界里的 OpenTelemetry。 在软件系统里,Metrics 是“生命体征”(每秒请求数、CPU 使用率、错误率),Logs 是“护理记录”(某时刻发生了什么具体事件),Traces 是“就医路径”(一个请求从入口到数据库经历了哪些服务)。过去我们要搭三套独立系统,监控告警相互割裂;OpenTelemetry(简称 OTel)把它们统一成一套标准:一套 SDK 完成埋点,一个 Collector 完成采集转发,后端想接 Prometheus、Jaeger 还是云厂商,随时切换。本篇文章将带你用零基础的方式,把这三根柱子一次性认全。

岗位关联

对 AI 从业者而言,掌握 OTel 意味着你能用一套标准接入 AI 模型服务的监控、日志与链路追踪,降低可观测性建设成本,也让自己在 SRE/DevOps 面试中手握“统一标准”的加分项。

目录
  1. 可视化演示
  2. 统一标准背景
  3. Metrics 指标体系
  4. Logs 日志体系
  5. Traces 链路追踪
  6. Collector 架构
  7. 后端对接方式
  8. AI 应用场景
  9. Python 演示
  10. 常见误区
  11. 面试题

可视化演示

展示Metrics/Logs/Traces三支柱统一采集、Collector处理管线及分布式Trace传播链路,揭示OTel标准化可观测性核心机制。

本节配有交互动效(进入视口后自动加载,约 3KB)。

可观测性三支柱 Metrics:时序指标数据,监控计数器与仪表盘 Logs:结构化日志记录,诊断系统运行状态 Traces:分布式追踪链路,定位请求性能瓶颈 OTel = Metrics + Logs + Traces 三支柱统一采集消除数据孤岛

统一标准背景

想象一家医院,病人多、科室多、仪器多。一开始每个科室自己买系统:体检科有 A 厂的体检表,护士站有 B 厂的护理单,导诊台有 C 厂的流转单。表面的问题是“要多学三套软件”,暗面的问题更严重——三份单子上的“病人编号”互不相同,护士想查体检报告时,只能靠病人姓名人工拼接。

软件世界也如此。Metrics(指标)通常由 Prometheus 生态负责,Logs(日志)由 ELK 生态负责,Traces(链路追踪)由 Jaeger/Zipkin 生态负责。它们各自的 SDK、协议、语义完全独立,联调一个跨服务问题,要在三套系统间来回切换。这就是可观测性世界的“三份病历”困境。

OpenTelemetry(OTel)正是为了终结这种局面而生的开源标准。它由 CNCF 孵化,是 OpenTracing 与 OpenCensus 两个项目合并的产物。OTel 提供统一的 SDK(埋点库)、统一的协议 OTLP 和统一的采集器 Collector。业务代码只需要接入一套 SDK,数据就能以同一种语言进入后端。它的核心诉求用一句话概括:采集一次,到处使用。

回到医院比喻:统一标准之后,护士在护理单上填的“病人编号”,和体检科拍的片子、导诊台的流转记录自动共享同一个 ID。软件世界里,OTel 也有类似的“统一 ID 规范”,后文会看到它如何打通三根柱子。

OTel=SDK⏟埋点采集+Collector⏟集中处理+OTLP⏟统一协议\displaystyle \text{OTel}=\underbrace{\text{SDK}}_{\text{埋点采集}}+\underbrace{\text{Collector}}_{\text{集中处理}}+\underbrace{\text{OTLP}}_{\text{统一协议}}
这个等式把 OTel 拆成三件套:SDK 负责“从应用里取数”,Collector 负责“取完数后加工分拣”,OTLP 负责“用统一信封寄出”。
  1. SDK(埋点采集)嵌入到业务代码里的库,负责生成指标、日志和链路数据。它好比医院发给每个科室的“标准空白单”,任何人都用同一版式记录。
    SDK=instrumentation+export pipeline\displaystyle \text{SDK} = \text{instrumentation} + \text{export pipeline}
  2. Collector(集中处理)一个独立进程,接收来自各个服务的遥测数据,做过滤、批处理、重试后导出。它好比医院的“病历归档中心”,所有科室的单子先汇聚到这里,再分发给需要的地方。
    Collector:receiver→processor→exporter\displaystyle \text{Collector}: \text{receiver} \rightarrow \text{processor} \rightarrow \text{exporter}
  3. OTLP(统一协议)OTel 定义的传输格式,规定了数据怎么编码、怎么压缩、怎么传输。它好比医院统一的“电子病历信封”,不管内页是心脏科还是骨科,信封规格全球一致。
    OTLP=encoding+transport+payload schema\displaystyle \text{OTLP} = \text{encoding} + \text{transport} + \text{payload schema}

Metrics 指标体系

继续用医院做类比。体检科有一台“生命体征监护仪”,屏幕上滚动着心率、血压、血氧三条曲线——这就是 Metrics。它回答的问题是“系统现在健康吗”。运维人员盯着的 QPS、错误率、CPU 使用率、内存水位,都属于这一支柱。

OTel 将指标建模为带名称、时间戳、数值和一组标签(attribute)的采样点。指标数据的核心特征在于“可聚合”:单机内存不足不要紧,只要所有实例的指标汇总起来,就能看出集群整体健康状况。OTel 将常见指标类型归纳为三种:

  • Counter(计数器):只增不减,适合统计总请求数、总错误数。
  • Gauge(瞬时值):可增可减,适合描述内存使用量、当前连接数。
  • Histogram(直方图):记录数值分布,适合描述请求延迟的分位数。

回到体检科:监护仪每 15 秒刷新一次曲线,这个刷新间隔在 OTel 里叫采集间隔。SDK 按固定节奏将聚合后的指标推给 Collector,再由 Collector 转给后端。正是指标数据的“定期采样”特性,让它的数据量可控、查询最快——但也让它无法回答“当时到底发生了什么”这类细节问题,这就要靠下一根柱子:日志。

metric=name+timestamp+value+attributes,∀value∈{Counter,Gauge,Histogram}\displaystyle \text{metric}=\text{name}+\text{timestamp}+\text{value}+\text{attributes},\quad \forall \text{value} \in \{\text{Counter},\text{Gauge},\text{Histogram}\}
这句公式想表达:一条指标相当于“时间 + 数字 + 标签”的元组,而数字背后的计量方式分为累加型、瞬时型和分布型三类。
  1. name(指标名)指标的唯一标识,如 http.server.request.duration。语义约定保证同一个指标名在不同语言 SDK 里含义一致。
    name=metric family+unit\displaystyle \text{name} = \text{metric family} + \text{unit}
  2. value(数值)指标的测量结果。Counter 累加、Gauge 取当前值、Histogram 累积到桶内。
    value∈{monotonic sum,instant,distribution}\displaystyle \text{value} \in \{ \text{monotonic sum}, \text{instant}, \text{distribution} \}
  3. attributes(标签)描述指标观察对象的维度,如 method="GET"、status="500"。标签越多,切分查询越灵活。
    attributes={(k1,v1),(k2,v2),… }\displaystyle \text{attributes} = \{ (k_1, v_1), (k_2, v_2), \dots \}

Logs 日志体系

护士站有一本电子护理记录,值班护士每次巡视都往里追加一条:19:02 病人体温 38.5℃,已物理降温;19:30 复测体温 38.8℃,通知医生。这些记录按时间排列,是“护理日志”。软件系统里的 Logs 正是这样——由开发者在代码关键路径上打印的一段段事件描述。

日志记录的是“发生了什么”这一具体事实,因此它信息量最大,但也最“散”。在 OTel 的标准里,一条日志被建模为 LogRecord,包含时间戳、日志级别、日志正文和一组属性。为了支持自动检索,日志还需要结构化:把 "time=19:02 level=ERROR msg=rds timeout service=auth" 这样的文本,解析成可查询的键值对。

回到医院场景:护理日志写得再好看,如果无法和体检数据关联,医生仍然要手工比对。OTel 的方案是让日志自动携带 traceId——一旦日志带上这个“与链路共享的编号”,就能在调大故障时循线找到前端和后端。这就是日志与链路的联动,也是三支柱统一的价值所在。

LogRecord=timestamp+level+body+attributes\displaystyle \text{LogRecord} = \text{timestamp} + \text{level} + \text{body} + \text{attributes}
类比来看:写日志好比记病房观察笔记,除了写“今天病人很躁动”,还得规范写清“哪床、哪时、谁观察、伴随指标”——完整字段才能支撑后续检索。
  1. timestamp(时间戳)事件发生的精确时间。同一条请求的日志,时间戳是最重要的排序依据。
    tevent=unix epoch+nanosecond offset\displaystyle t_{\text{event}} = \text{unix epoch} + \text{nanosecond offset}
  2. level(级别)INFO/WARN/ERROR 等严重程度分级,用于过滤噪音。
    level∈{D,I,W,E,F}\displaystyle \text{level} \in \{ \text{D}, \text{I}, \text{W}, \text{E}, \text{F} \}
  3. body(正文)日志主体内容,通常是人类可读的自然语言。
    body=free-form text+optional template variables\displaystyle \text{body} = \text{free-form text} + \text{optional template variables}
  4. attributes(属性)Otel 的日志属性常包含 traceId、spanId 等关联字段,让日志能挂到具体链路节点上。
    attributes⊃{traceId,spanId,service.name}\displaystyle \text{attributes} \supset \{ \text{traceId}, \text{spanId}, \text{service.name} \}

Traces 链路追踪

导诊台有一张“就医路径表”:病人 10:00 挂号 → 10:15 抽血 → 10:40 心电 → 11:20 复诊。这张表记录病人“去过哪、何时去、花了多久”,对应的正是软件世界里 Traces 做的:追踪一个请求自进入网关到调用数据库的完整路径。

一个 Trace 由多个 Span 组成。Span 是追踪的基本单元,代表一次具名操作,比如 HTTP 请求、数据库查询、消息队列发送。每个 Span 记录操作名、开始时间、结束时间和标签;多个 Span 通过父子关系组成一棵调用树——就像就医路径表里的每步检查都记录着“上一个环节是谁”。

OTel 沿用了 Dapper/OpenTracing 的模型。为了把追踪信息跨服务传递,OTel 遵循 W3C TraceContext 规范,在一个名为 traceparent 的标准 HTTP 头里写入 TraceId 与 SpanId:

回到导诊台视角:如果每次跨科室信息传递都按同一张“路径表”记录,院长想复盘病人就医全流程,只需按 trace-id 把各科室的记录串起来。同理,在大规模微服务架构中,OTel 能让一个请求从网关到数据库的每一跳都有据可查,故障定位从“猜”变成“看树”。

traceparent=version−trace-id−parent-id−flags\displaystyle \text{traceparent} = \text{version}-\text{trace-id}-\text{parent-id}-\text{flags}
这个头部好比“追踪接力棒”:服务 A 把棒交给服务 B,B 再交给 C,每个服务在棒上盖一个“我处理过的戳”(spanId),最终可还原完整路径。
  1. trace-id(全局追踪号)32 位十六进制随机数,唯一标识一整条请求链路。所有服务共享同一 trace-id,日志与链路靠它关联。
    trace-id∈{0,1}128\displaystyle \text{trace-id} \in \{0,1\}^{128}
  2. parent-id(父 Span 标识)上一跳服务生成的 spanId。通过 parent-id 就能在分析端画出一棵 Span 树。
    parent-id=spanId of parent span\displaystyle \text{parent-id} = \text{spanId of parent span}
  3. flags(标志位)表示采样标记,例如 01 代表“这条链路已被采样记录”。
    flags=sampled∈{00,01}\displaystyle \text{flags} = \text{sampled} \in \{00, 01\}

Collector 架构

医院改革进入第二步:每个科室还保留自己的仪器,但所有化验单、护理单、流转单先统一交到新设立的“病历归档中心”。这个中心不关心单子最后由哪家保险公司结算,只负责三件事:收单、标准化、转交。OTel 世界中,承担“归档中心”职责的就是 Collector。

Collector 是一个独立部署的进程,它向上接收来自 SDK 的数据,向下连接一个或多个后端。它内部定义了清晰的三段式处理管道:

  • 接收器(Receiver):决定数据怎么进来,支持 OTLP、Prometheus 等格式;
  • 处理器(Processor):对数据做裁剪、采样、批处理、加标签;
  • 导出器(Exporter):决定数据往哪儿去,支持 OTLP、Jaeger、Prometheus 等后端协议。

回到医院归档中心:它对外展示统一的收单窗口,对内提供各种转交格式,于是“病历归档中心”部署在靠近病人的科室(Agent 模式),还是集中分布于院区(Gateway 模式),完全取决于人流与网络情况。OTel 的 Collector 同样支持两种部署模式:Agent 模式随业务实例部署在节点上,低延迟、本地缓冲;Gateway 模式独立部署在集群内,适合统一出口策略。正是这种“采集与后端解耦”的设计,让换监控后端成为改配置而非改代码。

receiver→decodeprocessor→enrich/batchexporter→encodebackend\displaystyle \text{receiver} \xrightarrow{\text{decode}} \text{processor} \xrightarrow{\text{enrich/batch}} \text{exporter} \xrightarrow{\text{encode}} \text{backend}
这组管道如同病房的手工台:护士先接单(receiver),检查填写规范并补充缺失信息(processor),最后按目的地分箱寄出(exporter)。
  1. receiver(接单窗口)从各服务 SDK 拿到 OTLP 数据,解包为内部数据模型。
    receiver:wire format→pdata\displaystyle \text{receiver}: \text{wire format} \rightarrow \text{pdata}
  2. processor(分拣与加工)执行过滤掉 debug 日志、按 traceId 采样、攒一批再发等策略。
    processor:pdata→pdata′\displaystyle \text{processor}: \text{pdata} \rightarrow \text{pdata}'
  3. exporter(寄送出口)把数据编码为后端期望的格式(如 Prometheus Remote Write、Jaeger Thrift)。
    exporter:pdata′→backend format\displaystyle \text{exporter}: \text{pdata}' \rightarrow \text{backend format}

后端对接方式

电子病历统一之后,医院面临最后一公里:保险报销平台只认 PDF,卫健委平台只认 XML,科研中心只认 CSV。归档中心不会要求所有科室改单子,而是自己准备不同的转交模板,把统一病历翻译成各平台要的格式。OTel 的最后一公里,正是“后端对接”。

OTel 将数据从 SDK 到后端的传输统一为 OTLP 协议。OTLP 是 gRPC/HTTP 之上的二进制编码,支持指标、日志、链路三种数据的传输。由于后端种类繁多,OTel 采取“双管齐下”策略:

  • 后端支持 OTLP 则直接用;
  • 后端只认自家协议,则由 Collector 的导出器完成格式转换。

回到医院比喻,这相当于“病历只填一次,分发到处都能用”。对开发者最直接的收益是:公司从自建 Prometheus 迁到云厂商时,不需要改动业务代码,只需调整 Collector 配置文件。OpenTelemetry 统一标准让“一份数据、多种用途”成为现实——这正是它作为可观测性标准的价值所在。

backend∈{OTLP-native}∪{vendor formats converted by exporter}\displaystyle \text{backend} \in \{\text{OTLP-native}\} \cup \{\text{vendor formats}\text{ converted by exporter}\}
通俗讲:医院先统一内部病历,再让归档中心做“翻译官”。应用层只用写一次 OTel 埋点,后面接 Prometheus、Jaeger、Datadog 还是云厂商,都是配置层面的事。
  1. OTLP-native(原生支持)Jaeger、Grafana Tempo、云厂商后端可以直接接收 OTLP。
    send→OTLPbackend\displaystyle \text{send} \xrightarrow{\text{OTLP}} \text{backend}
  2. vendor formats(厂商格式)Prometheus 使用 Remote Write,Zipkin 使用 Thrift,由 Collector 导出器适配。
    send→exporter adapterPrometheus/Zipkin\displaystyle \text{send} \xrightarrow{\text{exporter adapter}} \text{Prometheus/Zipkin}
  3. 多后端同时导出一份数据可同时发给监控告警、链路分析和日志检索三套系统,互不冲突。
    fan-out:1→N backends\displaystyle \text{fan-out}: 1 \rightarrow N \text{ backends}

AI 应用场景

AIOps 统一数据底座

阶段:数据采集与实时监控

解决的问题:传统监控三套系统数据割裂,AIOps 平台做异常检测时,无法将指标突刺、错误日志、调用链放大三个维度的信息关联到同一次故障。OTel 统一采集后,AIOps 算法可以拿到带 traceId 对齐的三类数据,训练根因分析模型时有了一致的时间轴与关联键。

具体案例:某电商公司搭建 AIOps 平台,用 OTel SDK 埋点所有微服务,Collector 同时导出到 Prometheus 与 Elasticsearch。当支付成功率指标下降 20% 时,算法自动关联同时间窗的 ERROR 日志与对应 trace 调用链,定位到“库存服务缓存雪崩”,把平均故障恢复时间从 45 分钟降到 8 分钟。

MLOps 模型链路追踪

阶段:模型训练与推理监控

解决的问题:ML 推理管线涉及特征服务、模型服务、后处理服务,传统日志彼此割裂,难以定位“特征漂移导致精度下降”发生在哪个环节。OTel Trace 可将一次推理请求串成完整调用链,Metrics 记录推理延迟,Logs 记录输入特征摘要。

具体案例:某推荐系统团队在推理服务中接入 OTel:每个推理请求生成一个 trace,从特征拉取到模型打分到结果排序整条链路可见;同时用 Gauge 记录特征分布均值的漂移量。当线上 AUC 下跌时,运维能立刻在链路图上发现是特征服务超时,而非模型本身退化。

云原生 AI 基础设施监控

阶段:基础设施可观测性建设

解决的问题:GPU 训练任务显著依赖节点的网络、存储、调度状态,传统监控无法把“GPU 利用率下跌”与“PvP 调度重试”事件关联起来。OTel 统一采集节点指标和 Kubernetes 事件日志后,异常分析不再靠人工翻三套系统。

具体案例:一家大模型训练团队用 OTel Collector 采集所有 GPU 节点的 DCGM 指标(服务器级别的 GPU 管理指标),并通过结构化日志记录调度器事件;当出现 NCCL 超时(分布式通信超时)时,通过 traceId 把训练通信链路与节点网络指标对齐,快速判定为某交换机拥塞,替换网络路径后训练恢复稳定。

Python 演示

python144 行
# -*- coding: utf-8 -*-"""简化版 OpenTelemetry 三支柱数据统一采集演示"""import loggingimport randomimport time  class MetricsRecorder:    """模拟 OTel Metrics SDK:计数器 + 直方图"""     def __init__(self):        self.request_count = 0        self.error_count = 0        self.latency_samples = []  # 所有延迟样本     def observe_request(self, latency_ms, is_error):        self.request_count += 1        self.latency_samples.append(latency_ms)        if is_error:            self.error_count += 1     def snappy_export(self):        """导出聚合结果"""        avg = (            sum(self.latency_samples) / len(self.latency_samples)            if self.latency_samples else 0        )        return {            "http.server.request.count": self.request_count,            "http.server.request.error": self.error_count,            "http.server.request.latency.avg": round(avg, 2),        }  class LogRecord:    def __init__(self, level, message, trace_id, span_id):        self.timestamp = time.strftime("%H:%M:%S")        self.level = level        self.message = message        self.trace_id = trace_id        self.span_id = span_id     def to_dict(self):        return {            "ts": self.timestamp,            "level": self.level,            "msg": self.message,            "traceId": self.trace_id,            "spanId": self.span_id,        }  class Span:    def __init__(self, name, trace_id, span_id, parent_id):        self.name = name        self.trace_id = trace_id        self.span_id = span_id        self.parent_id = parent_id     def to_dict(self):        return {            "name": self.name,            "traceId": self.trace_id,            "spanId": self.span_id,            "parentId": self.parent_id,        }  class TraceRecorder:    """模拟 OTel Trace SDK"""     def __init__(self):        self.trace_list = {}     def start(self, trace_id):        self.trace_list[trace_id] = []     def add_span(self, trace_id, name, span_id, parent_id):        self.trace_list[trace_id].append(Span(name, trace_id, span_id, parent_id))     def export(self):        return [s.to_dict() for spans in self.trace_list.values() for s in spans]  class Collector:    """模拟 OTel Collector:收单 -> 加工 -> 导出"""     def __init__(self):        self.metrics = None        self.logs_list = []        self.traces = None     def receive(self, metrics, logs_list, traces):        self.metrics = metrics        self.logs_list = logs_list        self.traces = traces     def export_to_console(self):        print("=== Metrics 指标 ===")        for key, value in self.metrics.items():            print(f"  {key}: {value}")        print("=== Logs 日志 ===")        for log in self.logs_list:            print(f"  {log}")        print("=== Traces 链路 ===")        for span in self.traces:            print(f"  {span}")  def simulate_user_request(trace_rec, mrec, logs_list, trace_id):    """模拟一次用户请求穿过两个服务"""    span_server = "server-1"    span_db = "db-2"    trace_rec.add_span(trace_id, "GET /api/order", span_server, None)    trace_rec.add_span(trace_id, "SELECT order", span_db, span_server)    latency = random.uniform(5, 200)    is_error = latency > 150    mrec.observe_request(latency, is_error)    logs_list.append(        LogRecord("WARN" if is_error else "INFO", "query order", trace_id, span_db)    )  def main():    mrec = MetricsRecorder()    trec = TraceRecorder()    logs_list = []    logging.basicConfig(level=logging.INFO)     for i in range(5):        tid = f"trace-{i+1}"        trec.start(tid)        simulate_user_request(trec, mrec, logs_list, tid)     collector = Collector()    metrics_snappy = mrec.snappy_export()    logs_ready = [log.to_dict() for log in logs_list]    traces_ready = trec.export()    collector.receive(metrics_snappy, logs_ready, traces_ready)    collector.export_to_console()  if __name__ == "__main__":    main()
目的:模拟一个微服务请求中三个支柱(指标、日志、链路)的采集、关联与统一导出流程。
  1. class MetricsRecorder:对应 OTel Metrics SDK,维护请求计数、错误计数和延迟样本列表。
  2. def observe_request(self, latency_ms, is_error):模拟每次 HTTP 请求产生一条指标观测。
  3. def snappy_export(self):对应 Collector 内的指标聚合/处理器,将原始样本缩成平均延迟等汇总值。
  4. class Span:模型化 Trace 的最小单元,持有 traceId、spanId、parentId 三个关键字段。
  5. def add_span(...)模拟一次内部调用如何往链路树上追加一个节点。
  6. class Collector:对应 OTel Collector,先接收来自三个 SDK 的数据,再统一导出。
  7. def export_to_console(self):模拟“后端对接”:把三类数据用统一格式交给下游系统。
  8. simulate_user_request(...)模拟一个请求穿过 API 网关与服务端,同时产生三类数据。
  9. TraceRecorder.start(...)用同一个 traceId 为一次用户请求建立追踪上下文。

常见误区

误区Metrics、Logs、Traces 都差不多,选一种就够了吧?反正都是监控。

正解如果把可观测性比作体检报告,Metrics 是体温单(告诉我发烧了),Logs 是护理记录(告诉我 19:02 喝了退烧药),Traces 是就诊路径(告诉我先去内科又转外科)。三者各回答不同问题:指标负责“是否异常”,日志负责“当时发生了什么”,链路负责“路径是怎样”。少了任何一根,故障定位都像医生只拿体温单不给病历——能说不舒服,但说不出原因。

为何常见:因为大多数初学者只接触过 Prometheus 或 ELK 其中一种,以为“一个工具打天下”,没有经历过真正的跨服务分布式排障。

误区Collector 就是个中转管道,数据进来什么样,出去就是什么样。

正解Collector 有真实的数据加工能力。它内部有 processors,能做批处理、过滤采样、属性增删、重试发送。比如可以把 INFO 级别日志全部丢弃,可以按百分比对链路上采样,可以把两个后端协议同时输出。它更接近医院“病历归档中心”:接收后先核对、再分拣、再转译,而非原封不动的邮筒。

为何常见:多数教程只演示了 receiver -> exporter 的最小配置,容易让读者误以为没有处理器。

误区学了 OpenTelemetry 就必须把数据存到它的官方存储、用它的官方 UI。

正解OTel 明确不提供商业化后端,它定位是“数据采集与传输标准”。数据到了 Collector 之后,可以导给 Prometheus、Jaeger、Zipkin,也可以导给云厂商托管服务。这个解耦正是它最大的价值:业务代码与后端厂商脱钩,切换后端的成本接近于改配置。

为何常见:名字带 "Telemetry",又有统一协议 OTLP,容易让人误以为它自带监控平台。

误区只要装了 OTel SDK,所有性能指标、所有慢查询、所有业务错误都会自动采集。

正解SDK 只提供自动埋点框架(如 HTTP 请求钩子、数据库驱动钩子),但业务级指标(订单转化率、队列积压深度)仍需手动 instrumentation。类比:医院能自动测体温,但“病人有没有头晕”必须由护士手动记录。完备可观测性 = 自动埋点 + 有策略的业务自定义埋点。

为何常见:官方 demo 展示开箱即用的 HTTP 指标,让新手误以为“装了就全自动”。

面试题

Q1. OpenTelemetry 统一的三支柱分别是指什么?

单选题 · easy

  • A.Metrics、Logs、Traces← 正确答案
  • B.CPU、内存、磁盘
  • C.Docker、K8s、Istio
  • D.Grafana、Prometheus、Jaeger

答案:A。OTel 的三大支柱是 Metrics(指标)、Logs(日志)、Traces(链路追踪),类比医院分别为体温仪、护理记录、就诊路径。CPU/内存/磁盘是基础设施资源,容器/调度是云原生技术栈,而 Grafana/Prometheus/Jaeger 是具体实现工具,与“三支柱”不是一个层面。

Q2. 关于 OpenTelemetry 的特点,下列说法准确的是?

单选题 · easy

  • A.它是一键安装的监控产品
  • B.它是采集数据统一标准← 正确答案
  • C.它只能搭配云厂商后端
  • D.它取代了 Kubernetes 调度

答案:B。OTel 是一套数据采集与导出标准,类比医院的“电子病历统一格式”。它不自带商业 UI,不强绑后端,也与 K8s 调度无关。初学者容易把“标准”误解为“全家桶产品”。

Q3. 若想统计某接口的请求延迟分布,最适合的 OTel 指标类型是?

单选题 · mid

  • A.Counter 计数器
  • B.Gauge 瞬时值
  • C.Histogram 直方图← 正确答案
  • D.Sum 求总值

答案:C。延迟分布需要分位数(P50/P99),Histogram 将样本划分到多个桶中记录分布,类似医院体检记录“身高区间人数”。Counter 适合只增不减的总量,Gauge 只拍当前快照,无法还原分布。

Q4. OTel 中日志与链路进行关联,通常依赖什么字段?

单选题 · mid

  • A.log message 内容关键字
  • B.traceId 和 spanId 属性← 正确答案
  • C.服务名和 IP 地址
  • D.时间戳和日志级别

答案:B。日志记录里带上 traceId/spanId,就能在故障分析时把日志挂到链路树上,实现“日志与调用链联动”。按内容关键字或服务名只能做到粗粒度过滤,时间戳与级别也无法唯一定位到某次请求。

Q5. 下面符合 OTel Collector 管道处理顺序的是?

单选题 · mid

  • A.Exporter 到 Processor 再到 Receiver
  • B.Receiver 到 Exporter 再到 Processor
  • C.Receiver 到 Processor 再到 Exporter← 正确答案
  • D.Processor 到 Receiver 再到 Exporter

答案:C。Collector 管道是“接单→加工→寄出”,对应 receiver 解码、processor 过滤与批处理、exporter 编码导出。顺序若反,数据还没解析就加工,显然不成立。

Q6. Collector 的 Agent 模式与 Gateway 模式的区别是?

单选题 · mid

  • A.Agent 贵,Gateway 便宜
  • B.Agent 随应用节点部署,Gateway 独立部署← 正确答案
  • C.Agent 只处理日志,Gateway 只处理指标
  • D.Agent 不支持 OTLP,Gateway 才支持

答案:B。Agent 模式以 sidecar 方式跑在业务节点上,采集距离近、延迟低;Gateway 模式独立部署为集群级服务,适合统一出口策略。两者承载的数据类型完全相同。

Q7. 关于 OTel 与后端采集平台的关系,理解正确的是?

单选题 · hard

  • A.OTel 必须搭配 Prometheus 才能用
  • B.OTel 无法导出到商业 SaaS 平台
  • C.OTel 是标准,后端可自由替换← 正确答案
  • D.OTel 已内置 Grafana 可视化界面

答案:C。OTel 定位是标准而非产品,Collector 的 exporter 可将数据转给开源或商业后端,服务端只是换配置。选项 C 准确,其他描述与 OTel 的“采集与后端解耦”设计相悖。

Q8. 以下哪些属于 OpenTelemetry 三支柱的数据?(多选)

多选题 · easy

  • A.磁盘每秒读写字节数← 正确答案
  • B.某次请求的 ERROR 日志← 正确答案
  • C.跨服务调用的调用链信息← 正确答案
  • D.请求接口的延迟分布直方图← 正确答案

答案:ABCD。四类都落在三支柱内:磁盘字节数是 Metrics,ERROR 日志是 Logs,跨服务调用链是 Traces,延迟分布直方图是 Metrics 的 Histogram。可观测性追求多维度统一,四选项分别覆盖不同支柱及组合。

Q9. 关于 OTel 语义约定(Semantic Conventions),下面做法恰当的是?

多选题 · mid

  • A.指标名使用点分命名空间← 正确答案
  • B.标准属性字段内填自定义值
  • C.自定义动作使用自定义命名空间← 正确答案
  • D.资源标签直接写入业务自定义键

答案:AC。OTel 约定通用指标名使用点分命名空间(如 http.server.request.duration),自定义业务指标应进入自定义命名空间以保证不冲突。标准属性必须严格按语义约定取值,资源标签也不能被业务自定义键污染。

Q10. 公司想让 Java 与 Python 微服务共用一套采集方案,最合适的设计是哪些?(多选)

多选题 · hard

  • A.两边各接一套独立监控栈
  • B.两边统一接入 OTel SDK← 正确答案
  • C.中间加一层 OTel Collector← 正确答案
  • D.Java 用 Jaeger,Python 用 Zipkin

答案:BC。统一 SDK 产生同一数据格式,Collector 承担汇聚转发的角色,两者搭配才能实现“一处埋点、处处可用”。A 与 D 会重新制造数据孤岛,与统一标准的目标背道而驰。