凌晨三点,急诊室大门被推开,一位病人被推进抢救室,主诉是“系统崩溃”。作为值班医生,你面前摆着三样东西:监护仪上跳动的生命体征曲线、半尺厚的病历本、流调员递来的行动轨迹图。只看监护仪,你知道病人心率140很危险,却不知道为什么;翻完病历本,你了解了病人有高血压史,但不清楚今天发生了什么;端详轨迹图,你发现他刚从健身房出来,却无法判断身体内部出了什么状况。只有把三份材料拼在一起,你才能快速整合出结论:剧烈运动诱发心梗,马上送导管室。 这就是云原生系统的日常。微服务架构让一个用户请求要穿过十几个服务,就像病人要在医院里经历挂号、抽血、CT、专家门诊。系统的“生命体征”就是指标(Metrics)——周期性采样的数值,回答“系统现在健康吗”;“病历记录”就是日志(Logs)——每行带时间戳的事件描述,回答“刚才发生了什么”;“行动轨迹”就是链路追踪(Traces)——一个请求经过每个服务的完整路径,回答“为什么变成这样”。三者合在一起,构成可观测性的三支柱。本篇我们将延续这个“急诊科诊断”的比喻,把三支柱的架构、协同方式和工程落地讲清楚。

岗位关联

可观测性三支柱是 AI 工程团队的“急诊诊断能力”——掌握它,你就能在系统故障时用最短时间从海量数据中定位根因,把平均故障恢复时间(MTTR)缩短一个量级。

目录
  1. 可视化演示
  2. 三支柱概述
  3. 指标监控:生命体征仪表盘
  4. 日志管理:病历记录本
  5. 链路追踪:病毒传播链流调
  6. 关联分析:三种报告合体诊断
  7. OpenTelemetry:统一体检标准
  8. 落地实践:体检套餐的性价比设计
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

演示操作系统进程的五种基本状态及转换条件:New→Ready→Running→Waiting/Terminated,通过步进控制逐个展示状态转换过程。

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

进程状态转换 五种基本状态:New/Ready/Run/Wait/Exit 调度器控制Ready与Running之间的转换 I/O等待导致Running转入Waiting状态

三支柱概述

想象你是一家三甲医院的急诊科主任。凌晨三点,一个病人被推进抢救室,主诉是“系统崩溃”。你面前摆着三样东西:生命体征仪上跳动的心率曲线、桌上半尺厚的病历本、流调员递来的行动轨迹图。单独看任何一样,你都只能得到片面结论——心率快可能是发烧也可能是恐慌;病历厚说明病史长但不说明当下危机;轨迹图能看出去了哪里却看不出身体内部出了什么问题。只有三样齐备,你才能判断:这是急性心梗,马上送导管室。

云原生系统的可观测性,就是为每套系统配备这“急诊三件套”。指标(Metrics)是系统的生命体征——每秒请求数、CPU 使用率、错误率,以数值形式周期性采集,回答“系统现在健康吗”。日志(Logs)是系统的病历本——每一行带时间戳的文本记录,回答“系统刚才发生了什么”。链路追踪(Traces)是系统的行动轨迹——一个请求从入口到每个微服务的完整路径,回答“一次请求具体经过了哪里、耗时多久”。

这三者不是三个独立工具,而是一套协同诊断体系。监控告诉你“出问题了”,可观测性告诉你“为什么出问题”。三支柱的价值在于:指标报警时,你能顺着日志找到具体错误,再沿链路追踪定位到是哪个服务的哪个环节拖慢了整个请求。

指标监控:生命体征仪表盘

急诊室里的心电监护仪从不关心病人昨晚吃了什么,它只以固定频率捕捉心率、血压、血氧饱和度这些关键数值。一旦数值越界,立刻响起刺耳警报。指标监控就是系统的监护仪:用数值描述系统状态,以固定时间间隔采样,用最少的存储换来最实时的健康感知。

