ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

从零搭建平台运行状态观测系统:PLFM_RADAR实战解析

从零搭建平台运行状态观测系统:PLFM_RADAR实战解析

1. 为什么我会自己动手做一套 PLFM_RADAR

PLFM_RADAR,名字拆开就是 Platform Radar,平台雷达。这不是什么商业产品,也不是某个大厂开源的项目,而是我自己从零搭起来的一套平台运行状态观测系统。大概半年前,我接手维护两个业务中台和前后端几个核心服务,每天上班第一件事就是轮流登录不同的监控后台,看上板数据、看告警、看日志平台,再手动拼凑出"平台今天到底行不行"这个问题的答案。没错,拼凑,这个词就是痛点所在。

线上监控工具其实不少:有开源的 Prometheus + Grafana 组合,也有商业 APM 工具,还有云厂商自带的可观测性套件。但工具越多,问题越明显——每个工具都在自己的维度里转,A 系统的告警可能只是瞬时抖动,B 系统的错误率持续升高却因为触发阈值没到而被忽略,C 系统的业务量暴跌在技术监控里完全看不到。所有信息都摆在眼前,但没有一个统一的视角能告诉我平台的综合健康度是多少,哪里正在恶化,哪里需要我马上动手处理。

所以 PLFM_RADAR 的定位一开始就很清晰:它不是要替代现有的 Prometheus、日志系统或者 APM,而是在这些底层数据之上做一个聚合、打分、可视化和告警的"雷达层"。形象点说,就是给平台装一个座舱仪表盘,从多个维度看这个"飞机"是平稳巡航还是已经失速。雷达图这个可视化形式是天然契合这种需求的——多维度、一眼看全貌、异常维度很容易突出。

这套东西适合谁来参考呢?如果你手上有几个核心系统需要维护,而你恰恰对现有监控工具的"信息孤岛"状态不满意;如果你想给团队一个更直观的"平台健康度"汇报口径;或者你正在做可观测性平台建设,想找一种轻量级实践参考,那这篇实操记录应该能帮到你。我不打算堆概念,所有内容都是落地细节、踩过的坑,以及当时为什么那样选型。

2. 整体架构与指标设计

PLFM_RADAR 的技术架构相当简单,简单到不需要画架构图你也能想象出来:一条采集链路、一个计算服务、一套存储、一个前端可视化面板,外加一条告警通知通道。真正的复杂度不在架构,而在指标定义和权重设计上。

2.1 架构选型:为什么走轻量级聚合路线

当时摆在我面前的有两条路。第一条是在现有 Prometheus 体系上做二次开发,用 PromQL 把数据拉出来再写告警规则。第二条是自建一个轻量的聚合服务,通过 API 从各个监控源拉数据,自己保存指标快照,自己算健康分。我选了第二条,理由有三个。

第一,现有监控源的种类不统一。Prometheus 能覆盖技术指标,但业务指标(比如订单量、支付成功率)散落在业务库和日志里,Prometheus 抓不到。我需要的是一个能对接多种数据来源的中间层,而不是再往 Prometheus 生态里塞插件。

第二,雷达图的本质是横向对比多个维度,并且需要历史回溯。Prometheus 虽然也能查历史数据,但要把"可用性、响应时间、错误率、资源水位、业务量、发布稳定性"这六类指标统一成一种评分语言,再保存每日快照,直接在 PromQL 层面做会很别扭。我在自己的服务里定义一套统一的指标模型,集中计算和存储,后续扩展维度也方便。

第三,部署和维护成本要可控。自建的聚合服务就是几个 Python 文件、一个 SQLite/PostgreSQL、一个 Nginx 托管静态页面,整条链路不超过三个进程。对比搭一套完整 Prometheus + Grafana + AlertManager 集群,维护成本低了一个量级。

