Skip to content

观测性三件套:Metrics / Logging / Tracing ​

看在前面 ​

观测性(Observability)题在面试里的标准形态:"线上一个接口变慢,你怎么定位?"——答案的第一句永远是"三件套各回答一个问题,先问哪个"。这篇按五块写。包括:三支柱的分工边界、Prometheus 拉模型与 PromQL 要点、结构化日志与 trace_id 贯穿、OpenTelemetry 统一采集架构、四大黄金信号。

与同目录方法论互链:拿到信号之后的排查动作见 问题定位步骤。按 trace 定位到瓶颈后的优化见 性能优化方法论。JVM 层指标暴露的 Jolokia 实践见 JMX在Springboot的使用。

一、三支柱:各回答一个问题,别混用 ​

text
              ┌──────────────────────────────────────────────┐
   用户请求 ──►│  Metrics   "有没有问题?在哪?"(聚合、低成本) │
              │           _rate(errors[5m]) > 0.1 ? 报警 : -  │
              ├──────────────────────────────────────────────┤
              │  Tracing   "问题在哪一段?"(跨服务、因果链)   │
              │            gateway 230ms ─ serviceA 210ms     │
              │                        └─ db query 180ms ◄── 真凶在这
              ├──────────────────────────────────────────────┤
              │  Logging   "当时到底发生了什么?"(个体、全量) │
              │            trace_id=abc ORDER_123 超时 timeout │
              └──────────────────────────────────────────────┘
  发现问题(Metrics)→ 定位链路(Tracing)→ 还原现场(Logging)
支柱回答数据形态成本模型反模式
Metrics系统现在健康吗时序数值(预聚合)极低(每指标一串数)拿指标做个体排查(没有个体)
Logging具体这次发生了什么离散事件文本高(全量存储)什么级别都 INFO、无结构化
Tracing慢/错在哪一段span 树(有因果关系)中(采样)采样率一刀切 100% 或 0.1%

边界感是第一考点:指标是"面",链路是"线",日志是"点"。面用于报警与容量,线用于跨服务归因,点用于最终现场还原。用日志做监控(肉眼 grep)和用指标做排查(看不出单次请求),都是支柱错用。

二、Metrics:Prometheus 拉模型与 PromQL ​

text
拉模型(Pull):Prometheus 周期性抓取目标的 /actuator/prometheus(metrics 端点)

  Prometheus ──scrape(15s)──► 应用A :8080/actuator/prometheus
             ──scrape(15s)──► 应用B │  # HELP http_server_requests_seconds ...
             ──scrape(15s)──► 中间件 exporter(mysqld_exporter/node_exporter...)
  为什么拉不推:
  ✔ 抓取间隔/目标清单集中在服务端可控(扩缩容改配置即生效)
  ✔ 目标挂了 Prometheus 直接知道(推模型是"没消息=不知道挂没挂")
   ✔ 应用侧零依赖(只暴露文本端点),天然配合服务发现
   短生命周期任务(批处理)推不了 → Push Gateway 中转,是拉模型的补丁不是主流

  存储与查询:时序 = {__name__="http_requests_total", method="POST", uri="/api/order",
                       status="500"} @ timestamp → VTSDB(本地)/ 远端存储

PromQL 四个必会要点(面试写得出一条才算会):

promql
# 1. Counter 必须求率:QPS 与错误率
rate(http_server_requests_seconds_count[5m])                  # 每秒速率
sum(rate(errors_total[5m])) / sum(rate(requests_total[5m]))   # 错误率(除法防各实例分母不一)

# 2. P95/P99 延迟:histogram_quantile + 聚合
histogram_quantile(0.95, sum(rate(http_server_requests_seconds_bucket[5m])) by (le, uri))

# 3. by 聚合维度决定报警的"分辨率":by (service) 看整体,by (instance) 找单机
# 4. 阈值要有窗口语义:x > 0.9 for 2m(持续 2 分钟才报,防毛刺)