一个指标由三要素构成:指标名称(如 http_requests_total)、标签(如 method="GET"、status="500")以及采样值。这就是指标的数据模型——把标签想象成监护仪上的不同导联:心电图导联贴胸口,血氧夹夹手指,每个标签都从不同维度切分同一个指标。计数器只增不减(总请求数),仪表盘可升可降(当前连接数),直方图记录分布(请求耗时分布)——这对应监护仪上不同的波形模式。

采集方式上,系统通过 Prometheus 这类工具定期“拉取”(Pull)各服务的指标端点,或由服务主动“推送”(Push)到网关。配上告警规则,当指标超过阈值(如 P99 延迟超过 500ms),就触发告警通知值班工程师。

指标监控的局限也很明显:它只告诉你“心率 140”,但不告诉你“为什么 140”。这时就需要翻开病历本——日志。

Error Rate=错误请求数总请求数×100%\text{Error Rate} = \frac{\text{错误请求数}}{\text{总请求数}} \times 100\%
错误率是系统健康度最直观的“体温计”——它衡量所有请求中失败请求占的比例。
  1. 错误请求数(分子)统计周期内返回状态码为 5xx 或业务定义失败的请求总数,代表“病人出现不良反应”的次数。
    错误请求数=∑status∈{5xx,业务异常}1\text{错误请求数} = \sum_{\text{status} \in \{5xx, \text{业务异常}\}} 1
  2. 总请求数(分母)同一周期内所有进入系统的请求数量,构成判断比例的基数,对应“所有就诊人次”。
    总请求数=成功请求数+错误请求数\text{总请求数} = \text{成功请求数} + \text{错误请求数}
  3. 乘以 100%将比值转换为百分比形式,方便与告警阈值(如 1%、5%)直接比较。
    Error Rate×100%≥阈值⇒触发告警\text{Error Rate} \times 100\% \ge \text{阈值} \Rightarrow \text{触发告警}

日志管理:病历记录本

每个病人都有一本厚厚的病历,医生每次查房都会在上面写一行:“体温 38.5℃,主诉头痛,开药布洛芬”。日志就是系统的病历本——每一行日志记录一个离散事件,包含时间戳、级别、来源和详细描述。它最贴近“发生了什么”的原始事实,因此排障时工程师的第一反应永远是“去看看日志”。

但病历本有个麻烦:它太厚了。一个中等规模的系统每天产生几十 GB 甚至 TB 级日志,像病历堆满整间档案室。如果每行日志都是非结构化自由文本(“user 123 visit failed“),事后检索就像在病历堆里找某一句话——大海捞针。结构化日志为此而生:用 JSON 等格式固定字段,让每行日志成为可被机器解析的”结构化病历”。

日志的采集通常通过 Filebeat/Fluentd 等代理读取日志文件或监听标准输出,解析后送入 Elasticsearch、Loki 等存储。为提高检索效率,日志系统通常建立倒排索引,支持按关键字、时间范围、字段值组合查询。

但病历本只记录“出现过什么”,记录不了“一件事如何在科室之间流转”。要追踪流程路径,需要第三份材料——行动轨迹图。

结构化日志行: {timestamp,level,service,trace_id,message}\text{结构化日志行: } \{\text{timestamp}, \text{level}, \text{service}, \text{trace\_id}, \text{message}\}
想象病历本每行都按统一格式填写:日期、医生、诊断、处方。检索“昨天所有开过阿司匹林的病人”才能一条 SQL 查出来。
  1. timestamp(时间戳)记录事件发生的精确时间,精确到毫秒甚至纳秒,是日志关联分析的核心排序键。
    timestamp=2024-01-15 14:30:02.123\text{timestamp} = 2024\text{-}01\text{-}15\ 14:30:02.123
  2. level(级别)标识日志的严重程度,常见 DEBUG/INFO/WARN/ERROR 四级,类似病历上的“注意”“紧急”标签。
    level∈{DEBUG,INFO,WARN,ERROR}\text{level} \in \{\text{DEBUG}, \text{INFO}, \text{WARN}, \text{ERROR}\}
  3. trace\_id(追踪号)把同一请求的所有日志串联起来的身份证号,这是三支柱关联的“病例编号”。
    trace_id=全局唯一请求编号,贯穿所有服务日志\text{trace\_id} = \text{全局唯一请求编号,贯穿所有服务日志}