架构上我把整个系统拆成四块:

  • 采集器(Collector):按固定周期调用各系统 API 或直接查数据库,拿到原始指标
  • 评分引擎(Scorer):把原始指标映射到 0-100 分,按维度权重聚合出综合健康分
  • 存储与 API:保存评分快照和原始指标,对外提供查询接口
  • 前端看板:雷达图 + 趋势图 + 告警列表,按需刷新

每个模块都做独立进程,中间用消息或轮询串起来,哪怕某一个采集源挂了,其他模块还能正常运转。这是我从线上事故里学到的第一条经验:不要让一个采集源的故障拖垮整个看板。

2.2 指标拆解:六个维度的雷达模型

雷达图至少需要三个维度才能形成几何特征,维度太少就成了线段,太多则图面混乱。我最终选了六个维度,既覆盖了技术层面,也覆盖了业务层面,刚好能让雷达图的轮廓清晰可读。这六个维度是:

  1. 服务可用性:核心服务在过去一段时间的存活率,用探活结果折算
  2. 响应性能:核心接口的平均响应时间与 P95 响应时间,对照基线折算
  3. 错误率:技术错误(5xx + 服务异常)和业务失败的加权比重
  4. 资源水位:CPU、内存、磁盘、带宽的剩余空间,取最紧张项作为代表
  5. 业务健康度:核心业务量(订单量、登录量、支付量)与历史同期基线的偏差率
  6. 发布稳定性:最近一个交易周期内(比如一小时内)发布操作对错误率、响应时间的影响幅度

指标选型上有几个很关键的思考。资源水位为什么取"最紧张项"而不是平均值?因为平均水位很容易掩盖木桶效应——四台机器三台空闲一台跑满,平均值看着很安全,但跑满那台恰恰可能承载了关键流量。雷达图的丑处在于每个维度都诚实地暴露短板,所以我刻意让这个维度"报忧不报喜"。

业务健康度是最容易被技术团队忽略的维度。我在做监控时发现一个典型场景:凌晨三点支付成功率从 99.9% 滑到 95%,技术指标完全正常,没有任何告警,但业务损失已经发生了。把业务量偏差率纳入雷达图之后,这类异常至少会在早上大家打开看板时扑面而来。我对趋势类指标的计算采用了"同比环比加权"的方式,既参考前一个小时的环比,也参考上周同时间的同比,避免把正常的业务周期性波动误判成异常。

2.3 权重与评分算法

六个维度不是平权的。对于不同业务阶段,权重应该动态调整。我做了一个简单的可配置权重体系,初始值如下:

维度默认权重说明
服务可用性25%不可用是最高优先级,直接决定平台生死
响应性能20%用户体验的核心支撑
错误率20%错误率的波动往往指向代码或依赖问题
资源水位15%资源问题通常有提前量,权重适中
业务健康度15%需要排除纯营销活动导致的大幅波动
发布稳定性5%发布频次低,平时影响小,出事时影响极大

权重可以在配置文件中调整,比如大促前把业务健康度权重从 15% 提到 25%,把资源水位的权重也提到 20%,因为大促期间资源是最容易爆的。评分的计算方式我用了一个简单但有效的分段函数,以"响应性能"维度为例:

  • 如果 P95 响应时间在基线 ±10% 以内,给 100 分
  • 如果在基线的 110%-150% 之间,线性衰减到 70 分
  • 如果在基线的 150%-200% 之间,衰减到 30 分
  • 超过基线 200%,给 0 分

这种分段衰减比纯粹的线性公式更符合直觉:响应时间从 200ms 涨到 210ms 不叫问题,从 500ms 涨到 800ms 才是需要立刻干预的。每个维度都有自己的分段参数,对"多少算异常"的定义完全通过配置表达,不用改代码。最终综合健康分 = 各维度得分 × 权重之和。这个分数的意义不在于绝对值,而在于它的变化趋势——连续三天 60 分以下,说明有系统性问题正在积聚,哪怕此时没有任何一条告警被触发。

3. 核心实现:数据采集与雷达图渲染

