惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

F
Full Disclosure
Latest news
Latest news
P
Privacy International News Feed
T
Tenable Blog
Schneier on Security
Schneier on Security
O
OpenAI News
K
Kaspersky official blog
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
C
Cisco Blogs
L
LangChain Blog
H
Help Net Security
W
WeLiveSecurity
V
Vulnerabilities – Threatpost
C
Cyber Attacks, Cyber Crime and Cyber Security
AWS News Blog
AWS News Blog
博客园 - 叶小钗
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
罗磊的独立博客
C
Check Point Blog
Engineering at Meta
Engineering at Meta
J
Java Code Geeks
Stack Overflow Blog
Stack Overflow Blog
雷峰网
雷峰网
MongoDB | Blog
MongoDB | Blog
C
Cybersecurity and Infrastructure Security Agency CISA
P
Privacy & Cybersecurity Law Blog
Apple Machine Learning Research
Apple Machine Learning Research
博客园 - 【当耐特】
V2EX - 技术
V2EX - 技术
Spread Privacy
Spread Privacy
博客园 - Franky
T
Threatpost
T
Tor Project blog
P
Proofpoint News Feed
D
DataBreaches.Net
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
H
Heimdal Security Blog
NISL@THU
NISL@THU
大猫的无限游戏
大猫的无限游戏
Microsoft Security Blog
Microsoft Security Blog
Know Your Adversary
Know Your Adversary
I
Intezer
T
Tailwind CSS Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
美团技术团队
博客园 - 聂微东
T
Threat Research - Cisco Blogs
PCI Perspectives
PCI Perspectives
The Hacker News
The Hacker News
B
Blog RSS Feed

Aimee's Blog

微服务与服务拆分:何时拆、怎么拆 异步与事件驱动架构:把协作从「打电话」改成「发消息」 高可用设计:怎么让系统尽量不宕机 可扩展性设计:怎么让系统加机器就能扛更多 缓存架构:多级缓存怎么搭 高并发三板斧:限流、熔断、降级 架构设计到底在设计什么 —— 从单体到微服务的演进 服务成本账:一个服务一个月烧多少钱 API 设计:好接口长什么样 消息队列:为什么要 MQ,以及丢失、重复、顺序怎么破 查询优化:慢查询怎么治 —— 索引 + 预聚合 缓存:为什么快、三大坑、和数据库怎么保持一致 存储与基建选型 —— 传统业务 vs AI 大模型时代 权限系统怎么设计?—— 从 RBAC 到 Google Zanzibar 鉴权怎么落地?—— 常见业务场景的方案选型与避坑 JWT、OAuth、Token 刷新 —— 一个全栈工程师的鉴权入门笔记 我把跑了五年的旧博客升级到了 3.0 大模型 AIGC 满天飞 推荐一些学习和资料网站 2024 冒个泡 冒个泡 阿里云新机器安装 自定义配置 vue 的 webpack.config.js 从输入url到页面展示 React API 使用demo 欢迎加入 aimee前端技术交流群 github actions 自动化部署 很久以前的美食美荟 无穷之路纪录片还不错哦 vue 模板编译和组件化 vue2 响应式原理 vue2 虚拟DOM原理 vue2 源码解析-初始化 vue2.0 router及简易实现 防抖和节流 Proxy 和 Object.defineProperty git rebase
可观测性:线上出问题怎么查
Aimee · 2026-06-15 · via Aimee's Blog

服务半夜报警、接口忽然变慢、用户说"点了没反应"——登上机器一脸懵:日志刷得飞快却不知道看哪条,问题到底出在哪个服务、哪一段、哪一行,全靠猜。

靠手动打几行日志、登上机器一行行翻去抓瞎,和有一套**可观测性(Observability)**体系按图索骥,是两种完全不同的体感。可观测性这套我也是补服务端才真正搞懂的——以前一直把它当成"监控的高级说法"。这篇就把它最该懂的事讲清楚:三大支柱各管什么、怎么配合,以及一次"接口变慢"该怎么顺藤摸瓜。配套一个零依赖 demo,跑一下就能在终端里看到指标、日志、链路三件套,和一次完整的排查路径。


一、可观测性是什么,为什么不是"监控"

老话叫"监控(Monitoring)":提前定好要盯的指标(CPU、内存、QPS),到了阈值就报警。它能回答你预先想到的问题——"CPU 超 80% 了吗"。