链路追踪:病毒传播链流调

疫情期间,流调员的职责是画出一条传播链:0 号病人去了超市,接触了收银员,收银员又接触了保安……每一个接触点、每一次停留时长都记录在案。链路追踪干的就是这件事:针对一个用户请求,记录它穿过的每一个服务、每个服务内的操作及耗时,形成一棵“调用树”。

链路追踪的核心概念是 Span(追踪片段)。一个 Span 代表一次带时间范围的操作,比如“订单服务调用支付服务”这一过程。多个 Span 通过父子关系组成一棵树,树的根就是用户请求的入口,整棵树称为一个 Trace。每个 Span 都携带 trace\_id(整棵树的唯一编号)、span\_id(自己的编号)、parent\_span\_id(父节点编号)和时间戳。

由树形结构,我们可以快速计算每个环节的耗时占比——比如发现整个请求 2 秒里 1.5 秒耗在数据库查询上。但要让 Span 跨服务传递,请求每次调用下游服务时,都必须把当前的 trace\_id、span\_id 打包进 HTTP 头(如 traceparent 头),下游服务收到后继续沿这条链记录。这个过程叫上下文传播。

有了三份报告,下一步的关键是:如何把它们拼成一份完整的诊断书?

Trace={Span0(root),Span1,Span2,… },Spani.parent=Spanp(i)\text{Trace} = \{ \text{Span}_0(\text{root}), \text{Span}_1, \text{Span}_2, \dots \}, \quad \text{Span}_i.\text{parent} = \text{Span}_{p(i)}
一次请求就像一场小型“流调”:每个服务是接触者,每个 Span 是一条“某时在某地停留了多久”的记录,父指针拼出完整传播链。
  1. Trace(整条链)一次外部请求引发的所有 Span 的集合,对应完整传播链。
    Trace=⋃i=0nSpani\text{Trace} = \bigcup_{i=0}^{n} \text{Span}_i
  2. Span(单个接触记录)一次具体的操作片段,包含开始时间、结束时间和操作名,对应流调表上的一行。
    Span={name,start_time,end_time,attributes}\text{Span} = \{\text{name}, \text{start\_time}, \text{end\_time}, \text{attributes}\}
  3. parent 关系(接触关系)每个 Span 指向其父 Span,形成树形结构,对应流调中“谁接触了谁”。
    Spani.parent_span_id=Spanp(i).span_id\text{Span}_i.\text{parent\_span\_id} = \text{Span}_{p(i)}.\text{span\_id}

关联分析:三种报告合体诊断

急诊医生从不只看一张单子:心电图的异常 ST 段(指标)提示心梗,化验单的肌钙蛋白升高(日志)印证诊断,家属描述的“从健身房出来就倒地”(轨迹)则解释了诱因。在可观测性里,三种数据的关联分析,才是从“发现异常”走向“定位根因”的桥梁。

关联的枢纽是一个公共字段——trace\_id。当请求穿过订单服务时,订单服务的日志会带上 trace\_id;支付服务被调用时,也会生成带同一 trace\_id 的日志和 Span;整个过程中产生的指标同样可以用标签标记路由维度。这样,当某条链路追踪变慢时,我们可以带着 trace\_id 去日志系统搜出这条链路上的所有日志,再结合指标的全局画像,快速锁定根因。