四种指标类型一句话。Counter 只增(求 rate)、Gauge 可增减(内存/连接数直读)、Histogram 分桶(可算分位数)、Summary 服务端预算分位数(不可跨实例聚合)。聚合分位数必须用 Histogram,这是选型题的标准答案。

三、Logging:结构化 + trace_id 贯穿 ​

text
非结构化:2026-10-17 14:23:01 INFO  OrderService 下单成功 12345
  → grep 12345 能找到这一条,找不到"这次请求的其他日志"

结构化(JSON):
{"ts":"...","level":"INFO","service":"order","trace_id":"abc123","span_id":"d4",
 "user_id":42,"order_id":12345,"msg":"order created","cost_ms":18}
  → ELK/Loki 里按 trace_id 一键拉出跨服务全部日志,按字段过滤聚合

工程要点四条:MDC(Mapped Diagnostic Context)注入 trace_id——入口 filter 生成(或从 W3C traceparent 头提取)放进 MDC。日志框架自动带出,跨服务由 trace context 透传续上。级别纪律——ERROR 要"有人被叫起来",WARN 是"可自动恢复但要留痕",INFO 是业务主线事件,DEBUG 生产默认关。日志要能在三件套之间导航——trace_id 就是日志与链路的连接键,缺了它日志是孤岛。采集——Filebeat/Vector 采集 → Kafka 缓冲 → Loki/ES 存储,应用只管写本地文件/标准输出。

四、Tracing 与 OpenTelemetry:一套 SDK 采三样 ​

text
                         ┌─────────────────────────────┐
 应用(OTel SDK)        │      OTel Collector          │
 ┌───────────────┐      │  ┌─────────┐  ┌───────────┐  │   ┌────────────┐
 │ OTel API      │ batch │  │ receive │─►│ process   │──┼──►│ Prometheus  │
 │ (Trace/Metric │────►│  │ (OTLP)  │  │ 过滤/改写/ │  │   ├────────────┤
 │  /Logs 统一)  │      │  └─────────┘  │ 采样/脱敏  │  │   │ Jaeger/     │
 └───────────────┘      │               └─────┬─────┘  │   │ Tempo       │
   埋点写 API,           │                     │批处理重试 │   ├────────────┤
   实现可换 (noop/        │                     ▼        │   │ Loki/ES     │
   OTLP/厂商)             │              export(多后端)  │   └────────────┘
                         └─────────────────────────────┘
  span 传播:traceparent: 00-{trace_id}-{parent_span_id}-{flags}  ← W3C 标准头
  serviceA ──HTTP──► serviceB ──MQ──► serviceC
     span1(200ms)  span2(150ms)   span3(40ms)   同一 trace_id 串成树

OTel 的价值主张一句话:API 与 SDK 分离 + Collector 中间层,后端(Prometheus/Jaeger/Loki)可替换。埋点代码只写一次,换监控栈不动业务代码。三个实践要点:采样策略分层(头部采样按比例 1%~10% 打底,尾部采样保错误与慢请求 100% 落盘)。错误请求必须留样本。跨异步传递(线程池要包装 TaskDecorator、MQ 要把 context 塞进消息头,否则链路在异步边界断掉)。span 命名要稳定(ORDER_CREATE 而非带 id 的动态串,否则聚合无意义)。

五、四大黄金信号:SRE 的报警四问 ​

Google SRE 四信号,任何服务的健康盘面就这四个:

信号问的问题典型来源常见病根
延迟 Latency请求多快(要区分成功/失败的延迟)Histogram P95/P99下游慢、锁竞争、GC
流量 Traffic扛了多少请求QPS / 并发 / 消息速率突发、攻击、重试风暴
错误 Errors多少请求失败5xx 率 / 业务错误码依赖故障、脏数据、发布
饱和度 Saturation资源到顶了吗CPU / 内存 / 连接池 / 队列堆积容量不足、泄漏、慢消费