架构定了,接下来就是动手实现。我不打算贴完整源码,但会把每个环节的关键实现思路和取舍讲清楚,尤其是那些容易被"看着简单、做起来翻车"的地方。

3.1 数据采集层的两个核心问题

采集层最大的坑不是"怎么采集",而是"采集对源系统造成的影响"。我曾经用 Python requests 每十秒扫一遍所有服务的健康检查接口,结果有个老系统的健康检查接口本身写得稀烂,每次被调用都会触发一次耗时的数据库查询,直接把我这个监控工具变成了事故制造机。后来我做了三个改进:

第一,探活频率分级。核心链路(登录、下单、支付接口)每 15 秒探一次,次要服务 30 秒,批量任务、后台服务等低频系统 60 秒。这一步把源系统压力降了一半还多。

第二,探活方式从"跑业务接口"降级为"查状态接口"。业务接口的耗时和成功率,我不主动去调,而是从网关日志和 APM 里拉数据,主动调用只做最简单的心跳检查。比如 TCP 能否建连、健康检查接口是否在数百毫秒内返回。很多团队做监控失败,就是因为把自己变成了系统之间的额外流量源。

第三,采集任务全部异步化。每个采集源跑在独立线程池里,单个采集源超时不影响其他源。我用的是 5 秒超时 + 最多两次重试 + 熔断策略:同一个源连续失败 5 次,就暂停采集 2 分钟,等源系统自己恢复。这套机制上线之后,我的监控进程再也没成为线上故障的一份子。

对于业务指标,我直接从业务数据库的接口取值。这里涉及一个判断:是查数据库还是查业务已暴露的统计接口?查到 DB 的问题在于你直接接触业务表结构,业务一旦调整表字段,监控就得跟着改;查统计接口的问题在于接口返回结构可能不稳定。我最终采用了折中方案——优先复用已有统计接口,缓存 30 秒,只有确实没有统计接口的业务数据才允许直连 DB,并且 SQL 必须限定在单表 count 或 sum 级别,严禁 join 大表。别小看这个约束,监控系统的数据库查询往往是压垮业务库的最后一根稻草。

3.2 健康评分的持久化设计

评分引擎跑出来的健康分如果只展示不存储,那就跟白算一样。我用 PostgreSQL 存历史快照,表结构极其简单:

业务维度得分表(dimension_score)字段:日期、采集时刻、维度名称、原始指标值、维度得分、权重 综合健康分表(overall_score)字段:日期、采集时刻、综合健康分、当时生效的权重配置版本

为什么要把权重配置版本也存下来?因为权重一变,历史综合分数就不可比了。比如我大促前把业务健康度权重从 15% 提到 25%,如果没有记录版本,一个月后回看趋势图会觉得平台健康度骤然下降,实际只是计算口径变了。这是我在回看历史数据时踩过的深刻教训,现在每个分数快照都带着权重版本号,趋势对比永远只在同口径下进行。

存储粒度上,综合健康分每分钟存一条,维度得分每五分钟存一条,原始指标值只保留最近 7 天(落一份到 CSV 做离线归档)。这样数据库体积增长可控,一年积累下来大概也就几百 MB,完全不用上时序数据库那套厚重方案。很多人一听说做监控就自动联想到时序数据库,但在这种规模下,一张设计得当的关系表比 TiDB、InfluxDB 都省心。

3.3 雷达图可视化方案的两次选择

前端可视化,第一批我用了开源的 ECharts,因为雷达图是它的标配能力,几乎没有学习成本。ECharts 雷达图的核心是 series 类型为 radar,每个维度在配置里定义 max 值、name,然后传进去的就是六个维度的得分数组。它自带渐变填充区域,长得就非常好看。但我随后发现两个实际问题。

第一个问题是响应式。团队同事喜欢在大屏上常驻展示雷达图和趋势图,而 Grafana 一类的方案虽然原生支持响应式,但对自定义雷达图的支持并不流畅。ECharts 则需要自己在 resize 事件里手动调用 chart.resize(),我在前端代码里写了一个监听 window resize 的 HOC,把这个问题解决了,但这反映出一个更深的问题:如果核心诉求是"一眼看全貌",大屏就必须是首等公民,不能只在电脑小窗口里好看。