Log 行→trace_id 关联Span 瀑布图→服务名+时间窗关联Metric 曲线\text{Log 行} \xrightarrow{\text{trace\_id 关联}} \text{Span 瀑布图} \xrightarrow{\text{服务名+时间窗关联}} \text{Metric 曲线}
trace\_id 就像病人手腕上的住院号,病历本上的每行记录、监护仪上的每段波形、流调表上的每个足迹,都写着同一个号码。
  1. trace\_id 关联日志与追踪日志行中记录 trace\_id,查询时以该 id 作为过滤条件,得到同一请求的全部日志。
    filter(Logs,trace_id=t)→{该请求的全部日志}\text{filter(Logs}, \text{trace\_id}=t) \rightarrow \{\text{该请求的全部日志}\}
  2. 服务名+时间窗关联指标链路中指出性能瓶颈的服务名和时段,再用该服务名和相近时间窗检索指标曲线,观察资源水位。
    filter(Metrics,service=s,time∈[t1,t2])→指标变化趋势\text{filter(Metrics}, \text{service}=s, \text{time}\in[t_1, t_2]) \rightarrow \text{指标变化趋势}
  3. 三向汇总形成结论将三类证据汇总,得到“哪个服务、什么时间段、因什么资源问题导致请求失败”的完整结论。
    结论=Traces∩Logs∩Metrics\text{结论} = \text{Traces} \cap \text{Logs} \cap \text{Metrics}

OpenTelemetry:统一体检标准

过去的问题是:医院里每台设备都用自己的数据格式——心电图仪输出的纸带、化验室的表格、流调系统的 GIS 图,想合并分析必须先手工誊抄。微服务架构早期也一样:Prometheus、ELK、Jaeger 各说各话,工程师被迫在三个工具间反复切换对齐。

OpenTelemetry(简称 OTel)是 CNCF 推出的统一可观测性标准,相当于给整个行业规定了“电子病历”的格式。它提供一套 API 和 SDK,让应用只需接一次 SDK,就能同时生成指标、日志、追踪三种数据,并通过统一的导出协议(OTLP)发送到任何后端——无论是开源的 Prometheus/Jaeger,还是商业的 Datadog/阿里云 ARMS。

OTel 的目标不是取代 Prometheus 或 ELK,而是做“设备与系统之间的翻译官”——让各种后端消费统一格式的数据。这大大降低了团队切换工具栈的门槛,也使得“一次埋点,多处消费”成为可能。

标准统一之后,现实中的落地还面临一个残酷问题:数据采集是要花钱的。

App→OTel SDK→OTLPBackend(Prometheus/Jaeger/Loki/… )\text{App} \rightarrow \text{OTel SDK} \xrightarrow{\text{OTLP}} \text{Backend}(\text{Prometheus}/\text{Jaeger}/\text{Loki}/\dots)
OTel 像医疗行业的 HL7 标准:设备厂商按标准输出数据,医院信息平台统一接入,医生不用关心每台设备各自怎么操作。
  1. App 接入 OTel SDK应用代码中引入 SDK,创建 TracerProvider、MeterProvider 和 LoggerProvider 三个入口。
    TracerProvider→Tracer→Span;MeterProvider→Meter→Instrument\text{TracerProvider} \rightarrow \text{Tracer} \rightarrow \text{Span}; \quad \text{MeterProvider} \rightarrow \text{Meter} \rightarrow \text{Instrument}
  2. SDK 统一处理三种信号SDK 内部负责采样、上下文注入、批量导出,应用代码与具体后端解耦。
    SDK=Sampling+Propagation+Batching+Exporting\text{SDK} = \text{Sampling} + \text{Propagation} + \text{Batching} + \text{Exporting}
  3. OTLP 导出到后端通过 gRPC/HTTP 将打包好的数据传给任意接收 OTLP 的后端,更换后端无需改业务代码。
    OTLP→gRPC/HTTP任意 OTLP Receiver\text{OTLP} \xrightarrow{gRPC/HTTP} \text{任意 OTLP Receiver}

落地实践:体检套餐的性价比设计