但线上的事故,十有八九是你没预想到的:某个接口在特定参数下变慢、两个服务之间的调用偶尔超时、一小撮用户命中了某个边界 bug。监控的看板上一切"正常",问题却真实存在。

可观测性换了个思路:不预设你会问什么,而是让系统主动吐出足够多的信号,事后你能从外部观测到的数据里,反推内部到底发生了什么——哪怕这个问题你从没预想过。

这套信号,业界收敛成了三大支柱:Metrics(指标)、Logging(日志)、Tracing(链路)。它们不是三选一,而是各回答一类问题、互相补位:

支柱一句话回答的问题数据形态
Metrics 指标系统现在健康吗整体状况:请求量多大、错误率多高、耗时多久按时间记录的(可聚合)
Logging 日志具体发生了什么某个时刻、某个请求里的细节:报了什么错一条条离散事件
Tracing 链路这个慢请求卡在哪一环一个请求跨多个服务时,每段花了多久一条请求的调用树

有个画面挺好记:指标是仪表盘(一眼看整体健康)、日志是行车记录仪(回放某一刻的细节)、链路是 GPS 轨迹(看请求一路经过哪些服务、在哪堵住)。下面逐个拆。

二、Metrics:系统现在健康吗

指标是按时间记录的数:每秒请求数、错误率、p95 耗时、内存占用……特点是可聚合——能求和、求平均、求分位数,画成随时间起伏的曲线。

该收哪些指标?RED 与 USE

指标可以收成千上万个,但盯不过来。业界有两套现成的方法论帮你抓重点:

  • RED(面向请求/服务):Rate(请求量)、Errors(错误数/率)、Duration(耗时)。任何一个对外提供接口的服务,盯住这三个数,基本就知道它健不健康。
  • USE(面向资源):Utilization(使用率)、Saturation(饱和度/排队)、Errors(错误)。看 CPU、内存、磁盘、连接池这类资源时用它。

对大多数业务服务,先把 RED 收齐就够用了。demo 里每个接口都按 RED 统计:请求量、错误数、平均耗时和 p95(95 分位耗时——比平均值更能反映"大多数人的最差体验",因为平均值会被少数极端值和大量快请求一起拉平)。

怎么把指标收上来:Prometheus 的"拉取"模型

收指标主流用 Prometheus。它的设计有个关键选择——拉取(pull):不是你的服务把数据"推"给它,而是每个服务自己暴露一个 /metrics 端点(就是一段纯文本),Prometheus 定时来拉

http_requests_total{path="/order"} 70
http_request_errors_total{path="/order"} 11
http_request_duration_ms_p95{path="/order"} 41

每行是一个带标签(label)的样本:{path="/order"} 就是标签,让同一个指标能按接口、机器、状态码等维度切分。demo 的 Metrics.toPrometheus() 导出的就是这种文本,Prometheus 来拉的也正是这个。

为什么是拉取而不是推送?拉取模型下,Prometheus 掌握全部目标列表——拉不到就知道这个实例挂了(顺带成了一种存活检查);扩缩容时只要服务注册进来就能被发现,不用每个实例反向配置往哪推。推送模型在某些场景(短命的批处理任务)更合适,Prometheus 也留了 Pushgateway 兜底,但常驻服务默认走拉取。

这些数存哪:时序数据库

指标这种"每个时间点一个数值"的数据,有专门的存法——时序数据库(TSDB,Time Series Database),Prometheus 自带一个。它针对"按时间追加写、按时间范围+标签查"做了重度优化,和你存订单用的关系型数据库是两套东西。

这正呼应一句老话:什么数据用什么存。 指标是时序数据 → 时序库;订单是结构化关系数据 → 关系库;日志是半结构化文本 → 搜索/日志库。用错了存储,要么慢要么贵。

收上来的指标,最后用 Grafana 画成看板(dashboard):一屏排开各服务的 RED 曲线,健不健康一眼就看出来。Prometheus 负责拉取和存储,Grafana 负责展示,是最经典的开源组合。

三、Logging:具体发生了什么

指标告诉你"/order 错误率涨到 15%",但为什么错,指标答不了——它只是个数。这时候要翻日志

别再打纯文本了:结构化日志

很多人的日志长这样:

[2026-06-07 12:00:01] order failed for user 123, reason: db timeout

人能读,但机器难检索。想统计"db timeout 出现了多少次""用户 123 的所有报错",得写正则去硬抠这行文本,既慢又脆(改一下格式就全废)。

