可观测性三支柱
这一课你将能做到
- 用自己的话说明 Metrics / Logs / Traces 分别回答什么问题,并各举一个本站判题链路的故障场景
- 讲清「监控」与「可观测性」的区别:为什么只有大屏不等于能排障
- 在本机用 Docker Compose 跑起 Prometheus + Grafana,抓到 Spring Boot backend 的第一批指标
为什么要学
设想这个场景:晚上十点,用户在群里反馈「提交判题后一直转圈」。你登上服务器,top 看 CPU 不高,磁盘没满,然后呢?tail -f 翻几万行日志,grep 半天找不到那个用户的请求。四十分钟过去,你只知道「好像有点慢」,不知道慢在哪一跳——是 Nginx 转发、backend 本身、MySQL 慢查询,还是 Kafka 堆积、判题 worker 挂了。
这就是没有可观测性的日常。可观测性(Observability) 指系统主动暴露足够丰富的信号,让你不登机器就能回答四个问题:现在是否正常?哪里变慢?影响面多大?根因在哪一跳?
它靠三根支柱:Metrics(指标) 告诉你「有事」;Logs(日志) 告诉你「这次发生了什么」;Traces(链路追踪) 告诉你「请求走过了哪条路、每段花多久」。三者是后端面试的高频考点,也是任何团队 on-call 的基本功。本课建立心智模型,后续每课展开一根支柱。
人话总结:指标是心电图(整体趋势),日志是病历(单次细节),链路是 CT 扫描(全身路径)。医生看病三样都要,你排障也一样。
先建立图像
| 支柱 | 一句话人话 | 典型载体 | 擅长回答 |
|---|
| Metrics | 把行为压成可聚合的数字时间序列 | Counter / Gauge / Histogram | QPS 多少?错误率升了吗?P99 多高? |
| Logs | 某次事件的详细记录 | 结构化 JSON 行 | 这次请求参数是什么?异常栈在哪? |
| Traces | 一次请求穿过哪些服务、各花多久 | Trace + Span 树 | 慢在哪一跳?错误从哪个服务传播? |
本站判题链路的信号流:
用户提交代码
│
▼
浏览器 → Nginx → Spring Boot backend → Kafka → 判题 worker → MySQL / Redis
│
├─ Metrics → /actuator/prometheus → Prometheus → Grafana
├─ Logs → stdout(JSON,含 traceId)→ Loki / ELK
└─ Traces → OTel SDK → Collector → Jaeger / Tempo
排障的标准顺序(经验法则,不是铁律):
指标报警「有事」→ 链路找「哪一跳」→ 日志看「细节和堆栈」
三者互补,缺一不可:只有指标,你知道出事了但不知道原因;只有日志,你要在海量文本里大海捞针;只有链路,没被采样到的那次请求你就瞎了。
跟着做
步骤 1:给三个故障选对支柱
打开记事本,各写一行答案:
- 「过去 10 分钟判题提交接口 5xx 比例从 0.1% 升到 5%」→ 优先 Metrics(趋势与告警)
- 「某个 submissionId 返回 404,想知道当时请求参数」→ 优先 Logs
- 「提交到出结果要 30 秒,怀疑 Kafka 排队或判题 worker 慢」→ 优先 Traces
应得到:你能说清「为什么不只靠翻日志文件」——日志有细节但没有全局趋势,指标有趋势但没有单次上下文。
步骤 2:用 Docker Compose 起最小观测栈
新建目录 obs-demo,写入 docker-compose.yml:
services:
prometheus:
image: prom/prometheus:v2.53.0
ports:
- "9090:9090"
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
grafana:
image: grafana/grafana:11.1.0
ports:
- "3001:3000"
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin
再写 prometheus.yml(假设 backend 已开在宿主机 8080 并暴露 Actuator):
scrape_configs:
- job_name: coderoadmap-backend
metrics_path: /actuator/prometheus
static_configs:
- targets: ["host.docker.internal:8080"]
打开 http://localhost:9090 输入 up,应看到 up{job="coderoadmap-backend"} = 1。打开 http://localhost:3001(admin/admin)即为 Grafana。
应得到:一个真实可点的指标采集链路。backend 侧 Actuator 的暴露配置在第 8 课细讲,本课只需要知道「门在哪」。
步骤 3:在 Prometheus 里查一条真实指标
在 http://localhost:9090/graph 输入:
http_server_requests_seconds_count{application="coderoadmap-backend"}
sum(rate(http_server_requests_seconds_count[5m]))
应得到:第一行是每个 URI×状态码的请求计数(Micrometer 自动埋的),第二行是全局 QPS。你已经完成了「指标从应用到时序库」的闭环。
步骤 4:用一次假想请求串起三支柱
设想请求:POST /api/judge/submissions(提交判题)。写下你会留下什么信号:
Metrics: http_server_requests_seconds_count{uri="/api/judge/submissions", status="202"} +1
Logs: {"level":"INFO","traceId":"4bf92f...","msg":"submission accepted","submissionId":12345}
Traces: Span "HTTP POST" → child "SubmissionService.create" → child "Kafka send" → child "INSERT submissions"
应得到:同一个 traceId 能把日志和链路钉在一起;指标回答「整体是否变差」。这就是三支柱的协作方式。
步骤 5:用自己的话写「监控 vs 可观测性」
写一句话边界,再对照参考方向:监控常预设已知故障模式(CPU>90% 就告警);可观测性强调信号足够丰富、可自由关联,能对从没见过的新故障组合做推理。监控是可观测性的子集,不是对立面。
讲清楚要点
- 指标可聚合、可告警,所以「系统是否整体变差」先看 Metrics——但指标看不到某次请求的参数细节。
- 日志保留离散事件的完整上下文,所以「这一次为什么失败」靠 Logs——但没有结构化字段就无法高效检索。
- Trace 用父子 Span 还原调用树,所以「慢在哪一跳」靠 Traces——但全量追踪成本高,需要采样策略(第 4 课讲)。
- 未知故障无法事先写全告警规则,所以可观测性强调信号完备度与关联能力——它不是否定监控,而是监控的升级版心智。
- 三支柱要能互相跳转才有价值,所以生产日志必须带
traceId,指标标签要与服务名一致——关联比「多采一点」重要得多。
- 工具栈会换(Prometheus、云厂商 APM、Loki……),所以心智模型要对齐 OpenTelemetry 的三大 signal——API 可换,问题类型不变。
- 可观测性是交付标准的一部分,不是上线后再补的挂件——埋点契约应和接口设计同时评审。
你可能会懵的地方
-
有监控大屏不就等于有可观测性了?
不等于。大屏只覆盖你预先想到的故障。若没有结构化日志字段、没有链路、没有一致的服务标签,遇到新故障组合你仍是盲的。判题接口突然超时这种「Kafka 堆积 + worker 慢」的组合拳,预设大屏很难覆盖。
-
三个都要采,是不是重复浪费?
不重复。同一请求在三根支柱上的「分辨率」不同:指标回答群体趋势,日志回答单次细节,链路回答路径。省掉任一根,排障链路就会断。
-
是不是先上齐 ELK + Jaeger + Prometheus 才叫可观测?
不是。先保证三件事:HTTP/JVM 指标可看、日志 JSON 且带 traceId、关键路径有 Span。工具可渐进,字段契约要先立住。
-
初学者应该先学哪一根?
指标。因为它最先能回答「有没有事」,配置成本最低(Spring Boot 加一个依赖就有),而且是 SLO 和告警的地基。
-
可观测性和 APM(如 SkyWalking、听云)什么关系?
APM 是产品形态,可观测性是能力目标。用 OTel + 开源后端可以自建 APM 等价物;用商业 APM 也要懂三支柱,否则只是多了个看不懂的工具。
错法对照
| 错法 | 问题 | 正解 |
|---|
| 只盯机器 CPU,接口全红无人知 | 资源正常不代表用户不痛 | 先建服务级 RED 指标(第 10 课),再补资源指标 |
日志全是 println 拼接字符串 | 无法按字段检索、无法关联 | JSON 结构化 + 注入 traceId(第 3 课) |
| 有指标无链路,跨服务只能猜 | 不知道慢在 backend 还是 Kafka | 全链路传播 W3C Trace Context(第 4 课) |
| 监控系统挂了无所谓 | 故障时连「有事」都不知道 | up / scrape 失败本身要告警(第 6 课) |
| 把可观测当成运维专属 | 开发埋点乱、运维看不懂业务 | 开发定埋点契约,运维管采集管道,共同写 runbook |
卡点急救
up 查询显示 0 或无数据:先 curl http://localhost:8080/actuator/prometheus 确认 backend 端点有输出;再查 Prometheus 的 Status → Targets 页面看 scrape 报错——Docker 里访问宿主机要用 host.docker.internal,不是 localhost。
- Grafana 打开没数据:Grafana 默认不带数据源,要手动添加 Prometheus 数据源(URL 填
http://prometheus:9090,同 compose 网络内用服务名)。
- 指标名对不上:Micrometer 用点号(
http.server.requests),Prometheus 导出后变成下划线(http_server_requests_seconds_count)。查不到时先在 /actuator/prometheus 原文里搜。
面试常问
-
Metrics、Logs、Traces 的区别和适用场景?
背这个版本:Metrics 是数值时间序列,低成本可聚合,回答「趋势与比例」——QPS、错误率、P99;Logs 是离散事件记录,保留完整上下文,回答「这一次发生了什么」;Traces 用 Span 树还原一次请求的跨服务路径,回答「慢在哪、错从哪传播」。排障时三者配合:指标发现异常 → 链路定位跳点 → 日志看细节。指标适合告警,日志适合取证,链路适合定位瓶颈。
-
监控和可观测性有什么区别?
监控是预设已知故障模式:预先定义阈值和大盘,回答「已知问题发生了吗」。可观测性是系统的属性:信号足够丰富、维度可自由组合,能推理从未预见过的故障。一句话:监控告诉你「你担心的事发生了」,可观测性让你能回答「你没想到的事为什么发生了」。
-
为什么生产排障不能只看日志?
三个理由:日志无全局视图(看不出错误率是 0.1% 还是 10%);日志量大海捞针(没有 traceId 无法关联同一次请求);日志分散在多机多容器(不聚合没法看)。所以日志要配指标定范围、配链路定路径。
自测三问
- 分别用一句话说清 Metrics、Logs、Traces 各自最擅长回答什么?
- 为什么「告警响了」先看指标,而「某个用户偶发超时」时指标可能完全正常?
- 画出本站判题链路(Nginx → backend → Kafka → worker → MySQL),标出三支柱信号各从哪里流出。
小结一句话
指标看趋势、日志看细节、链路看路径——三支柱互补关联,才是「看得见、查得清」。
延伸