体检中心不会建议客户做“全身体检每天一次”——太贵且没必要。可观测性落地最大的坑,恰恰是“全量采集、全量存储、全量保留”。高基数的指标(每个用户一个标签)、全量日志、100% 采样率的链路追踪,会让存储和计算成本随业务流量线性爆炸。因此,落地三支柱的第一课是学会做减法。

成本优化有三板斧。第一板斧:指标降基数——把 user\_id 这类高基数标签从指标中剥离,只保留 service、api 等低基数标签;第二板斧:日志分层——热日志存高性能存储 7 天,冷日志压缩转储到对象存储 90 天,甚至只保留 ERROR 级别以上的完整上下文;第三板斧:采样策略——这是链路追踪成本控制的核心手段。

除了采样,更聪明的做法是场景驱动的采集策略:核心交易链路全量采样(如支付、下单),非核心链路(如商品浏览)10% 采样;错误请求全量保留,因为排障价值最高。最后,把三支柱的接入纳入 CI/CD 流水线,让每个新服务在发布前必须通过“可观测性体检”(埋点覆盖率检查),才算真正把三支柱落地成工程体系。

采样率=min⁡(1.0,N目标每秒Trace数N实际每秒Trace数)\text{采样率} = \min\left(1.0, \frac{N_{\text{目标每秒Trace数}}}{N_{\text{实际每秒Trace数}}}\right)
采样像流调只选取代表性病例做深度调查,而不是对每个接触者都做完整的 14 天轨迹回溯。
  1. 目标每秒 Trace 数(分子)根据可接受的存储成本设定每秒最多入库的 Trace 数,如 100 个/秒。
    N目标=QPS×采样率≤预算约束N_{\text{目标}} = \text{QPS} \times \text{采样率} \le \text{预算约束}
  2. 实际每秒 Trace 数(分母)系统当前真实的请求量,即流量峰值。当流量超过目标,采样率自动调低。
    N实际=当前 QPS,采样率=N目标N实际N_{\text{实际}} = \text{当前 QPS}, \quad \text{采样率} = \frac{N_{\text{目标}}}{N_{\text{实际}}}
  3. min 限幅流量低时采样率也不会超过 100%,即不会放大数据量。
    r=min⁡(1.0,N目标N实际)⇒r≤1.0r = \min(1.0, \frac{N_{\text{目标}}}{N_{\text{实际}}}) \Rightarrow r \le 1.0

AI 应用场景

场景一:AI 大模型服务的故障根因定位

阶段:模型服务上线后的运维监控

解决的问题:大模型推理服务链路长(网关→鉴权→推理→向量库→缓存),一次超时可能由任意一环引起。传统方式下工程师逐台查日志、对时间线,效率极低。借助三支柱,指标告警(首 token 延迟升高)触发后,通过 trace\_id 在日志中拉取该请求完整链路,直接定位是向量库查询超时还是 GPU 排队。

具体案例:某 AI 应用在晚高峰出现“回答变慢”投诉。指标面板显示 P99 延迟从 1.2s 飙到 4s;链路追踪发现 80% 耗时集中在 embedding 服务调用向量数据库;日志显示该时段向量库连接池被慢查询占满。三份证据合体后,运维将连接池扩容并加了限流,故障 15 分钟内恢复。

场景二:AI 训练任务的资源性能优化

阶段:分布式训练的资源分析与调优

解决的问题:大模型分布式训练中,GPU 利用率低往往源于数据加载瓶颈或通信阻塞。通过指标监控(GPU 利用率、网络吞吐)发现异常,通过链路追踪分析数据加载流水线中每个阶段的耗时占比,结合结构化日志(如数据迭代器报错)定位根因。

具体案例:训练团队发现 GPU 利用率只有 40%。链路追踪显示 DataLoader 的 prefetch 阶段耗时占 55%,指标显示 host CPU 已打满。团队据此将数据预处理改为异步多进程 + 缓存,GPU 利用率提升到 85%,整体训练时间缩短 30%。

