1. 为什么我会自己写一套"PLFM_RADAR"
先说结论:这套东西不是哪个大厂开源项目,也不是论文里的概念验证,而是我在业务线被线上问题反复折磨之后,自己动手攒的一套平台状态雷达监控系统。命名叫 PLFM_RADAR,PLFM 是 Platform 的缩写,RADAR 就是雷达,合起来就是"平台雷达"。取这个名字的意图非常直接——我要的不是一堆报表,也不是事后才能翻出来的日志,我要的是一个能持续扫描平台健康度、提前发现异常苗头的"雷达"。
做平台运维和研发的朋友应该都有这种体会:业务规模一上来,系统拆成几十个微服务,中间件一大堆,数据库分片分表,消息队列一条链路上挂七八个节点,这时候"平台到底健不健康"这个问题,其实很难回答。单看 CPU、内存、磁盘这些基础指标,根本看不出业务有没有问题;单看日志,又会被海量信息淹没;等用户投诉或者监控告警响起来的时候,往往已经造成实际影响了。
我遇到过的真实场景是这样的:某个核心接口的 P99 延迟从 200ms 慢慢涨到了 800ms,持续了将近 40 分钟,基础监控完全没反应——因为 CPU 和内存都正常,只有连接池指标异常,但当时监控面板里根本没有连接池这一项。等我从一堆业务日志里找到根因,用户那边已经炸锅了。那之后我就在想,我需要一个能主动"扫描"的平台观测工具,把关键链路、关键资源、关键瓶颈全部暴露在一张图上,任何一项出现偏离就能立刻看到。
PLFM_RADAR 要解决的核心问题就是三个:发现要早、定位要快、信息要全。它不是要替代 Prometheus、Grafana 这类成熟方案,而是要把这些工具的能力收拢到一个"平台健康度雷达"的视角下,站在业务和平台的交界处去做观测。适合参考这套设计的人,主要是中小团队的后端研发、运维工程师、SRE,以及那些被"监控有了但好像没什么用"困扰着的同学。
这套系统我从零开始搭,到能用、好用、扛住一次真实故障,前后花了大概三周业余时间。下面我把整个设计思路、技术选型和实操过程完整拆开讲,包含所有踩过的坑。
2. 整体设计思路:雷达的三段式工作逻辑
2.1 真实雷达和平台雷达的类比
真正的雷达是怎么工作的?它持续发射电磁波,接收反射信号,通过分析回波的强度、时间差和多普勒频移,来判断目标的有无、距离和速度。这个过程本质上是一个"感知—识别—预警"的闭环。
PLFM_RADAR 的设计完全套用了这个逻辑。感知层负责持续采集平台的各类状态信号,包括基础资源指标、中间件指标、业务指标、链路追踪数据;识别层负责对这些信号做降噪、聚合、关联分析,判断当前平台状态属于正常、异常还是告警边缘;预警层则把分析结果通过可视化和告警推送出去,让值班的人能够快速响应。
我见过太多监控系统的通病:有数据但不看,有告警但不准。问题就出在感知层和识别层之间缺了一个"翻译"的过程。采集上来的指标是离散的、低语义的,比如jvm_thread_count=512,这只是一个数字,它不能告诉你"线程数快打满了,可能导致请求排队"。PLFM_RADAR 的识别层做的就是这件事——把原始指标翻译成"平台健康语义",比如"线程池压力中等偏上"、"连接池接近饱和"、"GC 停顿频率异常",这些才是做决策需要的信息。
2.2 三层架构:采集、识别、展示预警
PLFM_RADAR 在物理部署上分成三个部分,我分别叫 collector、brain、pilot。
collector 是一组轻量级的采集代理,部署在每个需要观测的节点上。它的职责非常纯粹——采集指标并上报,不做任何复杂判断。采集的数据分为四类:
| 数据类别 | 关键指标示例 | 采集频率 |
|---|---|---|
| 基础资源 | CPU 使用率、内存用量、磁盘 IO、网络带宽 | 15s |
| 中间件状态 | MySQL 连接数/慢查询、Redis 内存/命中率、MQ 积压量 | 30s |
| 业务指标 | QPS、成功率、P99 延迟、活跃用户数、订单量 | 10s |
| 链路数据 | 接口调用次数、依赖时长、错误码分布 | 10s |
brain 是核心分析模块,负责把 collector 上报的原始数据做处理。它干三件事:一是做基线计算,把过去 7 天同一个时间点的数据做平滑,得到"这个时间段正常应该是什么水平"的上下界;二是做关联分析,比如当 P99 延迟上涨时,自动去关联连接池、GC、依赖服务这几类候选指标,给出"最可能的几个原因维度";三是做评分,给平台整体健康度打一个 0 到 100 的分值,并拆解到各个子系统。
pilot 是展示和预警层,实际就是一个配置了多个仪表盘的 Grafana,加上一套自定义的告警路由规则。这个命名方式是我自己的习惯——collector 收集信号,brain 思考判断,pilot 把结论带给你。实际上对应的就是 Prometheus + 自定义分析服务 + Grafana/Alertmanager 这套常见组合。
2.3 为什么不用开箱即用的监控系统
团队里有人问我,直接用 Prometheus + Grafana 不就行了,为什么还要套一层自己的逻辑。我的回答是:工具链可以复用,但"观测视角"必须自己定义。
Prometheus 确实是很优秀的时序数据库和采集器,Grafana 的图表能力也足够强,但开箱即用的默认配置是从"机器视角"出发的,不是从"业务平台视角"出发的。默认的 Node Exporter 看的是 CPU、内存、磁盘,它不会告诉你"下单链路中哪个环节变慢了"。如果只是把这些工具拼在一起,你得到的是一堆精致的图表,而不是一个会判断"平台当前状态是否健康"的系统。
PLFM_RADAR 的价值在于把"采集能力"和"判断逻辑"分开,采集交给成熟的组件,判断逻辑自己用代码实现。健康度评分、基线偏离检测、告警风暴抑制、多指标关联排查,这些才是我认为监控系统的核心价值所在,而这些恰恰是通用开源工具默认不提供的东西。
3. 核心技术点拆解:指标设计、健康度评分和链路关联
3.1 四大黄金指标:从 RED 方法到平台雷达指标体系
在做指标设计之前,我先确定了自己的方法论。很多 SRE 圈子的人都知道 Google 的四个黄金信号——延迟、流量、错误、饱和度。这确实是很好的起点,但对于一个完整的业务平台来说,只看这四个维度还不够。
PLFM_RADAR 的指标体系在四个黄金信号基础上做了扩展,我把它叫作"4+2"模型。4 就是黄金信号,2 是额外增加的"容量水位"和"依赖健康度"。
延迟指标不只是接口平均响应时间,而是拆成 P50、P95、P99 三档。为什么要分档?因为平均值会掩盖长尾问题。有一次一个接口平均延迟看起来很正常,但 P99 已经飙到了 5 秒,原因是一个新上线的功能在特定数据量下触发了慢 SQL。如果只看平均值,这个事故可能要更晚才会暴露。
流量指标要看两层,一是入口层流量,比如 Nginx 或网关的 QPS,二是内部服务间的调用量。入口流量涨了是好事还是坏事,要看内部调用是否跟得上,所以我做了入口流量与依赖调用量的环比联动分析。
错误指标要区分"错误率"和"错误数"。错误率高通常意味着逻辑或依赖有问题,错误数多但错误率低,可能只是流量涨了,这时候反而可能是正常现象。PLFM_RADAR 对这两类情况设置了不同的告警阈值。
容量水位是我特别看重的一个维度。所谓容量水位,就是当前使用量离上限还有多远。连接池用了 300 个连接、上限是 400 个,那水位是 75%。这个指标的最大价值在于"提前量"——在资源真正耗尽之前,你还有时间做扩容或者优化。基础监控看的是"现在怎么样",容量水位看的是"还能撑多久"。
依赖健康度则是把平台当成一个整体来评估。一个服务本身没问题,但它依赖的 Redis 集群慢查询变多了,这个服务就会被拖慢。PLFM_RADAR 会给每个核心服务维护一个"依赖列表",定时探测每个依赖的健康状态,任何一个依赖的健康度下降,都会在雷达图上看到对应服务的联动变化。
3.2 健康度评分算法:加权计算与基线偏离检测
健康度评分是 PLFM_RADAR 最核心的功能之一,它把多维度的指标压缩成一个 0 到 100 的分数。这个分数我用了一个很直接的加权模型:
健康度 = 100 - Σ(扣分项)基础扣分项分成两块。第一块是硬指标越界,比如 CPU 超过 85%、内存使用率超过 90%、连接池水位超过 80%,每项扣 10 到 20 分。第二块是基线偏离,即当前指标和历史同期正常水平比,如果偏差超过 30%,按偏离比例扣分。
加权系数的设计我基于一个原则:越接近用户侧的指标,权重越高。比如订单创建接口的 P99 延迟,权重是 0.3;数据库的连接池水位,权重是 0.15;而某条不太重要的内部日志服务的延迟,权重只有 0.05。这样设计的原因是,最终用户感受到的是端到端的质量,内部任何一个环节出了问题,最终都会反映到用户侧指标上,所以用户侧指标必须拥有最高的"话语权"。
基线不是硬编码的,而是系统自动学习的。brain 模块每一小时计算一次滑动窗口基线,取过去 14 天同时段数据的 P50 作为基准值。比如今天是周三下午 3 点,系统会取之前所有周三下午 3 点的指标数据求中位数作为基线。这个方法虽然简单,但实测下来非常有效,因为业务流量有很强的周期性,用"同比同时段"做对比才能避免误报。我见过不少监控系统就是因为拿"今天的 3 点和凌晨 3 点"对比,导致白天流量增长时疯狂误报,最后值班的人都把告警给屏蔽了。
健康度评分我分了三档:80 分以上是"健康",60 到 80 分是"亚健康",60 分以下是"危险"。这个分档不是拍脑袋定的。我把过去一个月的历史数据回放了一遍,把每次线上故障发生前 30 分钟的评分曲线都拉出来,发现真实故障发生前,健康度大概率都会跌破 70 分。所以 60 分作为危险线,留了大约 10 分的提前量。
3.3 链路关联与根因定位:主动关联分析代替人工翻日志
监控系统最耗时的环节就是定位根因。传统方式是接到告警之后,先看自己的服务日志,再去看依赖服务,最后看中间件,一层一层排查,运气好十分钟,运气不好一两个小时就没了。
PLFM_RADAR 做了一个我认为最有实用价值的功能——关联分析。它会在检测到核心指标异常时,自动去分析同一时间窗口内其他相关指标的变化情况,然后在告警消息里附带一份"疑似关联项"清单。
关联分析逻辑我编写了一个独立分析引擎,内部叫correlator。它接收一段异常时间窗口,然后去查询几个候选维度的历史数据:调用方服务的 QPS 变化、当前服务的错误率变化、所依赖中间件的关键指标变化、虚拟机/容器的资源指标变化。每个维度计算异常时间段与该候选指标正常基线的偏离程度,按偏离从大到小排序。
我说一个实际案例。有一次系统报警,说支付服务的 P99 延迟从 300ms 涨到 1.2s,持续了五分钟。如果按照传统的排查方式,可能要查网关日志、查支付服务日志、查数据库慢查询,最后才能定位到是订单服务的某个接口出现了慢调用,拖慢了支付服务的依赖调用,从而影响整体链路。
但用了 PLFM_RADAR 之后,correlator 给出的关联项排序是:
- 订单服务 createOrder 接口 P99 上升到 2.3s(偏离度 158%)
- MySQL 订单库的慢查询数量上升(偏离度 96%)
- 支付服务所在节点的 CPU 使用率变化不大(偏离度 12%)
这个排序实际上把根因指向了订单库的慢查询。我到 MySQL 里一查,果然是一条新上线的 SQL 没有走索引,在数据量增长后导致扫描行数暴增,托慢了整个调用链。原来可能需要 40 分钟的排查,这次从报警到确认根因只用了 8 分钟。
关联分析要正常工作,有一个前提条件——指标必须有足够的时间精度。所以我使用 10 秒级采集周期和 30 秒级聚合粒度。如果采集周期是 5 分钟,那关联分析基本没法用,因为异常发生时间窗口内的数据没有足够多的时间点来做对比。
4. 实操过程:从采集代理到可视化看板,一步步搭起来
4.1 采集层搭建:Node Exporter 和自定义业务指标
PLFM_RADAR 的采集层我分了三条线。基础资源指标用的是 Prometheus 官方生态的 Node Exporter;中间件指标根据中间件类型分别选用了 mysqld_exporter、redis_exporter 等;业务指标则是在业务代码里直接暴露/metrics端点。
Node Exporter 的部署没什么难度,但有一个细节要注意——版本和默认采集项。新版 Node Exporter 默认禁用了一部分 collector,比如diskstats在某些版本里默认就是关掉的。如果没开,磁盘 IO 的读写延迟指标就抓不到,而这项数据在做根因分析时非常关键。我踩过一次这个坑,部署完成后发现 IO 相关面板全是空的,查了半天才找到是因为 collector 没开。
Redis Exporter 有一个需要注意的点:默认配置下它会执行INFO命令获取实例状态,如果 Redis 设置了密码,需要在 DSN 里带上去,否则连不上实例。另外,如果 Redis 实例非常多,要注意控制抓取频率,否则 exporter 自身会成为 Redis 的额外负担。
业务指标暴露这一块,我写了一个 Go 语言的 metrics 库,业务代码里只需要注册几个指标:
// 初始化 qpsMetric := prometheus.NewCounterVec( prometheus.CounterOpts{ Name: "app_requests_total", Help: "Total requests counter", }, []string{"service", "endpoint", "code"}, ) latencyMetric := prometheus.NewHistogramVec( prometheus.HistogramOpts{ Name: "app_request_duration_seconds", Help: "Request latency in seconds", Buckets: []float64{0.05, 0.1, 0.2, 0.5, 1, 2, 5}, }, []string{"service", "endpoint"}, ) prometheus.MustRegister(qpsMetric, latencyMetric) // 请求入口处的埋点 start := time.Now() qpsMetric.WithLabelValues("payment", "/create", "200").Inc() latencyMetric.WithLabelValues("payment", "/create").Observe(time.Since(start).Seconds())这里我强烈建议 histogram 的分桶(Buckets)要根据业务的实际延迟分布来定,而不是照抄默认值。如果你的接口 P99 只有 300ms,那 5 秒、10 秒这种大桶用不上;如果你的接口偶尔会到 3 秒,那 2 秒和 5 秒之间就缺一档,导致分位数计算不准确。我自己的经验是,先跑一周,看下延迟分布,再反向调整 Buckets。
4.2 存储与服务发现:Prometheus 配置要点
Prometheus 的部署用的是 Docker 方式,配置文件prometheus.yml里最核心的就是抓取规则和服务发现。
我的服务发现用的是基于 Consul 的方案,没有用 Kubernetes 的自动发现,因为当时还有一部分服务跑在虚拟机里。Consul 注册的服务会带上env、service、instance这几个标签,Prometheus 通过consul_sd_configs自动发现目标:
scrape_configs: - job_name: 'node' consul_sd_configs: - server: 'consul:8500' tags: - 'node-exporter' relabel_configs: - source_labels: ['__meta_consul_service'] target_label: 'service' - source_labels: ['__meta_consul_node'] target_label: 'instance'这里有一个非常关键的细节——__meta_consul_node这个标签要用主机名替换掉默认的实例名。如果不做这个替换,Prometheus 默认会用<host>:<port>作为 instance 标签,而这个 host 可能是容器的 IP,每次重启就变了,导致历史数据断裂。我印象特别深的是,第一次上线后跑了两周,发现有个节点 CPU 持续高,却发现历史数据"断了一截",追根溯源就是容器 IP 变了,Prometheus 把它当成了一个新实例来处理。
存储方面我保留了默认的本地 TSDB,但设置了--storage.tsdb.retention.time=720h,也就是保留 30 天。在数据量不大的情况下(每秒 2 万序列以内),单机 Prometheus 完全够用。我目前每天的新增序列大概在 8000 条左右,查询速度没有明显瓶颈。
4.3 健康度评分模块:纯 Go 实现的轻量分析服务
brain 模块我写成了一个独立的 Go 服务,订阅 Prometheus 的数据。它通过 PromQL 查询指定时间窗口的指标数据,计算健康度评分,并把结果回写到 Prometheus 的自定义指标里,这样 Grafana 就可以直接画健康度趋势图了。
计算评分的核心逻辑分为三步:
第一步,拉取指标。用 PromQL 查询最近 1 小时的指标,比如avg(rate(node_cpu_seconds_total{mode="idle"}[5m])) by (instance)这种。第二步,对比基线。brain 服务内部缓存了每个指标的 14 天滑动基线值,这批基线会每天凌晨 3 点重算一次。对比时计算偏离度:偏离度 = (当前值 - 基线值) / 基线值。第三步,扣分。按偏离度映射到扣分权重,最后生成各子系统的健康度明细。
评分生成的伪代码大致是这个思路:
func calculateHealthScore(metrics map[string]float64) float64 { score := 100.0 if cpuUsage := metrics["cpu_usage"]; cpuUsage > 85 { score -= 15 // CPU 超限扣 15 分 } if cpWatermark := metrics["conn_pool_watermark"]; cpWatermark > 0.8 { score -= 20 } if p99Dev := metrics["p99_deviation"]; p99Dev > 0.3 { score -= 10 * p99Dev } if score < 0 { score = 0 } return score }这个模块跑在 Kubernetes 集群里,资源占用非常小,日常只有 0.1 核 CPU 和 200MB 内存。因为健康度评分不是高频计算,我设置的是每 30 秒算一次,开销很可控。
4.4 可视化看板与告警:一张图看懂平台状态
可视化用的是 Grafana。我没有堆砌几十个面板,而是专注做了三个核心看板:平台总览、服务链路追踪、资源容量规划。
平台总览是打开 Grafana 默认看到的首页,核心就是一个大的健康度数字和一个雷达图。雷达图的每个维度分别是:API 延迟、错误率、流量健康度、容量水位、依赖健康度。这个雷达图能很直观地看出"当前平台哪一项亮红灯"。
服务链路追踪看板的核心是一张按调用链排序的表格,每一行是一个服务,列包括 P99 延迟、QPS、错误率、依赖健康度、近 15 分钟趋势图。这张表的价值是,任何一项数据异常,都可以很快锁定具体是哪个服务出了问题。
容量规划看板是第一眼能发现"未来风险"的地方。我会把每个连接池、每个存储实例的使用率都画成水位数,超过 70% 的标黄,超过 85% 的标红。这样即便没有达到告警阈值,也能提前看出来哪些资源要扩容了。
告警这块我用了 Alertmanager,但是规则做了很多自定义。我踩过的最大的坑是"告警风暴"——当某个基础设施故障时,关联的所有服务都在告警,值班手机直接被刷爆。后来我做了两个调整:一是加了告警冷却时间,同一个告警规则在 30 分钟内只发送一次;二是做了路由分组,把相同服务维度的告警合并成一条消息,而不是每个指标单独发。
告警规则的设计我总结了一个清晰的原则:告警不是因为"指标超过阈值",而是因为"用户受到影响的可能性很高"。也就是说,CPU 到了 90% 不一定告警,因为如果流量没受影响,这可能只是计算型任务的正常波动;但如果是 P99 突破基线 30% 且用户请求成功率下降,那必须立即告警。
5. 实战复盘:一次真实故障,PLFM_RADAR 是怎么帮我扛住的
5.1 告警触发:健康度评分 63 分,雷达图亮红
有一次星期四下午 2 点 47 分,我手机上收到了 PLFM_RADAR 的告警:平台整体健康度 63 分,其中"订单服务"和"支付服务"两块都是红色。当时我正在开会,看到告警第一反应是拿出手机看 Grafana 上的雷达图。
雷达图上订单服务和支付服务的两条边都是红色的,延迟维度几乎填满,错误率维度也显示偏高。这个展示方式的好处是,我不需要打开十个页面去手动对比,一眼就能知道问题集中在哪个服务上。
我立刻打开了服务链路追踪看板,订单服务的 P99 延迟已经飙到 2.4 秒,比基线高出了 158%;支付服务的 P99 是 1.1 秒,比基线高 65%。看起来像是订单服务下游出问题了。
5.2 根因定位:关联分析把范围缩小到一条 SQL
点击订单服务那行,我展开关联分析详情。correlator 给出的关联项排序表清清楚楚地列出来:
| 关联维度 | 指标变化 | 偏离度 |
|---|---|---|
| MySQL 订单库慢查询数 | 每 5 分钟从 20 涨到 180 | 96% |
| 订单服务 createOrder QPS | 与平时一致,无明显变化 | 5% |
| 订单服务节点 CPU | 无显著变化 | 12% |
| 缓存集群命中率 | 稳定在 97% | 3% |
看到这个结果,基本可以确定问题不在服务本身、不在缓存、不在 CPU,而是指向 MySQL 慢查询。我直接登录数据库查slow_log,看到一条 SQL:
SELECT * FROM order_item WHERE create_time > '2024-06-06 00:00:00' AND status = 1;这条 SQL 的create_time字段没有走索引,在订单表数据量达到 6000 万行之后,全表扫描一次需要将近 6 秒。而它是在两周前的一次需求里新加的,当时测试环境数据量小,没有暴露问题,随着线上数据增长,性能开始急剧恶化。
我从告警到定位出这条 SQL,大约用了 8 分钟。如果按以前的排查方式,可能需要先看网关监控、再看两台服务的日志、对比依赖服务,最快也要 40 分钟,这个时间差在故障处置里面就是真金白银。
5.3 处理动作与事后改进
定位到慢 SQL 之后,处理方案很明确:先加索引止血。我执行了:
ALTER TABLE order_item ADD INDEX idx_create_time_status (create_time, status);因为订单表已经很大,加索引花了大概 3 分钟,期间对线上读写稍有影响,但整体可接受。加完索引后再看 Grafana,订单服务的 P99 延迟在五分钟内回落到 280ms,平台健康度恢复到 92 分。整个过程从告警到恢复,一共 16 分钟。
事后我做了三件事改进系统:
第一,把"慢查询激增"单独做成一个告警规则,不再只依赖健康度评分。慢查询是非常典型的数据库风险信号,值得拥有独立的高优告警通道。第二,在关联分析里增加了"SQL 变更记录"维度——把 CI/CD 发布记录拉进来,这样定位问题时可以直接看到"这个时间段内有谁发过代码"。第三,给订单表加上了容量规划看板的关注标记,如果数据量再翻倍,会提前提示这类单表可能需要进行归档或者分库。
这次应急处理让我对 PLFM_RADAR 的信心大增。它在关键时刻确实做到了"发现早、定位快、信息全",几乎把排查工作量压缩到了一个"看报告"的量级。
6. 常见坑与排查技巧总结
6.1 告警风暴怎么解决
前面提到告警风暴,我再详细说下我的解决办法。我经历过最惨的一次,一个数据库主库宕机,5 分钟内收到了 180 多条告警,十几个服务全部在刷"连接失败"和"延迟暴涨"。手机直接被打到没电,而且真正的根因被淹没在海量告警里,谁都没注意到第一条"MySQL 主库不可达"。
我的解决思路是分级告警体系。把所有告警分成 P0、P1、P2、P3 四个级别:
- P0:平台核心服务不可用、用户请求大面积失败,需要立即响应,电话加短信
- P1:P99 明显超标、错误率突破阈值、慢查询激增,需要 15 分钟内响应,企业微信加短信
- P2:容量水位超过 75%、单实例资源偏高,需要 2 小时内处理,仅企业微信通知
- P3:信息类提醒,比如某条依赖链路的延迟轻微上升,仅记录不通知
同时,Alertmanager 里配置了 inhibit 规则,让"高级别告警"可以抑制"低级别告警"。比如 MySQL 挂了(P0)时,自动抑制所有依赖它的服务的连接超时告警(P1),只保留最顶层的根因告警。效果立竿见影:同样的故障再来一次,告警消息从 180 条降到了 7 条,而且第一条就是根因。
6.2 Prometheus 指标数据不连续怎么办
Prometheus 数据断点是个很隐蔽的坑。一个常见原因就是服务发现标签变化,Prometheus 会把变化后的实例当成新目标,之前的数据暂时看不到了。另一个常见原因是抓取超时。默认的scrape_timeout是 10 秒,如果某个 exporter 响应慢,比如某些大型中间件的 exporter 在数据量大时容易超时,就会出现周期性的抓取失败。
我的解决方法是,在 exporter 端把--web.listen-address单独拆出来,和业务服务分开端口;同时给关键 exporter 设置独立的 job 和较长的超时时间:
scrape_configs: - job_name: 'mysqld' metrics_path: '/metrics' scrape_interval: 30s scrape_timeout: 20s static_configs: - targets: ['mysql-exporter:9104']另外,我还会定期检查 Prometheus 的up指标,如果发现任何 target 的up == 0,立刻报警。这个规则比很多复杂监控都重要,因为它保证了自己这个"雷达"本身是健康的。
6.3 标签基数爆炸,查询越来越慢
Prometheus 查询变慢的常见原因之一就是标签基数值过大。什么叫标签基数?就是某个标签拥有的不同取值数量。比如给业务指标加了一个user_id标签,那基数就是百万级别,Prometheus 的倒排索引会被撑爆,查询时明显卡顿。
PLFM_RADAR 我规定了一套标签规范:任何业务指标只允许使用service、endpoint、code、instance、env这五个维度,绝对禁止把user_id、order_id这类高基数标签加到监控指标里。如果需要按用户分析,那是业务日志系统的事,不应该塞进时间序列数据库。
此外,还要提一个大家容易忽视的坑:使用rate()函数的时候要保证时间窗口和采集周期匹配。比如采集周期是 30 秒,那rate(metric[1m])就只能覆盖两个采样点,计算出来的速率波动会非常大。我一般建议rate的时间窗口至少是采集周期的 4 到 5 倍,比如采集 30 秒一次,就用[2m]或[5m]来平滑。
6.4 时区错乱导致基线漂移
这是我在做基线计算时踩的另一个坑。默认情况下 Grafana 显示的是 UTC 时间,而业务数据的高峰期是北京时间上午 10 点到晚上 10 点。如果基线计算用的是 UTC,就会出现"基线把北京时间下午 3 点的高峰和 UTC 凌晨 3 点的低谷对比"这种错误,导致大量误报。
我修复的方式非常简单:Prometheus 的告警规则表达式和 Grafana 的时区全部设置为Asia/Shanghai,同时基线计算的时间窗口也按本地时间取整。这里需要注意的是,Prometheus 的timestamp()函数默认返回 UNIX 时间戳,不受时区影响,所以纯粹的时序计算没问题,出问题的只是"按天周期对标的基线"逻辑。如果你做基线,一定要把时区统一,否则数据对比就是错位的。
7. 这套雷达后续还能怎么扩展
PLFM_RADAR 目前基本稳定运行了半年,每周处理大概 1.2 亿个指标采样点,告警的准确率我做过一次统计,查了下过去 30 天告警和实际故障的对应关系,有效告警率大约在 82%。也就是说,100 次告警里有 82 次确实对应到真实或潜在的问题,18 次是误报。对于一个自研的轻量系统来说,这个准确率我比较满意。
后续有几个方向是我明确会继续做的。一个是把容量预测做成更智能的模型。现在的容量规划看板只是画了当前水位线,未来我想用线性回归基于过去 30 天的增长率预测未来一个月的资源用量,这样扩容的决策就能从"看趋势猜"变成"有数据算"。另一个方向是把日志系统中的异常日志也纳入关联分析的候选维度。目前还是日志系统和监控系统各管各的,如果能把日志异常事件和指标异常打通,根因定位的速度还能再快一步。
最后再分享一个小技巧。做这类监控系统,不要一开始就贪多求全。很多人搭监控第一步就把所有指标全接进来,上百个图表,看起来很唬人,实际上根本没人看。我建议倒过来——先只接最重要的 20 个指标,服务好一次真实故障,再根据这次故障暴露的盲区补指标。迭代式地做,系统会越来越贴合你业务的真实痛点。PLFM_RADAR 就是这么长出来的,每一层新能力背后都有一个真实的踩坑故事在推动。如果你也在为"监控有了但没用"而头疼,希望能从这套系统的思路里找到适合你的切入点。