监控的三个层次:黑盒、白盒与业务

监控最容易踩的坑,是「装了一堆仪表盘,出事时却没人知道」。根源在于把监控当成装工具,而不是回答问题。合理的监控体系通常分三层,从外到内逐层收窄:

  • 黑盒监控:站在用户视角问「服务能不能访问、响应多快」,比如探针定时请求健康检查接口。它最能反映真实体验。
  • 白盒监控:看系统内部指标——CPU、内存、QPS、延迟、错误率、连接池、GC。用于定位问题出在哪个环节。
  • 业务监控:看真正值钱的数字——下单成功率、支付转化、消息积压量。技术指标全绿但业务指标跳水,这类事故只有业务监控能先发现。
监控的目标不是「把图画满」,而是「在用户投诉之前,先一步知道哪里不对劲」。
本文核心知识点
  • 三层监控黑盒看体验、白盒看内部、业务看价值,缺一层就有盲区。
  • 拉取模型Prometheus 周期性地从目标抓取指标,天然适合服务发现。
  • SLO 告警用错误预算代替静态阈值,告警更贴近用户感受。
  • 日志分工Elasticsearch 存与检索、Logstash 加工、Kibana 展示。
监控体系示意:抽象仪表盘、雷达眼、告警铃与日志条带组成的蓝色扁平插画
指标回答「是否异常」,日志回答「为什么异常」,链路回答「异常发生在哪一跳」。

指标采集:Prometheus 的拉取模型

Prometheus 与很多监控系统最大的不同,是它采用拉取(pull)而非推送:由服务端按固定周期主动去抓取目标暴露的 /metrics 端点。这样做的好处是配置集中、目标一旦不可达立刻可知,配合服务发现能自动感知扩缩容。

它的数据模型也很克制:一个指标由「名字 + 一组标签」唯一确定,值永远是浮点数,配上时间戳就是一条时间序列。理解了这一点,PromQL 的写法就顺理成章。

# 平均每秒请求数:对计数器求速率(5 分钟窗口)
rate(http_requests_total[5m])

# 按接口分组求和,找出最热的路径
sum by (path) (rate(http_requests_total[5m]))

# 95 分位响应延迟
histogram_quantile(0.95, sum by (le) (rate(http_request_duration_seconds_bucket[5m])))

# 错误率:5xx 占总请求的比例
sum(rate(http_requests_total{status=~"5.."}[5m])) / sum(rate(http_requests_total[5m]))

写指标的三个建议:

  • 计数器只用 _total 后缀,用 rate() / increase() 取值,不要直接看绝对值。
  • 标签要有节制:用户 ID、请求 ID 这类高基数标签会让时间序列爆炸,是压垮 Prometheus 的头号原因。
  • 直方图优先于摘要:直方图可在服务端聚合出任意分位,摘要不行。

告警规则:从阈值到 SLO

告警规则写得好不好,直接决定值班体验。最原始的做法是拍一个静态阈值——「延迟超过 500ms 就报警」。它的问题很明显:阈值定高了漏报,定低了天天误报,而且和用户真实感受脱节。

更成熟的做法是基于 SLO 与错误预算:先定义「一个月内 99.9% 的请求在 300ms 内成功」,那么可容忍的失败比例就是 0.1%;只有当错误预算被快速消耗时才告警。这带来两个好处——告警直接对应「用户是否受损」,且速率型规则天然过滤掉偶发抖动。

告警方式触发条件适用
静态阈值指标持续高于某固定值资源类硬上限,如磁盘 85%
同比/环比与上周同一时刻偏差过大有明显周期性的业务量
SLO 燃尽率错误预算消耗速度过快面向用户体验的核心服务
缺失检测目标不再上报指标进程崩溃、采集中断
groups:
  - name: api-slo
    rules:
      # 5 分钟窗口错误率超过 5% 且持续 10 分钟
      - alert: HighErrorRate
        expr: |
          sum(rate(http_requests_total{status=~"5.."}[5m]))
          / sum(rate(http_requests_total[5m])) > 0.05
        for: 10m
        labels:
          severity: page
        annotations:
          summary: "接口错误率超过 5%"

for 字段是被低估的关键字:它要求条件持续满足一段时间才真正触发,能挡掉绝大多数瞬时抖动。没有 for 的告警,等于把噪声直接推给值班人。

日志体系:ELK 的分工

指标能告诉你「出问题了」,但如果要知道「为什么」,就得靠日志。ELK 是日志领域最经典的组合,三个组件各司其职:

  • Elasticsearch:存储与检索,基于倒排索引,擅长全文搜索与聚合。
  • Logstash:采集与加工,把杂乱的原始日志解析成结构化字段。
  • Kibana:查询与可视化,提供检索界面和仪表盘。

实践中常用更轻量的 Filebeat 或 Fluent Bit 替代 Logstash 做采集端,把解析责任下沉到应用——日志在产生时就写成 JSON,是最省事、最不容易出错的做法。

# 一条结构化应用日志(JSON,单行)
{"ts":"2026-10-14T09:12:33Z","level":"ERROR","service":"order","traceId":"a1b2c3","msg":"payment timeout","orderId":"SO10086","costMs":3000}
日志埋点要点原因
结构化 JSON字段可被直接索引与聚合,无需正则解析
带 traceId跨服务串联同一次请求的全部日志
分级明确检索时能快速过滤 ERROR / WARN
避免打印大对象日志量膨胀会推高存储成本与检索延迟

成本控制同样重要:日志保留期按价值分层——ERROR 留 30 天、INFO 留 7 天、DEBUG 默认关闭;用索引生命周期策略(ILM)自动把老索引转到廉价存储或删除。

链路追踪与关联分析

在微服务架构里,一次用户请求可能穿过五六个服务。指标告诉你「某个服务变慢」,链路追踪则告诉你「慢在调用链的哪一跳」。它的核心是分布式上下文传播:请求进入系统时生成一个全局 traceId,每经过一个服务再生成一个 spanId,父子关系构成一棵调用树。

  • Trace:一次完整请求的调用链,贯穿所有服务。
  • Span:调用链中的一个环节,含开始时间、耗时、状态、标签。
  • Context 传播:通过 HTTP 头或消息头把 traceId 传下去。

真正让排障效率翻倍的是三者的关联:在 Grafana 面板上看到某接口 P99 飙升,一键跳到该时间窗的慢链路,再从某个慢 span 直接下钻到它的日志。这条「指标 → 链路 → 日志」的动线打通后,定位一个线上问题往往从半小时缩短到几分钟。实现上,OpenTelemetry 已成为采集端的统一标准,指标、日志、链路可以共用一套 SDK。