Python 演示

python103 行
"""链路追踪模拟:展示 Trace/Span 与三支柱协同使用 OpenTelemetry 风格的简化实现"""import timeimport uuidimport jsonfrom collections import defaultdict  class Span:    def __init__(self, name, trace_id, span_id, parent_id=None):        self.name = name        self.trace_id = trace_id        self.span_id = span_id        self.parent_id = parent_id        self.start_time = time.time()        self.end_time = None        self.attributes = {}        self.children = []     def finish(self):        self.end_time = time.time()        self.attributes["duration_ms"] = round(            (self.end_time - self.start_time) * 1000, 2        )        return self.attributes["duration_ms"]  def trace_exporter(root_span):    """导出 span 树为字典列表"""    lines = []     def walk(span, depth):        dur = span.attributes.get("duration_ms", 0)        prefix = "  " * depth        lines.append(            f"{prefix}span: {span.name} ms={dur} "            f"trace={span.trace_id[:8]} parent={span.parent_id}"        )        for child in span.children:            walk(child, depth + 1)     walk(root_span, 0)    return "\n".join(lines)  def simulate_request(order_id):    """模拟一次订单请求:订单->支付->库存"""    trace_id = uuid.uuid4().hex    root = Span("POST /order", trace_id, uuid.uuid4().hex)     db_span = Span("cache.order_cache", trace_id, uuid.uuid4().hex, root.span_id)    time.sleep(0.05)    cache_log = {"level": "INFO", "service": "order", "trace_id": trace_id}    db_span.finish()    root.children.append(db_span)     pay_span = Span("payment.charge", trace_id, uuid.uuid4().hex, root.span_id)    time.sleep(0.12)    if order_id % 5 == 0:        pay_span.attributes["error"] = "insufficient_balance"        err_log = {"level": "ERROR", "service": "payment", "trace_id": trace_id}    else:        err_log = None    pay_span.finish()    root.children.append(pay_span)     inv_span = Span("inventory.check", trace_id, uuid.uuid4().hex, root.span_id)    time.sleep(0.03)    inv_span.finish()    root.children.append(inv_span)     root.finish()    logs = [cache_log, err_log] if err_log else [cache_log]    return root, logs  def main():    fake_metrics = defaultdict(int)    print("=== 可观测性三支柱模拟 ===")    for order_id in range(1, 8):        root, logs = simulate_request(order_id)        fake_metrics["total_orders"] += 1        has_error = any(l["level"] == "ERROR" for l in logs)        if has_error:            fake_metrics["error_orders"] += 1         print(f"\n--- 请求 #{order_id} ---")        print("【链路追踪 Trace 树】:")        print(trace_exporter(root))        print("【结构化日志】:")        for log in logs:            print("  " + json.dumps(log, ensure_ascii=False))     error_rate = fake_metrics["error_orders"] / fake_metrics["total_orders"] * 100    print("\n【指标监控】")    print(f"  总订单数 = {fake_metrics['total_orders']}, "          f"错误率 = {error_rate:.1f}%")  if __name__ == "__main__":    main()
目的:模拟一个用户请求经过订单、支付、库存三个服务时,同时产生链路追踪(Span 树)、结构化日志(带 trace\_id)和指标(错误率)三类数据,直观展示三支柱如何协同工作。
  1. class Span每个 Span 记录操作名、trace_id、span_id、父 id 和起止时间,对应追踪概念的单条“流调记录”。
  2. simulate_request(order_id)模拟一次完整请求,依次调用三个服务并生成子 Span,相当于让请求穿过三个“科室”。
  3. trace_exporter(root_span)递归遍历 Span 树并格式化输出,展示请求的完整调用链。
  4. cache_log` / `err_log每个服务产生结构化日志并携带 trace\_id,这是后续关联分析的“住院号”。
  5. fake_metrics["total_orders"]记录总请求数和错误数,最后计算错误率,对应指标监控的“生命体征”。

