告警越来越多,问题却越来越难查?聊聊可观测性的 4 个断层

2 阅读8分钟

很多团队都经历过这样的阶段:

服务上线时只有日志,出问题靠 grep;后来接入监控,开始看 CPU、内存和 QPS;再后来补上链路追踪,以为终于可以“一眼定位问题”。

结果是:仪表盘越来越丰富,日志平台越来越贵,告警也越来越多,但真正的故障排查仍然很慢。

一个典型的线上场景可能是这样:

  • 用户反馈下单变慢;
  • 监控显示某台机器 CPU 很高;
  • 日志里没有明显报错;
  • 链路追踪只看到几个耗时较长的 Span;
  • 最后排查半天,发现是某个下游接口在特定租户数据下出现了慢查询。

这不是工具不够多,而是可观测性链路之间存在断层。

可观测性不是“把 Metrics、Logs、Traces 都接进来”,而是让工程师在不知道问题是什么时,仍然能够提出假设、缩小范围并找到证据。

下面聊聊最常见的 4 个断层。

断层一:指标很多,但没有业务语义

最常见的指标面板通常长这样:

CPU 使用率
内存使用率
磁盘 IO
请求 QPS
线程池大小
连接池数量

这些指标当然有价值,但它们回答的是“系统资源怎么样”,不一定能回答“用户受到了什么影响”。

CPU 从 30% 涨到 60%,可能完全不影响业务;而 CPU 只有 20%,也可能因为线程阻塞导致所有请求超时。

所以,第一层可观测性应该围绕用户可感知的信号建立。

常用的四类 Golden Signals 是:

  • Latency:请求耗时;
  • Traffic:请求流量;
  • Errors:错误率和错误类型;
  • Saturation:资源饱和度。

但在业务系统里,还应该补充业务 SLI,例如:

支付成功率
订单创建成功率
消息投递延迟
库存扣减成功率
搜索无结果率

只有先定义“什么算好”,后面的指标才有解释意义。

例如,延迟看平均值很容易被大量正常请求稀释。更有价值的是分位数:

histogram_quantile(
  0.95,
  sum by (le, route) (
    rate(http_request_duration_seconds_bucket{service="order-api"}[5m])
  )
)

这条查询回答的是:过去 5 分钟,95% 的请求在多长时间内完成?

还可以继续回答:

  • P95 变慢发生在哪个 route?
  • 是所有租户都慢,还是某个租户慢?
  • 是服务端处理慢,还是客户端等待久?
  • 错误率是否和延迟上升同时发生?

指标如果没有业务语义、维度和时间窗口,最终只会变成“看起来很专业的背景墙”。

断层二:有日志,但没有统一上下文

很多故障排查卡住,不是因为日志少,而是因为无法把同一次请求的日志串起来。

例如:

request start
user query start
db timeout
request failed

这四行日志看起来能读懂,但系统里同时可能有几千个请求。没有统一的关联字段,就只能靠时间戳猜。

可观测性日志至少要满足三个条件:

  1. 结构化;
  2. 带关联标识;
  3. 记录关键上下文,但不包含敏感数据。

一个最小可用的日志上下文可以设计成:

type LogContext = {
  traceId: string;
  spanId?: string;
  requestId: string;
  service: string;
  route?: string;
  tenantId?: string;
  errorCode?: string;
};

在请求入口生成上下文,然后在整个调用链中透传:

logger.info({
  event: "order_create_started",
  traceId,
  requestId,
  service: "order-api",
  route: "POST /orders",
  tenantId,
}, "order create started");

关键是字段名要统一:

  • 不要今天用 trace_id,明天用 traceID;
  • 不要把完整身份证、手机号、Token 写进日志;
  • 不要在低基数指标里塞用户 ID;
  • 不要只记录“失败”,要记录失败类型和可检索的错误码。

日志的价值不在于数量,而在于:

从一个错误现象出发,能否在几分钟内找到对应的请求、调用链和业务上下文。

断层三:有链路追踪,但没有真正打通

Trace 工具最容易产生“我们已经可观测了”的错觉。

打开链路面板,能看到一串 Span,不代表它就能帮助定位问题。

常见问题有三类。

第一,Span 命名没有业务信息。

GET
GET
POST

不如:

OrderController.create
OrderService.validateStock
PaymentClient.createPayment

第二,异步任务丢失上下文。

很多请求进入消息队列或定时任务后,traceId 没有继续传递,导致主请求和后台任务变成两段互不相关的链路。

第三,采样策略只考虑数量。