结构化日志(Structured Logging)把日志写成带字段的 JSON:

{"ts":"2026-06-07T12:00:01Z","level":"error","traceId":"trace-0005","msg":"request failed","path":"/order","code":500,"reason":"db timeout"}

好处是机器可检索:level=error AND reason="db timeout"path="/order" 这样按字段精确过滤,而不是对一行文本碰运气。demo 里的 Logger 输出的就是这种 JSON 行。

日志级别:别乱打

每条日志带一个级别(level),用来表达"这条有多重要、平时要不要看":

级别什么时候用线上默认
debug排查时才需要的细节(变量值、命中没命中缓存)(太吵)
info正常的关键节点(请求进来了、任务跑完了)
warn不正常但还能扛(重试了一次、降级了)
error出错了、需要关注(异常、失败)

线上一般设个 minLevel=info,把 debug 过滤掉——否则 debug 噪音会把真正要紧的 error 淹没。demo 里就演示了这点:cache miss 是 debug 级,默认不输出。

反过来也别走极端:把什么都打成 error,告警就成了"狼来了"(见第五节)。级别打得准,日志才有用。

集中收集

单机日志躺在各自机器的磁盘上,十几台机器出了事一台台 grep 太痛苦。所以要集中收集:每台机器的日志统一汇到一处,再统一检索。

常见两套开源方案:ELK(Elasticsearch 存+搜、Logstash/Beats 采集、Kibana 看)偏重、检索强;Loki(配 Grafana 用)更轻,思路是"日志也按标签索引、像查指标一样查日志"。选哪个看日志量和检索需求,但集中这件事本身,是规模上来后绕不开的。

日志存哪、怎么查

ELK / Loki 本质在回答两个问题:日志存哪、怎么查出来。

存哪——日志是"半结构化、量大、写多读少"的数据,存法和订单这种关系数据完全不同:

  • Elasticsearch:倒排索引,全文检索 + 按任意字段查都强,但存储和成本高(日志量大很烧钱);
  • Loki:只给日志的标签建索引、正文压缩后扔对象存储,便宜不少,适合"按标签 + 时间范围捞";
  • 云日志服务(阿里云 SLS、AWS CloudWatch Logs):托管,开箱即用;
  • 冷热分层(控成本的关键):近期热日志放 ES / Loki 可快查,老日志归档到便宜的对象存储,各设保留期(热的存 7~30 天,冷的归档更久)。

怎么查——靠结构化字段精确过滤(level=error AND path=/order),而不是对一行文本碰运气(ES 配 Kibana、Loki 配 Grafana)。最关键的一招是用 traceId 串:拿一个出问题请求的 traceId,精确捞出它经过的所有日志——这正是第六节排查实战的最后一步。"结构化 + 带 traceId"不是为了好看,是为了查得到

项目里怎么落地

把日志真正用起来,几条实践:

  • 统一规范:全服务结构化 JSON,必带字段——时间、级别、服务名、traceId、关键业务 id(订单 / 用户)、msg。绝不打敏感信息(密码、token、身份证),这是安全红线。
  • 采集与应用解耦:应用只管把日志打到 stdout / 文件,由采集 agent(Filebeat / Fluentd)收走;日志洪峰大时,中间加一层 Kafka 缓冲削峰(就是消息队列那篇的削峰),再落到存储:
应用打日志 → 采集 agent(Filebeat/Fluentd) → [Kafka 缓冲] → ES/Loki 存储 → Kibana/Grafana 查
  • 日志和监控分工:告警优先用指标(Metrics)——错误率、延迟这些;日志负责事后查细节。别用"狂打 error 日志 + 关键字告警"代替指标监控,那样又贵又吵。
  • 控量控成本:日志量爆炸是真实账单。线上关掉 debug、热点路径别狂打、必要时采样;配合上面的冷热分层和保留期。

四、Tracing:这个慢请求卡在哪一环

前面两根支柱在单个服务里就够用。但现代系统往往是一个请求跨好几个服务:网关 → 订单服务 → 库存服务 → 数据库 → 缓存。这时候问题来了——

用户说"这个请求好慢",你看指标:订单服务 p95 高。但订单服务自己也在调下游,到底是它自己慢,还是它等的某个下游慢? 单看一个服务的指标和日志,串不起整条调用链

traceId 与 span:把一次请求串起来