常见误区

误区我只要把日志打印得足够详细,出问题翻日志就能解决,指标和链路追踪没必要搞那么复杂。

正解日志是病历本,但病历本回答不了“病人现在心率多少”和“病毒是怎么传播的”。一个请求跨 10 个服务,每处都打印日志,想串起来得手动搜索 trace\_id,而链路追踪直接给出调用树。三支柱各司其职:指标发现异常,追踪定位环节,日志补充细节,缺一不可。

为何常见:排障时第一个动作通常是看日志,且日志信息最直观、最容易理解,容易让人产生“只要话说得够多就一定能找到原因”的错觉。

误区给指标加上 user\_id、order\_id 这类高基数标签,排查时就能按用户维度精确查询了,多方便。

正解高基数标签(每个用户/订单一个值)会让指标基数爆炸——每个唯一组合都是一条时间序列,存储和查询成本飙升,Prometheus 这类 TSDB 几乎无法支撑。指标设计应遵循“维度有限、基数可控”原则,高基数信息交给日志和追踪保存。

为何常见:人脑天然希望数据维度越细越能定位问题,没意识到指标系统的高基数代价与其他系统完全不同。

误区给服务加个 SkyWalking Agent 就算做链路追踪了,三支柱也就齐了。

正解追踪不只是“接入一个工具”,而是要求请求在跨服务时通过 HTTP 头传递 trace 上下文,否则每个服务各自生成的 Span 无法串成 Trace 树。同时要设计好 Span 的命名、属性和采样策略,否则会产生大量无意义的 Span 数据。

为何常见:工具接入是可见的动作,而上下文传播是隐性的架构设计,初学者往往只看到前者,误以为“装了工具=完成了追踪”。

误区可观测性就得指标、日志、追踪全部搭建完成才算数,缺一样就是不合格。

正解落地是渐进式的。存量系统可以先做指标+日志,后续逐步接入追踪;对单体应用,追踪的收益不如微服务大。关键是根据系统形态和排障场景决定优先级,而非机械追求三件套齐全。

为何常见:概念宣传中“三支柱”是并列关系,容易让新手误以为是必须同时满足的硬性要求,忽略了落地的成本与场景适配。

面试题

Q1. 可观测性三支柱指的是哪三样数据?

单选题 · easy

  • A.指标、日志、链路追踪← 正确答案
  • B.CPU、内存、磁盘
  • C.告警、监控、可视化
  • D.请求、响应、错误码

答案:A。三支柱是 Metrics(指标)、Logs(日志)、Traces(链路追踪),对应系统的生命体征、病历和行动轨迹。B 是典型的资源指标,C 是可观测性的应用层能力,D 是单次请求的数据维度,都不构成三支柱的概念结构。

Q2. “服务每秒请求数持续上升但错误率为 0”,用三支柱中最适合回答“系统现在健康吗”的是?

单选题 · easy

  • A.日志
  • B.指标← 正确答案
  • C.链路追踪
  • D.告警规则

答案:B。指标以周期性数值描述系统状态的健康度,相当于生命体征仪,能直接回答“现在健康吗”。日志和追踪回答“发生了什么、为什么发生”,告警是对指标阈值的主观设定,自身不产生可观测性数据。

Q3. 关于指标数据模型,以下哪个说法正确?

单选题 · mid

  • A.指标由名称、标签、采样值组成← 正确答案
  • B.指标标签基数越高越好
  • C.计数器可以随时减少
  • D.指标是唯一能定位根因的数据

答案:A。指标模型是“名称 + 标签 + 值”,标签需控制基数避免存储爆炸,计数器只增不减。定位根因需要三支柱协同,单靠指标只能发现问题而非根因。

Q4. 结构化日志相比非结构化日志的优势有哪些?

多选题 · mid

  • A.便于机器解析和检索← 正确答案
  • B.支持按字段值过滤查询← 正确答案
  • C.可以完全替代链路追踪
  • D.降低日志对时序数据库的依赖