第二个问题是单点维度得分点击联动。我想在雷达图旁边挂一张表格,鼠标悬停在雷达图的某个维度顶点时,表格里就显示该维度的原始指标和未打分前的具体数值。ECharts 有 dispatchAction 事件,但要在雷达图和旁边组件之间打通事件链路,还是得自己拼。我在 JS 里加了一个简单的事件总线,雷达图 hover 时发布维度名,表格组件订阅事件刷新数据。说实话这块代码不算优雅,但它让我理解了为什么商业可观测性平台喜欢自己画可视化——通用图表库的交互能力边界是固定的,你要做深度联动就只能自己来。

最终前端看板的构成是三块区域:左上角大号雷达图(六边形,直观展示当前健康轮廓),左下角六个维度的原始指标卡片(显示最新数值和环比变化),右侧是健康分趋势曲线(按小时聚合)和最近告警列表。整个页面用 Vue 3 + ECharts 写的,打包后体积不到 300KB,放在 Nginx 里直接托管,不需要任何后端模板渲染。

4. 告警规则与通知链路

有了打分和看板,PLFM_RADAR 只完成了一半。另一半是告警——而且告警要有策略,不是所有问题都立刻喊出来。没有策略的告警系统,运行三个月后就会变成"狼来了"故事的案例,人人静音,形同虚设。

4.1 告警规则设计的两层模型

我把告警分成两层:维度告警和综合告警。

维度告警针对单个维度的异常,规则相对简单。比如可用性低于 95% 触发 Warning,低于 90% 触发 Critical;错误率高于 2% 触发 Warning,高于 5% 触发 Critical;P95 超过基线 150% 触发 Warning。这些阈值直接配在 YAML 文件里,每个维度可以单独配置。

综合告警则相对"聪明"一些,针对的是"所有维度都还健康,但组合起来不对劲"的情况。比如六个维度的分数全在 75 分以上,可是综合健康分因为某个低权重维度的拖累掉到了 70 分以下。按单维度规则,没有任何告警会触发,但平台的用户体验可能已经开始劣化。我设计的综合告警规则包含三种场景:

  • 综合健康分连续 10 分钟低于 60 分
  • 任意三个维度同时低于 70 分(指向系统性故障,而非单点问题)
  • 综合健康分五分钟内下降超过 25 分(陡降场景,比如从 80 分直接掉到 55 分)

第三类"陡降"告警是我最看重的。我见过太多告警策略只盯绝对阈值,结果就是:错误率从 0.1% 涨到 3% 的过程是逐步的,等到突破 2% 阈值触发告警时,线上其实已经出了问题快一小时。而陡降检测规则能在分数快速变化的早期阶段就发出信号,虽然当时可能还没碰到任何绝对阈值,但这种"变化率"信号恰恰最接近故障的真实形态。

另外,告警必须带上前后三分钟的指标上下文。否则收到一条"服务可用性低于 90%"的告警,你还得登录系统自己查这段时间到底发生了什么,那告警的价值就大打折扣了。我的告警消息模板是:告警维度 + 当前得分/原始值 + 变化趋势(上升/下降)+ 关联的原始指标快照 + 最近一次发布时间。这条模板让值班同事在面对告警时,能在一分钟内形成初步判断,而不是到处翻系统。

4.2 通知渠道与告警去重

通知链路我接了两条:企业微信机器人推送和邮件。企业微信机器人用于紧急告警(Critical 级别),邮件用于 Warning 级别和每日健康报告。渠道选择完全是基于团队现状:值班同学手机上一定有企业微信,邮件只是留档备查。