**链路追踪(Tracing)**解决的就是这个。核心两个概念:

  • trace:一整个请求的完整调用过程,分配一个全局唯一的 traceId
  • span:这条 trace 里的一段工作(一次函数调用、一次下游请求、一次查库)。每个 span 记自己的开始/结束时间,于是有了耗时

一个请求经过的所有服务,都带着同一个 traceId;每段操作开一个 span,span 之间用 parent 关系串成一棵。把这棵树画出来,哪一段最长一目了然。demo 里 Tracer 就是这么干的,输出一条 /order 请求的 span 树:

HTTP /order             40ms  ██████████
  └─ cache.get           5ms  █
  └─ db.query           35ms  █████████

条形最长的 db.query 就是瓶颈——慢在数据库这一环,不用再瞎猜。

OpenTelemetry:采集的统一标准

要让 traceId 自动在服务间传递、span 自动采集,过去每家工具各搞一套,换工具就得重新埋点。现在业界统一到了 OpenTelemetry(OTel):一套厂商中立的标准 + SDK,定义了 trace/metric/log 怎么采集、怎么传递。你的代码按 OTel 埋点,后端想用 Jaeger、Grafana Tempo 还是别的来存储展示,随时能换,不被绑定。

关键认知:链路追踪的价值,在跨服务才真正凸显。单服务的慢,日志+指标基本能定位;一旦请求跨了三五个服务,没有 traceId 把它们缝在一起,排查就是大海捞针。

五、告警:别让"狼来了"

收了一堆指标,总不能盯着看板过日子。**告警(Alerting)**就是给指标设规则:超过阈值就自动通知人(电话、IM、邮件)。比如"/order 错误率 > 5% 持续 5 分钟"就报警。

听起来简单,真正的难点是别让告警变成噪音。两个最常见的坑:

  • 告警风暴:数据库一抖,依赖它的几十个服务同时报警,几百条消息糊脸,真正的根因反而被淹没。
  • 狼来了:阈值设太敏感、或者对一些"抖一下自己会恢复"的情况也报警,半夜被吵醒一看是误报——几次之后,大家就开始无视告警,等真出事也没人理了。

实践里靠这几招治噪音:

  1. 分级:区分 P0(立刻起床处理)/ P1(上班时间看)/ 提示(看板上有就行),只有最高级别才电话轰炸。
  2. 基于"症状"而非"原因"告警:对用户能感知的结果告警(错误率、延迟、可用性),而不是对每个内部指标都设阈值——内部指标抖动多,用户体验才是底线。
  3. 告警要可执行:一条好告警应该告诉值班人"出了什么事、影响多大、大概去哪看",而不是甩一个干巴巴的数字。
  4. 抑制与聚合:同一个根因引发的一连串告警,合并成一条;已知在处理的,自动静默。

一句话:告警的目标不是"报得多",而是"报得准"——每一次响铃都值得人放下手里的事去看。

六、排查实战:一次"接口变慢"怎么查

把三根支柱串起来,看一次真实的排查。场景:用户反馈下单变慢了。

第一步,看指标(Metrics)——定位"哪个接口"。 打开 Grafana,扫各接口的 RED。demo 的汇总表里:

接口        Rate  Errors  ErrRate  avg   p95
/order       70     11    15.7%    40ms  41ms   ← p95 最高,还有错误率
/user        73      0     0.0%    12ms  13ms
/health      60      0     0.0%     2ms   3ms

/order 的 p95 明显高于其它接口,还伴随 15.7% 的错误率——嫌疑接口锁定 /order。但指标只告诉你"它慢",没说慢在哪。

第二步,看链路(Tracing)——定位"慢在哪一段"。 抓一条 /order 的慢请求,看它的 span 树:

HTTP /order             40ms
  └─ cache.get           5ms
  └─ db.query           35ms   ← 这一段吃掉了大头

db.query 占了绝大部分耗时——瓶颈在数据库这一环。如果这里是跨服务调用,你还能顺着 traceId 跳到下游服务,继续往下看。

第三步,看日志(Logging)——定位"那一段到底报了什么"。 拿着这条请求的 traceId,去日志系统里精确过滤,翻 db 那一环附近的日志:

{"level":"error","traceId":"trace-0005","msg":"request failed","path":"/order","code":500,"reason":"db timeout"}

真相浮出水面:数据库超时了。接下来就是看那条慢 SQL 是缺索引、还是锁等待、还是连接池被打满——但定位到这一步,问题已经从"系统某处慢"收敛成了"/order 的某条 DB 查询超时",范围小了几个数量级