如果只按固定比例采样,很可能恰好丢掉那些真正需要排查的慢请求和错误请求。更合理的方式是:

  • 正常请求低频采样;
  • 错误请求优先保留;
  • 慢请求按延迟阈值保留;
  • 关键业务链路提高采样率;
  • 敏感请求脱敏后再保留。

Trace 不只是调用耗时列表,它应该回答:

  • 这次请求经过了哪些服务?
  • 时间主要花在哪个环节?
  • 哪个下游依赖先出现异常?
  • 数据库查询和外部调用分别耗用了多少时间?

如果 Trace 和 Logs、Metrics 之间没有 ID 关联,那么它们只能各自提供局部证据,无法形成因果链。

断层四:告警很多,但没有人能立刻采取行动

告警是另一个特别容易被“数量优化”误导的地方。

有些团队把告警阈值调得非常灵敏,结果每天收到大量通知,但大部分都是:

  • 瞬时抖动;
  • 单实例异常但流量已经切换;
  • 资源水位升高但业务仍正常;
  • 同一个根因触发了十几个重复告警。

告警越多,工程师越容易产生告警疲劳。真正的线障反而被淹没在噪音里。

一个有效的告警至少要包含四层信息:

alert: CheckoutLatencyHigh
expr: |
  histogram_quantile(
    0.95,
    sum by (le) (
      rate(http_request_duration_seconds_bucket{route="/checkout"}[5m])
    )
  ) > 2
for: 10m
labels:
  severity: page
  team: payments
annotations:
  summary: "Checkout P95 latency is above 2s"
  dashboard_url: "https://grafana.example.com/d/checkout"
  runbook_url: "https://wiki.example.com/runbooks/checkout-latency"

其中最重要的不是表达式,而是:

  • for:持续多久才真正告警;
  • severity:是需要立刻处理,还是工作时间内处理;
  • team:谁负责;
  • runbook_url:第一步该做什么;
  • dashboard_url:从哪里开始查。

告警应该面向用户可感知的症状,而不是所有内部资源指标。

例如:

  • 支付成功率低于目标,值得 Pager;
  • 单个 CPU 短时升高,可能只需要记录;
  • 磁盘容量 7 天后会满,可以提前工单;
  • 某下游错误率持续上升,并且影响核心链路,才需要升级。

告警不是一个“阈值集合”,而是一套行动协议。

如何把四层信息真正连起来

可观测性建设可以采用一个很朴素的目标:

从用户反馈的一个现象,沿着请求链路走到最终根因。

具体可以按下面的顺序推进。

1. 先定义业务 SLI

不要从工具开始,而要从“用户在意什么”开始:

下单成功率
支付成功率
搜索成功率
消息送达延迟

2. 建立统一上下文

所有服务至少透传:

traceId
requestId
service
route
errorCode

统一字段命名,并在入口处生成或接收上游上下文。

3. 让日志、指标、链路互相引用

理想状态是:

告警
  -> Dashboard
  -> 异常时间窗口
  -> 代表性 Trace
  -> 对应日志
  -> 明确错误码和调用链

只要其中一个环节断裂,排查时间就会成倍增加。

4. 对高基数和敏感信息做边界设计

不要把所有东西都放进 Metrics。用户 ID、订单 ID 这类高基数数据适合放日志或 Trace,不适合直接成为指标标签。

日志中也要避免记录:

  • Token、Cookie、密码;
  • 完整手机号、身份证号;
  • 支付凭证;
  • 用户上传的原始内容。

可观测性越强,越需要明确数据边界。

5. 用复盘验证可观测性,而不是只验证系统恢复

一次故障结束后,除了问“怎么修好的”,还应该问:

  • 能否在 5 分钟内定义影响范围?
  • 能否找到一个代表性 Trace?
  • 能否通过日志还原关键业务状态?
  • 告警是否足够具体?
  • 下一次是否可以用同样路径更快定位?

如果复盘只改代码,不改观测能力,下一次故障很可能只是换一种方式重演。

最后

可观测性不是三个工具的缩写。

Metrics 解决“整体趋势和异常检测”,Logs 解决“具体发生了什么”,Traces 解决“时间花在哪里、请求经过了谁”。

只有它们通过统一的业务语义、关联 ID 和行动协议连接起来,工程师才可能从“知道系统出问题了”,走到“知道为什么出问题”。

工具可以采购,拓扑可以画出来,但真正决定排障效率的,是系统能不能围绕一次用户请求,提供一条连续、可信、可操作的证据链。

如果你也在做可观测性建设,也欢迎在微信公众号搜索「长安米粒贵」继续交流。