告警风暴是另一个必须处理的现实问题。单个维度连续五分钟异常,如果每一分钟发一条告警,值班手机会被刷屏,大家反而会忽略关键信息。我的做法是引入间隔聚合:同一维度的同一级别告警,在 30 分钟窗口内最多推送 2 条;第一条在触发时立即推,第二条在窗口内如果状态恢复则推"已恢复",如果窗口结束时仍未恢复则推"持续中"。换句话说,告警频率的上限是每小时 4 条,其中两条还是状态更新。

还有一个细节:不同的告警级别要对应不同的值班响应时限。Critical 级的告警,推送后 15 分钟无人确认,会自动升级到电话级别——当然电话这块我还没有完全自动化,目前是脚本推送一条消息到更大的群,本质上靠人肉盯群。但即使是人工升级,也比没有升级机制强。

5. 实操部署与调参记录

下面这部分是纯实操记录,讲部署步骤和调参过程。如果你要复现 PLFM_RADAR,可以直接按这份清单操作。

5.1 四步完成部署

整个系统的部署我控制在了一个小时以内。用 docker-compose 编排三个容器:采集与评分服务(Python)、PostgreSQL、Nginx(托管前端静态文件和提供 API 代理)。

第一步,初始化数据库。我准备了一个 schema.sql,包含维度得分表、综合健康分表、告警记录表三张表。建表语句最简单的写法就是直接跑一个 psql 命令。需要注意的一点是给时间字段建索引,因为看板默认会按最近 24 小时做趋势查询,没有索引的 PostgreSQL 在数据量积累到几十万行后,查询会从毫秒级退化到秒级。

第二步,配置采集源。编辑 config.yaml,每个采集源是一个源对象,包含类型(http_probe / db_query / gateway_metric)、目标地址、采集频率、超时时间、认证凭据。配置文件单独放,不写死在代码里,这样加一个采集源不用重新构建镜像。这一步是我吃过亏才形成的习惯——最早我把采集目标硬编码在 Python 代码里,每次加服务都要改代码重新部署,后来把配置外置之后,新增采集源只需要编辑 YAML 并调用一次热加载接口,省了很多事。

第三步,启动评分引擎。评分引擎是一个常驻进程,每隔一分钟加载最新一轮的原始指标,计算维度得分和综合分,写入数据库。它的状态页暴露在本地端口,可以看到每个采集源最近一次采集成功时间、耗时和返回的指标数。查看状态页是排查时我做的第一件事——先确认数据质量,再讨论告警逻辑。

第四步,部署前端。把打好的静态文件放进 Nginx 容器,然后代理 API 请求到评分服务。Nginx 里有一处必须配置:对 /api 前缀开启 proxy_buffering off,否则首字节延迟会非常高,看板和 API 的交互体验会变差。这个参数属于那种文档不显眼、但只要在大流量部署下就必然踩到的坑。

5.2 调参过程中的两次关键调整

参数调优不是一次性完成的,我上线两个月里做了两次大调整。

第一次调整是关于基线时间窗。最初我用"前 24 小时的平均值作为基线",结果发现业务周期性的早晚高峰和平日低谷,导致每天上午 10 点的"P95 高于基线 150%"告警成了固定节目。后来我把基线切到"上周同一天同时段的数据",并且加了周末和节假日的日历标识。一个线性的时间窗口代替不了业务自身的周期性规律,这个问题在监控领域特别经典,几乎所有做告警的人都逃不掉。

第二次调整是关于雷达图维度得分的抖动抑制。某个维度如果原始值在一个阈值边界抖来抖去,得分会在 70 分和 90 分之间反复横跳,看起来像心电图。我给评分引擎加了一个 5% 滞回区间:只有超过上边界才降级,只有低于下边界才恢复。比如错误率触发 Warning 的阈值是 2%,那么只有当错误率升到 2% 以上才把得分降到 70 以下,但当错误率回落到 1.9% 时得分还在低位,必须等回落到 1.5% 以下才恢复高分。这个滞回机制在工业控制里叫迟滞,用在监控评分里效果一样好——它消灭了约 80% 的抖动性告警。

6. 常见问题与排查实录