这就是三根支柱的配合,也是可观测性最该带走的一条心法:

指标定位"哪个接口慢" → 链路定位"慢在哪一段" → 日志定位"那一段到底报了什么"。

traceId 是把三者缝在一起的线:指标发现异常 → 找到一条问题请求的 traceId → 用它在链路里看调用树、在日志里捞细节。三根支柱各看一面,traceId 让它们指向同一个请求

七、一张表:症状 → 先看哪根支柱 → 看什么

线上遇到不同症状,优先翻哪根支柱,一张表带走:

症状先看哪根支柱具体看什么
报警说错误率涨了MetricsRED 里的 Errors,定位是哪个接口
某个接口变慢Metrics → Tracing先看 Duration/p95 锁定接口,再看 span 树找慢的那一段
一个请求跨服务、不知卡在哪Tracing按 traceId 看调用树,哪段 span 最长
用户报某次操作失败Logging按 traceId / 用户 ID 过滤,看 error 日志的 reason
想知道整体健不健康MetricsGrafana 看板,各服务 RED 曲线
半夜被告警吵醒、疑似误报Metrics + 告警规则对照阈值是否过敏感、是不是"狼来了"
容量/资源是否到顶Metrics(USE)CPU/内存/连接池的使用率与饱和度

一句话收尾:三根支柱不是装着好看的,而是出事时让你从"一脸懵"变成"按图索骥"的地图。 平时埋好点、收好数,真出事那一刻就知道值了。


名词解释

  • 可观测性(Observability):从系统对外吐出的数据(指标/日志/链路)反推内部状态的能力,重点是能查清没预想到的问题;区别于只盯预设指标的"监控"。
  • Metrics(指标):按时间记录、可聚合的数值(请求量、错误率、耗时等)。
  • Logging(日志):一条条离散的事件记录,记录"某时刻发生了什么"。
  • Tracing(链路追踪):把一个请求跨多个服务的完整调用过程串起来,看每段耗时。
  • RED 方法:面向请求的指标方法论——Rate(请求量)、Errors(错误)、Duration(耗时)。
  • USE 方法:面向资源的指标方法论——Utilization(使用率)、Saturation(饱和度)、Errors(错误)。
  • p95 / 分位数:把耗时排序后取第 95% 位的值,反映"大多数请求的较差体验",比平均值更真实。
  • Prometheus:开源的指标采集与存储系统,采用**拉取(pull)**模型,自带时序数据库。
  • 拉取 / 推送(pull / push):Prometheus 主动来服务的 /metrics 端点拉数据(pull),而非服务推给它(push)。
  • Grafana:开源的可视化看板工具,常配 Prometheus / Loki 画指标与日志看板。
  • 时序数据库(TSDB):为"按时间追加写、按时间范围+标签查"优化的数据库,专门存指标。
  • 结构化日志(Structured Logging):写成带字段的 JSON 而非纯文本的日志,机器可按字段精确检索。
  • 日志级别(Log Level):debug / info / warn / error,表达日志的重要程度,线上常过滤掉 debug。
  • ELK / Loki:两套开源的日志集中收集与检索方案;ELK 偏重检索强,Loki 更轻、按标签索引。
  • 日志采集 agent(Filebeat / Fluentd):部署在机器上、把应用日志收集转发到中心存储的工具,让应用和日志存储解耦。
  • 冷热分层:近期常查的"热"日志放可快查的存储(ES / Loki),老的"冷"日志归档到便宜的对象存储,各设保留期,平衡查询速度与成本。
  • traceId / span:traceId 是一个请求的全局唯一标识,串起它经过的所有服务;span 是其中一段工作,记录起止时间和耗时。
  • OpenTelemetry(OTel):厂商中立的可观测数据采集标准与 SDK,埋点一次、后端可换(Jaeger、Tempo 等)。
  • Jaeger:开源的链路追踪后端,负责存储与展示 trace。
  • 告警(Alerting):对指标设阈值规则、超限自动通知人;难点是分级、降噪、避免"狼来了"。

配套 demo:backend-notes/03-reliability/obs-demo —— 跑一下:打印结构化日志(JSON + 级别过滤)、每个接口的 RED 汇总并导出 Prometheus 文本格式、一条慢请求的 span 树,最后串成一次"接口变慢"的完整排查路径。零依赖,不用装 Prometheus / Grafana。

本文属《研发都要懂的事》· "跑得稳"专题。完整代码与系列在 GitHub · backend-notes