1. 从“monitor”这个词说起:它到底指什么
“monitor”这个词在技术圈里出现的频率极高,但每次出现时含义可能完全不同。有人说的 monitor 是操作系统里的管程(Monitor),有人说的 monitor 是监控系统(Monitoring System),还有人说的 monitor 是显示器硬件。这篇学习笔记要聊的,是我在实际项目中反复接触到的那个 monitor——系统监控与可观测性。
为什么突然想写这个?因为我在带新人的过程中发现一个很普遍的现象:很多人对 monitor 的理解停留在“装个 Prometheus,配个 Grafana 面板”这个层面。你问他为什么要监控、监控什么、告警阈值怎么定,他答不上来。这就好比一个人买了全套工具箱,但你问他螺丝刀是干嘛用的,他说“拧螺丝的”——没错,但拧哪里的螺丝、为什么要拧、拧多紧,这些才是关键。
monitor 的本质是什么?我的理解是:用可量化的数据回答“系统现在是否正常”这个问题,并且在异常发生之前或之初就发出信号。注意这里有两个关键词——“可量化”和“在异常发生之前或之初”。不可量化的感觉(比如“我觉得系统有点慢”)不叫 monitor;事后诸葛亮(比如用户投诉了才发现挂了)也不叫 monitor,那叫事故响应。
这篇文章适合谁看?如果你刚开始接触系统监控,或者已经在用一些监控工具但总觉得“哪里不对”,又或者你正在搭建自己的监控体系但不知道从何下手,那这篇笔记应该能帮到你。我会从监控的核心模型讲起,然后拆解指标采集、数据存储、告警规则、可视化这几个环节,最后分享一些我在实际项目中踩过的坑和总结的经验。
2. 监控体系的四个核心问题:先想清楚再动手
2.1 监控到底在监控什么
很多人一上来就问“用什么工具”,我觉得这个顺序反了。应该先问“我要监控什么”。从我的经验来看,监控对象可以分成四个层次:
基础设施层:CPU、内存、磁盘、网络。这是最底层的,也是最好采集的。但要注意,这些指标“正常”不代表系统“正常”。我见过 CPU 利用率只有 20% 但服务响应时间飙升的情况,原因是线程池被慢查询占满了。
中间件层:数据库连接数、消息队列堆积量、缓存命中率、连接池状态。这一层往往是问题的重灾区。举个例子,数据库连接池被打满,应用层表现是请求超时,但如果你只监控应用层的响应时间,你只能知道“出问题了”,不知道“为什么出问题”。
应用层:QPS、响应时间(P50/P95/P99)、错误率、饱和度。这是最贴近业务的监控层。这里有个经验:平均值会骗人。平均响应时间 100ms 看起来很好,但如果 P99 是 5 秒,意味着每 100 个用户就有 1 个在骂娘。
业务层:订单量、支付成功率、活跃用户数、关键业务转化率。这一层是给老板和产品经理看的,但对技术人员同样重要——因为技术问题最终会反映在业务指标上。
2.2 四种监控信号类型
业界有个比较成熟的分类框架,把监控信号分成四类:Metrics(指标)、Logs(日志)、Traces(链路追踪)、Profiles(性能剖析)。我简单说一下我的理解:
Metrics 是数值型的时间序列数据,比如“过去 5 分钟的平均响应时间”。它的优势是存储成本低、查询快、适合做告警。缺点是粒度粗,丢失了很多细节。
Logs 是离散的事件记录,比如“2024-01-15 10:23:45 用户 12345 请求 /api/order 失败,错误码 500”。它的优势是信息量大、适合排查具体问题。缺点是存储成本高、查询慢。
Traces 是请求级别的调用链路,能告诉你一个请求经过了哪些服务、每个服务耗时多少。它的优势是能定位跨服务的性能瓶颈。缺点是需要侵入式埋点,对代码有改造要求。
Profiles 是代码级别的性能数据,比如“哪个函数占用了最多 CPU”。它的优势是能定位到代码行级别。缺点是对运行时性能有影响,通常只在排查特定问题时开启。
我的建议是:Metrics 做日常监控和告警,Logs 做问题排查,Traces 做性能优化,Profiles 做深度分析。不要试图用一种信号解决所有问题。
2.3 告警阈值的设定逻辑
这是我最想强调的一点。很多团队的告警阈值是拍脑袋定的——“CPU 超过 80% 就告警”。但你想过没有,为什么是 80%?70% 行不行?90% 行不行?
我的经验是,阈值应该基于历史数据和业务影响来定,而不是基于“感觉”。具体做法是:
第一步,收集至少两周的历史数据,看看正常情况下的波动范围。如果 CPU 平时就在 60%-75% 之间波动,那 80% 的阈值可能太紧了,会频繁误报。
第二步,分析历史故障,看看故障发生前哪些指标出现了异常。比如某次故障前数据库连接数从 50 飙升到 200,那连接数的阈值就应该设在 150 左右。
第三步,区分告警级别。P0 告警(电话+短信)只留给真正影响用户的问题,P1 告警(IM 消息)留给需要关注但不紧急的问题,P2 告警(邮件)留给趋势性变化。
我踩过的一个坑:早期把所有告警都设成 P0,结果半夜被 CPU 告警吵醒,爬起来一看是定时任务在跑。后来我把告警分了级,P0 只留给“用户可感知的故障”,睡眠质量直线上升。
2.4 监控数据的保留策略
监控数据不是存得越久越好。原始精度数据(比如 10 秒一个点)存 15 天就够了,再久的数据可以降精度存储(比如 1 分钟一个点存 3 个月,1 小时一个点存 1 年)。这样既能满足排查需求,又能控制存储成本。
我见过一个团队,所有监控数据都按 10 秒精度存了两年,结果存储费用比服务器本身还贵。后来做了分级存储,成本降了 70%。
3. 指标采集的实操细节:从埋点到入库
3.1 采集方式的选择:推还是拉
监控数据的采集有两种模式:推(Push)和拉(Pull)。
推模式是客户端主动把数据发送到服务端。优点是实时性好、适合短生命周期任务(比如定时任务跑完就没了,拉模式可能来不及拉取)。缺点是客户端需要知道服务端地址、网络问题可能导致数据丢失。
拉模式是服务端定期去客户端拉取数据。优点是服务端控制节奏、容易发现客户端宕机(拉不到就是挂了)。缺点是对于短生命周期任务不友好、需要维护目标列表。
我的建议是:长期运行的服务用拉模式,短生命周期任务用推模式。很多现代监控系统两种都支持,可以根据场景灵活选择。
3.2 指标命名规范
这个看起来是小事,但实际影响很大。我见过指标命名混乱导致的后果:两个人分别采集了同一个指标,但一个叫cpu_usage,一个叫cpu_usage_percent,结果做聚合分析时对不上。
我的命名规范建议是:
- 使用小写字母和下划线:
http_request_duration_seconds - 包含单位:
_seconds、_bytes、_total - 使用标签区分维度,而不是把维度写进指标名:用
http_requests{method="GET", status="200"}而不是http_get_200_count - 计数器用
_total后缀,表示只增不减
3.3 埋点位置的选择
应用层监控的埋点位置很关键。我的经验是:
入口处埋点:在请求进入应用的第一层埋点,记录请求总量、响应时间、状态码。这能反映用户感知到的服务质量。
关键依赖处埋点:在调用数据库、缓存、外部服务的地方埋点,记录调用耗时和成功率。这能帮你快速定位是自身问题还是依赖问题。
业务逻辑关键节点埋点:在核心业务流程的关键步骤埋点,比如“订单创建成功”“支付回调处理完成”。这能把技术指标和业务指标关联起来。
一个实用技巧:埋点时记录一个
trace_id,这样当 Metrics 显示异常时,你可以用trace_id去 Logs 里查具体发生了什么。Metrics 和 Logs 的联动能大幅缩短排查时间。
3.4 采集频率的权衡
采集频率越高,数据越精细,但存储和计算成本也越高。我的经验值是:
- 基础设施指标(CPU、内存):15-30 秒一次
- 应用指标(QPS、响应时间):10-15 秒一次
- 业务指标(订单量):1 分钟一次
- 日志:实时采集,但可以做采样(比如只采集 10% 的 DEBUG 日志)
对于特别重要的指标,可以适当提高频率。但要注意,采集频率过高本身也会消耗资源,形成“观测者效应”。
4. 数据存储与查询:时序数据库的选型思路
4.1 为什么需要专门的时序数据库
有人可能会问:我用 MySQL 存监控数据不行吗?短期可以,长期不行。原因有三:
第一,写入量太大。假设 100 个指标,每个指标 10 秒采集一次,一天就是 864000 个数据点。MySQL 在这种写入压力下性能会急剧下降。
第二,查询模式不同。监控查询通常是“过去 1 小时的平均值”“过去 7 天的 P99”,这种聚合查询在关系型数据库里效率很低。
第三,数据过期策略不同。监控数据需要自动过期删除,关系型数据库做这个很别扭。
时序数据库(TSDB)就是为这种场景设计的。它在写入性能、聚合查询、数据过期方面都有专门优化。
4.2 选型时关注的几个维度
市面上的时序数据库不少,选型时我主要看这几个维度:
写入性能:每秒能写入多少数据点。这个指标决定了你能采集多细的粒度。
查询性能:聚合查询的响应时间。这个指标决定了你的 Grafana 面板能不能秒开。
存储效率:压缩比如何。好的时序数据库能把数据压缩到原始大小的 1/10 甚至更低。
运维复杂度:单机还是集群、是否需要额外依赖。对于小团队,运维复杂度往往比性能更重要。
生态兼容性:是否支持 PromQL 或类似的查询语言、是否有现成的 Grafana 数据源插件。
4.3 数据降精度与过期策略
前面提到过,监控数据应该分级存储。具体怎么做呢?
假设原始数据是 10 秒一个点,保留 15 天。然后通过降精度任务,把 10 秒数据聚合成 1 分钟数据(取平均值、最大值、最小值),保留 90 天。再聚合成 1 小时数据,保留 1 年。
这样做的逻辑是:越久远的数据,你越不需要看那么细的粒度。你排查昨天的问题可能需要 10 秒精度,但排查三个月前的问题,1 小时精度就够了。
注意:降精度时不仅要保留平均值,还要保留最大值和最小值。因为平均值会掩盖尖峰,而尖峰往往是问题的信号。
5. 告警规则的设计与优化:从“狼来了”到“精准打击”
5.1 告警规则的基本结构
一条告警规则通常包含这几个部分:
- 查询表达式:从时序数据库里查出需要判断的指标
- 持续时间:条件持续多久才触发告警
- 阈值:触发告警的临界值
- 告警级别:P0/P1/P2
- 通知渠道:电话/短信/IM/邮件
举个例子:avg(http_request_duration_seconds{job="api"}) > 0.5 for 5m。意思是 API 服务的平均响应时间超过 500ms 且持续 5 分钟,就触发告警。
5.2 减少误报的几种手段
误报是监控系统最大的敌人。频繁的误报会让团队对告警麻木,真正的问题反而被忽略。我总结了几种减少误报的手段:
使用持续时间:不要一超过阈值就告警,而是持续一段时间再告警。这能过滤掉毛刺。
使用多条件组合:比如“CPU 高 且 响应时间高”才告警,单独 CPU 高可能是正常波动。
使用动态阈值:基于历史数据自动计算阈值,而不是固定值。比如“超过过去 7 天同一时段的 3 倍标准差”。
使用告警分组:把相关的告警合并成一条通知,避免告警风暴。
使用静默期:在发布、维护等已知会触发告警的时间段,提前静默相关告警。
5.3 告警分级与通知策略
告警分级不是拍脑袋定的,而是基于业务影响和紧急程度。我的分级标准是:
| 级别 | 定义 | 通知方式 | 响应要求 |
|---|---|---|---|
| P0 | 用户可感知的故障,核心功能不可用 | 电话+短信+IM | 立即响应 |
| P1 | 非核心功能异常,或核心功能性能下降 | IM+短信 | 30分钟内响应 |
| P2 | 趋势性异常,暂不影响用户 | IM | 当天处理 |
| P3 | 提示性信息,无需立即处理 | 邮件 | 按需处理 |
一个经验:P0 告警一定要少。如果 P0 告警每天超过 3 条,说明要么系统太不稳定,要么分级标准有问题。
5.4 告警内容的编写规范
告警内容要能让接收者不看监控面板就能判断问题严重程度和大致方向。我建议包含以下信息:
- 告警名称:简洁明了,比如“API 响应时间过高”
- 当前值:比如“当前 P99 响应时间 2.3s”
- 阈值:比如“阈值 1s”
- 持续时间:比如“已持续 8 分钟”
- 影响范围:比如“影响 /api/order 接口”
- 相关链接:监控面板链接、日志查询链接、Runbook 链接
6. 可视化面板的设计:让数据说话
6.1 面板布局的基本原则
Grafana 面板不是越花哨越好。我的布局原则是:
从上到下,从粗到细:最上面放核心业务指标(一眼看全局),中间放应用指标(定位问题方向),下面放基础设施指标(深入排查细节)。
从左到右,从因到果:左边放原因类指标(CPU、连接数),右边放结果类指标(响应时间、错误率)。这样排查时视线从左到右移动,符合因果逻辑。
重要指标放大:核心指标用大面板,辅助指标用小面板。不要让次要信息占据主要位置。
6.2 图表类型的选择
不同数据适合不同的图表类型:
- 时间序列:折线图,适合看趋势
- 当前状态:单值图或仪表盘,适合看“现在怎么样”
- 分布情况:热力图,适合看 P50/P95/P99 的分布
- 对比分析:柱状图,适合看不同维度的对比
- 占比分析:饼图,适合看各部分的占比(但饼图不宜超过 5 个分类)
6.3 变量与模板化
Grafana 的变量功能非常实用。你可以定义$instance、$job、$interval等变量,让一个面板适配多个实例或时间范围。这样不用为每个服务单独建面板,维护成本大幅降低。
一个实用技巧:把常用的查询时间段做成变量(比如
$range),这样可以在面板上快速切换“最近 1 小时”“最近 24 小时”“最近 7 天”。
7. 实际项目中踩过的坑与经验总结
7.1 监控系统本身的监控
这是个容易被忽略的问题:监控系统自己挂了怎么办?我遇到过 Prometheus 因为磁盘写满而停止采集的情况,结果那段时间的监控数据全部丢失。
解决方案是:监控系统本身也要被监控。具体来说:
- 监控采集组件的存活状态(进程是否在、端口是否通)
- 监控存储组件的磁盘使用率、写入延迟
- 监控告警通道的可用性(发一条测试告警,看是否能收到)
- 设置“心跳告警”:如果监控系统超过一定时间没有上报数据,就触发告警
7.2 监控数据与日志的关联
前面提到过trace_id的作用。实际项目中,我建议在应用层统一注入trace_id,并在日志和监控指标中都带上这个 ID。这样当监控告警时,你可以直接点击链接跳到日志系统,用trace_id查出完整的请求链路。
7.3 监控覆盖率的评估
怎么知道监控覆盖得够不够?我的方法是做“故障演练”:故意制造一些故障(比如杀掉一个进程、模拟网络延迟),看监控系统能不能发现、多久能发现、告警内容是否准确。
如果某个故障监控系统发现不了,说明存在监控盲区,需要补充采集或告警规则。
7.4 团队协作与 Runbook
监控告警不是终点,而是起点。收到告警后应该怎么做?这就需要 Runbook(操作手册)。Runbook 应该包含:
- 告警的含义和可能的原因
- 排查步骤(先看什么、再看什么)
- 常见的修复方法
- 升级路径(搞不定找谁)
我的经验是:Runbook 不要写太长,一页纸就够了。太长的 Runbook 没人看。关键是要把“第一步做什么”写清楚。
7.5 监控体系的演进节奏
不要试图一次性搭建完美的监控体系。我的建议是分阶段演进:
第一阶段:核心指标采集 + 基础告警。先保证“系统挂了能知道”。
第二阶段:完善应用层监控 + 告警分级。做到“知道哪里挂了”。
第三阶段:引入链路追踪 + 日志关联。做到“知道为什么挂了”。
第四阶段:业务监控 + 智能告警。做到“在用户感知之前发现问题”。
每个阶段之间留出时间让团队适应,不要跳步。
8. 一些值得关注的细节
8.1 时间同步问题
监控数据的时间戳依赖各服务器的时间同步。如果服务器时间不同步,会导致数据错乱、告警误判。确保所有服务器都配置了 NTP 时间同步,并且定期检查同步状态。
8.2 监控数据的采样与聚合
对于高基数指标(比如每个用户 ID 一个标签),直接采集会导致存储爆炸。这时候需要做采样或预聚合。比如只采集 P99 而不是所有原始值,或者按用户分组而不是按单个用户。
8.3 告警的“疲劳管理”
如果团队对告警产生疲劳,再好的监控体系也白搭。定期回顾告警记录,分析哪些告警是无效的、哪些可以合并、哪些阈值需要调整。我建议每个月做一次“告警回顾”,持续优化。
8.4 监控与成本
监控不是免费的。存储、计算、网络都有成本。在设计监控体系时,要平衡“监控粒度”和“成本”。我的原则是:核心指标高精度、次要指标低精度、非必要指标不采集。
9. 写在最后
监控这件事,说到底是“用数据驱动运维决策”。工具会变、技术会变,但核心逻辑不变:采集数据、存储数据、分析数据、基于数据做决策。
我在实际项目中的体会是:好的监控体系不是建出来的,而是迭代出来的。一开始不用追求大而全,先把核心指标监控起来,然后根据实际遇到的问题逐步完善。每次故障都是一次优化监控的机会——故障发生后,问自己“如果监控更完善,能不能更早发现”,然后把答案落实到监控体系中。
最后分享一个小技巧:在每次故障复盘时,除了写“故障原因”和“修复措施”,再加一项“监控改进”。这样监控体系会随着每一次故障变得越来越完善,而不是原地踏步。