PLFM_RADAR 这个项目名拆开看就是 Platform Radar平台雷达。我最早动手做它是因为当时手上管着的业务平台有十几个核心指标日活、转化率、接口耗时、错误率、客诉量、营收波动……这些数据散落在不同的报表、看板、脚本和告警规则里平时各看各的都正常可一旦平台整体出问题往往要等业务方跑过来说最近好像不太对劲我们才开始一个个系统去翻。那种事后才知道的被动感太难受了。所以我决定做一个能把平台全局健康度一屏看到的系统于是 PLFM_RADAR 就诞生了。这套系统适合谁如果你也在维护一个业务平台被指标很多但看不清整体困扰或者想给团队做一个轻量但完整的监控可视化闭环那这篇文章里的设计思路、指标算法和代码实现你基本可以照着抄。我尽量把每一步背后的为什么也讲清楚避免你踩我踩过的坑。1. PLFM_RADAR 到底在做什么项目定位与整体设计思路1.1 名字拆解为什么叫平台雷达先说名字PLFM_RADAR 里 PLFM 是 Platform 的缩写RADAR 取的是雷达的工作原理发射波束、接收回波、识别目标。业务平台的监控本质上也是同一件事——我们持续向各个业务模块发出探测请求回收各类指标数据然后判断哪些模块出现了异常这就是平台的回波。这个命名不只是为了好听它逼着我用雷达的视角重新整理了监控逻辑。传统监控看板往往是一台机器一张图一个接口一张折线问题是被切碎的你看到接口 A 的 P95 涨了但不知道这是不是整体流量异常带来的连锁反应。雷达系统的核心差异在于它把多个维度的信号汇聚到同一坐标系里你看到的不是某一个点的变化而是整个平台形状的变化。另外雷达还有一层隐含语义它是主动探测不是被动等待。所以 PLFM_RADAR 在架构上就要求所有指标都按固定周期主动采集而不是依赖各业务方主动上报这保证了数据的及时性和口径一致性。1.2 架构选型轻量但完整避免过度设计设计之初我列了三个硬约束第一两周内要能上线第一版第二团队里只有三个人能投入且没人专门搞运维第三后续指标维度可能会从六个扩展到十几个。因此我直接排除了微服务、大数据套件这类重方案选了一条务实的技术栈后端服务Python FastAPI负责采集调度、指标计算、告警判定和 API 输出数据存储MySQL 存历史指标快照Redis 做实时缓存因为雷达接口需要毫秒级响应任务调度APScheduler替代引入 Celery 的重型方案前端展示一个轻量的 HTML ECharts 页面用雷达图做主视图告警通知企业微信 Webhook 邮件兜底。这套组合在单机部署下就能跑得很稳。你可能会问为什么不用现成的 Prometheus Grafana我试过它对基础设施监控确实很香但我们当时更缺的是一个面向业务健康度的语义层——P95 耗时是技术指标而性能健康度 82 分这个结论需要结合基线、目标、权重计算得出这类逻辑放在自己的服务里更灵活。工具选型要看你的核心诉求是基础设施监控还是业务健康评估两者并不冲突但别混在一个系统里做。1.3 雷达图的定义为什么用它做核心可视化雷达图Radar Chart / Spider Chart经常被人诟病不适合精确比较但在这个场景里它的优点恰恰是别的图表替代不了的多维数据可以在同一坐标系里同时呈现整体形态一眼可见凹进去的角就是短板。举个例子某天平台的日活、营收都在涨看起来一片向好但用户反馈维度的得分从 75 掉到了 50雷达图上就会非常刺眼地凹进去一块。你用折线图堆十个指标很难在十秒内意识到用户体验正在恶化但雷达图不会漏掉这一点。它适合的是形态识别和异常感知不追求精确数量对比这正好匹配平台监控的目标。当然雷达图也有缺陷——当维度超过八个图形会变得难以辨认。所以在指标体系设计时我给自己定了一个纪律核心雷达图的维度控制在六个更多细分指标放到二级页面用表格和折线图承载。2. 指标体系的搭建雷达要有波束平台要有维度2.1 六个核心维度怎么选雷达图的每个轴代表一个维度选维度时我遵循了三个原则每个维度都必须能反映用户可感知的结果每个维度都必须能采集到可靠数据维度之间尽量低耦合。基于这三点最终敲定了六个维度维度原始指标采集来源采集周期健康基线说明活跃度DAU、MAU业务库用户行为表小时级反映平台热度但单看活跃度不够要防止虚假繁荣性能体验核心接口 P95 耗时接口监控中间件分钟级技术指标直接决定用户体验每个接口单独算稳定性接口错误率、服务可用性网关日志聚合分钟级错误率超过 1% 就需要关注超过 3% 视为严重转化效率核心漏斗转化率埋点数据小时级反映核心业务链路健康度用户反馈客诉率、NPS 均分客服系统、评价表天级用户声音的量化滞后但很关键营收健康度日营收、退款率订单库小时级营收异常波动往往意味着业务链路出问题选维度时要克制不要一口气上十个。我当时也想把新增注册拉新成本等一堆指标塞进去后来发现维度越多每个维度的得分解释难度越大反而失去了雷达图一眼定位短板的优势。2.2 归一化与打分计算量纲不同的指标如何进入同一张雷达图六个维度的原始指标量纲完全不同P95 耗时看毫秒DAU 是几万人营收是几十万元。要让它们画在同一张雷达图上必须做归一化。我试过两种常见方案最后都没有直接采用。第一种是 min-max 归一化把当前所有数据的最小值映射成 0、最大值映射成 100这种做法的问题在于它只反映相对大小做出来雷达图几乎永远是满的因为最大值的那个维度恒为 100你看不出离目标还差多少。第二种是 z-score 标准化但它会出现负值在雷达图上语义不直观。最终我采用的是基线区间映射法每个维度的打分公式是def health_score(actual, warn_line, target_value): actual: 当前实际值 warn_line: 预警线低于这个值视为不健康 target_value: 目标线达到这个值视为健康 返回 0-100 的分数 if actual warn_line: return max(0.0, 20 * actual / warn_line) if actual target_value: return 100.0 return 80 20 * (actual - warn_line) / (target_value - warn_line)这个公式的思路很简单把低于预警线的情况映射到 0-20 分区间预警到目标之间映射到 20-100达到目标后封顶 100。为什么不是简单线性从 0 到 100因为低于预警线时业务通常已经处于异常状态分数之间需要拉开差距避免所有维度都是四十几分导致无法排序。另外不同指标的方向也不一样错误率是越低越好活跃度是越高越好所以采集层需要给每个指标一个 direction 字段计算时做方向转换。这里有一个关键细节阈值参数不能拍脑袋定。性能维度的 P95 目标值要根据历史 P95 的 P90 分位数来定比如过去 90 天里 80% 的时间 P95 都小于 300ms那目标值就定 300ms预警线再放宽到 500ms。用数据推导阈值比感觉 500ms 比较合理靠谱得多。2.3 综合评分、权重与多级告警判定雷达图本身不需要权重但为了让告警系统能给出一个单一结论我还是计算了综合健康度得分。权重用简单的专家打分法我给了性能体验和稳定性最高权重各 0.25活跃度和营收健康度次之各 0.2转化效率和用户反馈各 0.15。综合得分的计算score sum(weight[dim] * raw_score[dim] for dim in dims)告警判定不只看综合得分我把它拆成了三级黄色告警任意单一维度得分低于 60 分且持续两个采集周期橙色告警综合得分低于 75 分或两个维度同时低于 60 分红色告警综合得分低于 60 分或稳定性维度低于 30 分。为什么稳定性维度要单独拉到红色告警因为这是一个强因果关系——稳定性崩了其他所有指标大概率都会跟着崩这时候应该优先触达值班人而不是等综合得分慢慢跌下去。3. 核心代码与实操过程从数据采集到雷达图展示3.1 数据采集层适配器和定时调度PLFM_RADAR 的数据源很杂活跃度来自业务库性能指标来自中间件接口客诉数据来自客服系统外包的 API。我采用了一个通用的采集器框架每个数据源就是一个 Adapter统一返回指标名 数值 时间戳结构# collector/base.py 核心数据结构 dataclass class MetricSample: metric_name: str value: float timestamp: int dimension: str # 对应雷达图维度如 performance direction: str # up 表示越高越好down 表示越低越好以性能探测为例我用的是一个简单的主动探测 被动采集双通道。主动探测是每两分钟请求一次核心接口记录耗时被动采集是从网关日志中间件拉取最近一分钟的错误计数和耗时分位数。代码结构如下# collector/performance_adapter.py import requests import time def collect_perf_metrics(): samples [] # 主动探测三个核心接口 for api in [/api/v1/home, /api/v1/search, /api/v1/order]: start time.perf_counter() try: requests.get(https://platform.example.com api, timeout2) cost_ms (time.perf_counter() - start) * 1000 except Exception: cost_ms 5000 # 超时按 5000ms 处理 samples.append(MetricSample( metric_namefapi_p95_{api}, valuecost_ms, timestampint(time.time()), dimensionperformance, directiondown )) # 被动采集逻辑略从网关日志接口拉取最近1分钟的 P95、错误率 return samples这里特别强调一下超时处理必须把请求异常转换成明确的数值样本而不是跳过。否则稳定性出问题时采集器往往会因为拿不到数据而漏报雷达图上那一块反而是满的这是非常反直觉的坑。调度用 APScheduler每两分钟跑一轮采集每一轮结束后把结果写入 Redis 缓存并异步写 MySQL 历史表# scheduler.py from apscheduler.schedulers.blocking import BlockingScheduler def job(): all_samples [] for adapter in adapters: all_samples.extend(adapter.collect()) store_to_redis(all_samples) store_to_mysql(all_samples) scheduler BlockingScheduler() scheduler.add_job(job, interval, minutes2) scheduler.start()注意采集任务要设置超时和失败重试我设置成单轮总耗时超过 60 秒就放弃本轮标记数据缺失。宁可缺失也不能阻塞下一轮。3.2 指标计算与雷达数据接口核心计算服务负责把原始指标转成雷达图的六维数据并返回给前端。这里我专门写了一个 evaluator# evaluator.py def evaluate_dimension(samples, baseline_config): baseline_config 里存着每个维度的 warn_line、target_value、direction # 聚合同一维度同一指标取最近值跨指标按子权重合并 actual_dict aggregate(samples) scores {} details {} for dim, cfg in baseline_config.items(): actual actual_dict.get(dim) if actual is None: scores[dim] None # 数据缺失不参与评分 continue score health_score(actual, cfg[warn], cfg[target]) scores[dim] round(score, 1) details[dim] {actual: actual, warn: cfg[warn], target: cfg[target]} return scores, details注意数据缺失的情况我规定某个维度如果连续两轮拿不到数据就在返回结果中标记missing: true前端雷达图要把这个轴画成淡灰色并给出数据缺失的提示而不是当成 0 分或者满分。把缺失和 0 分混为一谈是我在早期版本里犯过的错误会导致告警系统产生大量误报。接口部分用 FastAPI 暴露两个端点# api.py from fastapi import FastAPI app FastAPI() app.get(/api/radar/summary) def radar_summary(): data redis_cache.get(radar:summary) if not data: raise HTTPException(status_code503, detaildata not ready) return data app.get(/api/radar/history) def radar_history(days: int 7): rows db.query(SELECT day, dim, score FROM radar_daily WHERE day DATE_SUB(NOW(), INTERVAL ? DAY), [days]) return rowsRedis 缓存里存的是最近一轮的完整评分数据这样即使 MySQL 查询压力大看板页面也能保持流畅。目前这套接口在公司内网环境下单机扛几百 QPS 毫无压力。3.3 前端雷达图渲染ECharts 配置展示层我用了一个单页 HTML 加上 ECharts没有引入前端框架因为需求真的很简单一个雷达图、一个历史趋势折线图、一个告警列表。ECharts 的雷达配置如下// radar_chart.js const dims [ { name: 活跃度, max: 100 }, { name: 性能体验, max: 100 }, { name: 稳定性, max: 100 }, { name: 转化效率, max: 100 }, { name: 用户反馈, max: 100 }, { name: 营收健康度, max: 100 } ]; const option { radar: { indicator: dims, radius: 65%, axisName: { color: #333, fontSize: 14 }, splitArea: { areaStyle: { color: [rgba(30,100,255,0.02), rgba(30,100,255,0.04)] } } }, series: [{ type: radar, data: [{ value: [82, 74, 95, 68, 60, 80], name: 当前平台健康度, areaStyle: { color: rgba(0, 150, 255, 0.3) } }] }] }; myChart.setOption(option);渲染后那个凹进去的角比如上面数据里的用户反馈 60 分就是团队当天的头号处理对象。我给前端加了一个交互点击雷达图的某个顶点会跳转到该维度的详情页展示这个指标最近十四天的折线图和告警历史方便追因。另一点值得提醒雷达图的轴最大值统一设成 100 就好不需要按维度动态调整。因为我们的分数天然是 0-100动态轴会让小的波动在视觉上被夸张放大反而误导判断。3.4 告警模块雷达图之外的第二只眼睛雷达图解决的是人主动去看的问题但人不可能 24 小时盯着屏幕所以告警模块必须自己推送。我实现了一个带抑制机制的告警器# alerting.py import time from collections import defaultdict last_alert_time defaultdict(float) ALERT_INTERVAL 300 # 同一告警项目 5 分钟内只发一次 def check_and_alert(scores, details): alerts [] now time.time() for dim, score in scores.items(): if score is None: continue if score 30: level RED elif score 60: level YELLOW else: continue key f{dim}:{level} if now - last_alert_time[key] ALERT_INTERVAL: alerts.append({dim: dim, score: score, level: level, detail: details[dim]}) last_alert_time[key] now if alerts: send_webhook(alerts) # 企业微信/钉钉 Webhook send_email(summary(alerts), recipients)抑制机制非常关键。没有它你的告警群会在一次故障里刷几百条消息真正的重点反而被淹没。5 分钟的抑制窗口是一个比较平衡的值既不会漏报也不会轰炸。另外我把告警里附加了一个信息熵字段用来表示当前各维度得分的离散程度。如果所有维度得分接近熵低说明是整体性问题如果只有一两个维度低熵高说明是局部问题。这个字段帮我们在告警通知里直接显示出局部异常还是全局异常值班同学的操作路径会完全不一样。4. 踩坑记录与常见问题速查4.1 高频问题与排查实录系统上线至今我遇到过不少问题挑五个最有代表性的放在下面方便你排查时快速对照。问题现象根因解决方式某个维度分数突然掉到 0采集脚本报错抛异常样本没写入后端却把 None 当成 0 分数据缺失必须显示为 missing不能参与评分采集器加错误率监控雷达图几乎所有维度都是 90 多分长期不变归一化用的 min-max最大值恒被映射成 100雷达图失去辨识度切换为基线区间映射法用业务基线和目标线替代数据最大最小值告警群每天被刷屏每次检查都发告警没有抑制机制阈值过窄毛刺也能触发增加 5 分钟抑制窗口阈值改成两个周期持续超标才触发页面显示的雷达图数据和数据库对不上前端拉到了 Redis 缓存而缓存更新失败后是旧数据写入 Redis 时增加版本号前端比对版本不一致则提示刷新某指标在凌晨异常但白天正常告警规则很难写平台业务有明显波峰波谷固定阈值不适用按小时维度拆分基线比如凌晨时段错误率目标放宽到白天的一倍这里面我要重点展开一下告警刷屏的排查过程。有一次凌晨三点稳定性维度跌破了 30按规则触发红色告警结果因为接口抖动恢复了又下跌下跌又恢复一小时内发了将近四十条消息。值班同事直接在群里说再这样我就退群了。那天我意识到告警系统如果不可信、不克制最后的结果就是大家把通知静音真正的事故反而无人响应。所以后来我不光加了抑制窗口还加了连续 N 个周期都异常才升级告警的逻辑宁可晚几分钟也不乱喊。4.2 调优经验与实用建议第一个建议阈值和基线一定要跟随业务周期迭代。我最初使用固定阈值后来发现平台的日活天然有周末高、周二低的规律固定阈值导致周二经常误报。后面我把基线拆成了工作日基线和周末基线准确率立刻上去了。第二个建议先用六个维度跑通完整闭环再考虑扩展。中途我也曾试图把十几个指标都塞进雷达图结果图变成了一团毛线球根本没法看。后来把维度拆成了两级一级雷达图永远保持六个核心维度二级页面用表格和折线图呈现细分指标。这个取舍很重要雷达图的表达力是有限的越贪心越难用。第三个建议采集数据的历史存档要全量保留。雷达图当天看的是实时数据但排查问题时往往要回溯上周三这个时候是什么状态。我把每分钟的原始样本压缩按小时平均值存储一张 MySQL 表就足够但它在后续调权重、重新评估阈值时给到了极大的数据支撑。第四个建议把打分公式的中间结果暴露出来。PLFM_RADAR 的每个维度详情页里我都会展示当前实际值、预警线、目标线、最终得分这几个字段。很多同事一开始不理解为什么这个维度是 70 分看到 actual 和 target 之后就明白了这也大大减少了关于评分合理性的争论。最后再聊几句系统跑了半年多之后我最大的一个体会是做监控系统的核心难点从来不是画图和发告警而是指标定义这件事本身。同一个性能健康度不同团队可能给出完全不同的定义如果没有一个能把原始数据翻译成业务结论的中间层你的雷达图再漂亮也只是一张没有灵魂的图。PLFM_RADAR 发展到后期我已经不太关注单个维度的涨跌而是更在意雷达图形态的变化趋势。比如用户反馈维度连着三天往下走哪怕还在及格线以上我也会提醒相关团队提前干预。这种看形态、看趋势的思路比单纯等告警阈值被触发要主动得多。如果你也想做一个类似的系统我的建议是先别贪大挑三四个最关心的指标用最笨的方式把采集、计算、展示、告警这个闭环跑通再看看大家愿不愿意用、用起来顺不顺手。等到你真正理解了业务健康度在你们平台的语义再逐步把维度扩展到六个、八个整个系统自然会长成最适合你们的样子。
阅读完成 · 觉得有帮助?