上线这半年,PLFM_RADAR 给我的团队省了不少事,但它自身也出过不少幺蛾子。我把印象最深的几个问题整理成速查表,每个问题都是实际发生过的,不是凑数。

问题现象根因排查方向最终方案
雷达图某个维度长期满分该维度没有配置基线或采集数据为空,评分成默认值查看维度得分的日志,检查原始指标是否在 0 或空增加"无数据即低分"策略,采集不到数据视为该维度异常
告警发送延迟 10 分钟以上采集线程池线程数不足,部分源排队等待过久查看采集耗时统计和队列长度线程池从 6 增到 12,超时时间从 5 秒调到 8 秒
趋势图在某时刻断点PostgreSQL 连接池被业务查询阻塞查看慢查询日志,定位到某个 join 大表的 SQL完全禁止业务监控 SQL 中的 join,强制单表查询
健康分突然掉到 0 但六项得分都正常权重配置被意外修改,版本号为 0 的脏数据检查权重版本记录和配置文件的校验逻辑增加启动时的配置校验,禁止权重列 sum 不等于 100
大屏雷达图第一次加载白屏浏览器版本过低,ECharts 新版不支持该内核查看控制台是否报错 echarts undefined锁定 ECharts 版本,改用基础渲染模式

这里有两个问题我想展开说说,因为它们的教训价值最高。

健康分突然掉到 0 那次,花了将近一小时才定位到根因。当时我看板显示综合健康分是 0,但六个维度的得分全是 100 或者 90 多,这就非常诡异——加权平均怎么都不可能是 0。后来查代码才发现,评分引擎在读取权重配置时,如果配置文件中权重值全部合法但 sum 不等于 100,会按"归一化失败"把综合分直接置为 0。这本来是一个安全兜底,但因为我在测试环境手改过一次 YAML 忘记复原,结果生产环境重载时把这个脏配置拉了进来。事后我在评分引擎里加了一条硬校验:配置加载即使报错,也要保留上一次生效的配置副本继续运行,绝不因为配置异常就让监控系统自己先躺下。

告警延迟的问题则暴露了一个监控系统自身的监控盲区:我当时没有任何指标告诉我说采集线程池已经满了。加了一行指标"采集队列中待执行任务数",超过阈值就在看板右上角亮一个"采集堆积"红点。这也是可观测性领域一个很有意思的自我指涉:一个监控系统必须知道自己是不是健康的,否则它给你提供的所有健康判断都值得怀疑。

7. 扩展方向与我的体会

PLFM_RADAR 目前的版本做了个减法——它只负责观测,不做告警工单闭环。后续如果要扩展,我觉得有三个方向最值得做:一是接入容量规划预测,把资源水位的长期趋势外推,提前一周评估是否需要扩缩容;二是引入更细粒度的用户侧视角,把不同渠道、不同用户端的体验差异也作为独立维度;三是把评分算法从固定权重演进为动态权重,根据业务实时状态自动调整各维度的关注程度。这三个方向无论哪个做深,都能让这套系统从"看得见问题"进化到"预判问题"。

最后聊聊我个人的使用体会。监控工具最怕的不是功能少,而是噪音多。PLFM_RADAR 在设计上的一切取舍,最终都指向同一个目标:减少无效信息,放大真问题。雷达图之所以作为核心展示形式,是因为它把多维度信息压缩成了一帧画面;告警之所以反复调参,是因为团队信任比技术指标更宝贵——只有每次都准、每次都解决真问题的告警,才会让人认真对待。如果你的监控体系也正在经历"工具很多但大家不看"的尴尬期,我的建议是别急着上更复杂的平台,先把最关键的五六个维度定义清楚,画一张能真实反映平台状态的雷达图,用它形成一个大家每天都会瞄一眼的统一视图,再一步步把告警和联动加进去。这套思路听起来朴素,但在我这边,它真的把团队对线上状态的感知从"抢救式"变成了"巡航式",这就已经是最大的价值了。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进