监控的三个层次:黑盒、白盒与业务
监控最容易踩的坑,是「装了一堆仪表盘,出事时却没人知道」。根源在于把监控当成装工具,而不是回答问题。合理的监控体系通常分三层,从外到内逐层收窄:
- 黑盒监控:站在用户视角问「服务能不能访问、响应多快」,比如探针定时请求健康检查接口。它最能反映真实体验。
- 白盒监控:看系统内部指标——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。
告警降噪与值班实践
告警系统最大的敌人不是漏报,而是狼来了。当值班人开始下意识忽略告警,再精确的规则也失去了意义。降噪有几条被反复验证的经验:
- 分级:只把「用户正在受损」的告警设成电话/呼叫级别(page),其余走群消息,别让所有告警都吵醒人。
- 聚合:同一根因引发的多条告警合并成一条,避免一次故障刷屏几百条。
- 抑制:下游服务故障时抑制上游的衍生告警,只保留最根源的那条。
- 静默:发布窗口、计划维护期间主动静默,别让正常变更触发误报。
值班机制上,建议明确「谁在值班、多久轮换、升级路径是什么」。一条 15 分钟无人响应的 page 应当自动升级到 backup。同时要建立复盘文化:每次故障后回看「监控是否第一时间发现、告警是否准确、响应是否够快」,然后去改规则、补监控,而不是只写一份整改报告。
一条从不误报的告警是可疑的;一条天天误报的告警是有害的。降噪是监控体系的一部分,不是可选项。
一套最小可用的监控栈
理论讲完,落到能立刻动手的最小组合。下面这套栈覆盖了指标、可视化与告警三件事,且全部可以用容器在半小时内起起来:
| 组件 | 职责 | 备注 |
|---|---|---|
| node_exporter / cAdvisor | 采集主机与容器指标 | 暴露 /metrics 供抓取 |
| Prometheus | 指标存储、抓取与告警规则计算 | 时序数据库 |
| Alertmanager | 告警去重、分组、抑制与路由 | 降噪核心组件 |
| Grafana | 仪表盘与统一查询入口 | 支持 PromQL / ES 多数据源 |
| Filebeat + Elasticsearch + Kibana | 日志采集、存储与检索 | 与指标关联排障 |
# docker compose 片段:一条命令起 Prometheus + Grafana
services:
prometheus:
image: prom/prometheus:latest
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
ports: ["9090:9090"]
grafana:
image: grafana/grafana:latest
ports: ["3000:3000"]
depends_on: [prometheus]
从最小栈开始,再按需长出来:业务指标不够就加埋点,日志太杂就先做结构化,链路看不清就接 OpenTelemetry。不要一开始就上大而全的平台——监控体系是随故障一起成长的,先把最关键的三五个指标盯住,比铺开几十个没人看的面板有用得多。