答案:AB。结构化日志以固定字段(JSON)存储,支持字段级检索,优势是“好查”。但结构化只是格式改进,替代不了追踪的树状结构,也与时序数据库无关。

Q5. 在链路追踪中,Span 的 parent\_span\_id 字段起到什么作用?

单选题 · mid

  • A.记录服务所在主机的 IP 地址
  • B.标识该 Span 的父节点,构成调用树结构← 正确答案
  • C.记录 CPU 消耗的关联进程号
  • D.标记该 Span 所在的服务名称

答案:B。parent\_span\_id 指向父 Span,将零散的 Span 串成 Trace 树,类似流调中“谁接触了谁”的关系记录。主机 IP 和进程号是资源标签,服务名称由 Span 的 attributes 标记,都不是 parent 字段的作用。

Q6. trace\_id 在可观测性关联分析中扮演什么角色?

单选题 · mid

  • A.唯一标识一次外部请求,关联日志与追踪数据← 正确答案
  • B.表示服务启动的唯一编号
  • C.是告警规则的主要编号
  • D.用于负载均衡的会话标识

答案:A。trace\_id 是每次请求全局唯一的“住院号”,日志、指标、追踪都在记录时携带它,查询时用该字段关联三者。它不是服务编号、告警编号或会话标识,后两者与请求级关联无关。

Q7. 关于 OpenTelemetry 的描述,正确的有哪些?

多选题 · hard

  • A.它统一了指标、日志、追踪的生成 API← 正确答案
  • B.它取代了 Prometheus 和 Jaeger 成为唯一后端
  • C.应用接一次 SDK 即可导出三种信号← 正确答案
  • D.它要求后端必须是云厂商的托管服务

答案:AC。OTel 提供统一 API/SDK 产生三类信号,并用 OTLP 导出到任意后端。它是“翻译官”而非“整套医院”,Prometheus/Jaeger 依然是常见后端,也不限制云厂商;B、D 与 OTel 的定位相悖。

Q8. 为控制链路追踪存储成本,最核心的手段是?

单选题 · mid

  • A.将日志全部删除只留追踪
  • B.对 Trace 设置合理的采样策略← 正确答案
  • C.用 MySQL 代替时序数据库
  • D.关闭所有服务的告警

答案:B。采样策略(如按流量比例、按错误全采)是控制 Trace 量级的主要手段,对应“只对代表性病例做流调”。删日志会破坏完整性,换数据库不解决数据量问题,关告警则失去监控能力。

Q9. 某请求 P99 延迟从 1s 升到 5s,工程师想确认瓶颈是数据库还是下游服务,最直接的证据来自哪里?

单选题 · hard

  • A.该请求的 Trace 瀑布图中各 Span 的耗时占比← 正确答案
  • B.聚合 CPU 使用率曲线
  • C.服务进程的启动参数
  • D.代码仓库的最近提交记录

答案:A。Trace 瀑布图逐段展示请求在数据库、下游服务各环节的耗时,一眼看出哪个 Span 占比最大——这正是“行动轨迹”的用途。CPU 曲线只看整体资源,启动参数和代码提交不能定位运行时的耗时分布。

Q10. 在真实微服务系统中通过三支柱定位“订单服务频繁超时”根因时,有效的协同排查路径有哪些?

多选题 · extreme

  • A.从链路追踪确定超时集中在下游支付服务← 正确答案
  • B.用 trace\_id 检索日志确认支付服务的具体错误← 正确答案
  • C.用指标时间窗比对支付服务所在节点的 CPU 负载← 正确答案
  • D.直接删除订单服务以消除故障

答案:ABC。三支柱协同的经典路径是:追踪定位环节 → 日志查细节 → 指标看资源水位,三者通过 trace\_id 和服务+时间窗关联,逐步收窄根因。D 是极端且错误的处置,不属于可观测性流程。