延迟信号最容易答错的细节:P99 必须与错误率一起看。故障时快失败(超时熔断)会把 P99"变好"。只看延迟会得出系统变健康的荒谬结论。所以延迟要按成功/失败分桶统计。

追问链 ​

面试官:为什么 Prometheus 选拉模型,推模型(如 StatsD)差在哪?

拉模型把控制权收敛在服务端:目标清单、抓取间隔、健康状态(scrape 失败即目标异常信号)一处可见。应用只暴露文本端点零依赖,配合 K8s 服务发现扩缩容免配置。推模型应用侧要配 agent、网络策略要开洞、且"没推上来"无法区分"没数据"和"挂了"。短任务推不了是拉模型的盲区,Push Gateway 补位。

面试官:trace_id 是怎么跨服务传的?异步/线程池里丢了怎么办?

同步 HTTP:入口从 W3C traceparent 头提取(没有则生成),出口把当前 context 注入请求头,下游 SDK 自动接续。异步两处断点:线程池直接执行 Runnable 会丢 ThreadLocal 上下文——要包装(Spring 的 TaskDecorator 或 OTel 的 ContextWrappingExecutorService)。MQ 消息要显式把 context 编码进消息属性,消费端提取续链。链路的完整性取决于最弱的一段边界处理。

面试官:日志采样还是全量?tracing 采样率怎么定?

日志:ERROR/WARN 全量,INFO 按服务分级(交易类全量、边缘服务可采样),DEBUG 默认关。tracing:分层数字比单一百分比科学——头部采样 5%~10% 打底保流量画像。尾部采样规则兜底(error=true 100% 留、latency > 1s 100% 留),既控成本又保证"出问题的请求一定有完整链路"。

面试官:Histogram 和 Summary 都能算分位数,用哪个?

Summary 在客户端预算分位数(不可聚合:实例 A 的 P95 和实例 B 的 P95 无法合并成全局 P95)。Histogram 存桶分布,服务端 histogram_quantile 可跨实例聚合。多实例微服务是常态,所以默认 Histogram。桶边界要按业务延迟量级设计(默认桶对毫秒级接口全挤在一桶,P95 算出来是废数)。

面试官:指标报警了但 tracing 采样没采到这条慢请求,怎么办?

两招配合:其一,尾部采样规则保证慢请求全留(阈值与报警阈值联动)。其二,指标报警信息里带上维度(哪个 service/uri/instance)。即使无 span,也能按时间窗 + 维度从日志(trace_id 在结构化日志里)反查个体。所以三件套互为索引的设计(指标维度 ↔ span 标签 ↔ 日志字段)不是优雅问题,是可运维性问题。

面试官视角小结 ​

  1. 三支柱分工:Metrics 面(健康/报警)、Tracing 线(跨服务归因)、Logging 点(现场还原)。"有没有问题→在哪一段→当时发生了什么"是标准动作顺序。支柱错用(grep 做监控、指标做排查)是主要扣分点。
  2. Prometheus:拉模型三理由(控制权收敛/目标健康可见/应用零依赖)。Push Gateway 是短任务补丁。PromQL 四要点(rate 求率、histogram_quantile 分位数、by 定分辨率、for 防毛刺)。聚合分位数必须 Histogram。
  3. 日志:结构化 JSON + MDC 注入 trace_id 贯穿——trace_id 是三件套的连接键。级别纪律与采集链路(file → agent → 缓冲 → 存储)。
  4. OTel:API/SDK 分离 + Collector 中间层换后端不动代码。头部+尾部分层采样(错误与慢请求必留)。异步边界(线程池/MQ)显式续 context,span 命名要稳定。
  5. 黄金信号:延迟(成功失败分桶)、流量、错误、饱和度。P99 与错误率必须联看,快失败会"美化"延迟。
  6. 联动:拿到信号后的完整排查见问题定位步骤。定位到瓶颈后的优化菜单见性能优化方法论。

2026-10 新增(Round1 补缺)。