1. 项目缘起:为什么需要一个叫“PLFM_RADAR”的东西
年初我们团队接到一个挺头疼的活儿:内部十来个业务系统跑在同一套基础设施上,隔三差五就有人跑过来说“页面好慢”“任务又失败了”“资源又不够了”。每次排查都要翻好几套监控工具,时序库里捞半天数据,才能拼出一个大致脉络。痛了几次之后,我们决定自己动手做一套平台级的“雷达”——这就是PLFM_RADAR的起点。
PLFM_RADAR这个名字,PLFM取自Platform,RADAR很直白,就是雷达。它干的事情可以概括成一句话:把散落在各系统里的运行状态数据统一收上来,用一套规则去扫描、识别、预警,让平台上的异常“看得见、说得清、追得到”。和传统单点监控不一样,它更关注平台整体视角:某个服务的抖动会不会拖垮依赖链?某台机器的水位上涨是不是全局限流的前兆?这些跨系统的关联判断,才是这套东西真正解决的核心问题。
如果你也面临相似的场景——系统不多但依赖复杂,监控工具各管一段,出了问题靠人肉翻日志,那这篇总结应该能给你一些可以直接抄走的思路。
2. 整体设计思路:先画场景,再定架构
2.1 先想清楚要“看见”什么
做监控系统最容易犯的错是一上来先选工具,选完才知道自己到底要监控什么。我们反过来,先花了两周时间把平台上的“场景”梳理了一遍,最后归成三大类:
- 资源水位类:CPU、内存、磁盘、带宽,这些是基础设施的“血压计”,异常往往有明确的数值阈值可判定。
- 服务状态类:接口响应时间、错误率、任务队列积压量、进程存活状态,这是业务系统的“心电图”,比纯资源监控更贴近用户体验。
- 依赖链路类:A服务调用B服务、B依赖数据库和缓存,某一段变慢会顺着调用链传导。这部分最容易被单点监控漏掉,却是平台事故的“重灾区”。
这三个场景对应三种不同的数据特征:资源数据是数值型的,周期性强;服务状态数据有瞬时抖动,需要平滑处理;链路数据强调关联性,需要做拓扑归并。设计雷达的第一步,就是让下面的每一层都朝着这三个目标去服务。
2.2 四层结构:采集、传输、存储、分析
PLFM_RADAR的架构不复杂,没有搞微服务那一套花活。整体分成四层:
- 采集层:每台机器部署一个轻量采集器,负责收集系统指标、服务探针数据,以及从各业务系统已有的日志里抽取关键状态。
- 传输层:采集器把数据推送到统一的接入网关,网关做基础校验、清洗和限流。这里只用简单的HTTP+JSON协议,不引入消息队列中间件,把故障面控制到最小。
- 存储层:指标数据进时序数据库,链路和事件数据进文档型存储,原始日志继续留在原来的日志平台。不强制“大一统”,各归各位。
- 分析层:定时任务负责计算聚合指标、执行告警规则;前端是一个简单的控制台,展示总览面板和告警列表。
这个设计有一个很重要的取舍:采集和存储分离、存储和分析分离。哪怕分析层挂了,数据还在库里,事后还能回溯补算;哪怕控制台访问不了,告警通知也会单独走消息通道发出来,不影响人接收。
2.3 为什么不用现成的开源全家桶一把梭
肯定有人问:Prometheus + Grafana + Alertmanager 不是挺好的吗?为什么还要自己写一套?
说实话,我们内部确实一直在用这些工具,单点监控和可视化它们做得非常好。但我们的痛点是跨系统关联分析,这块用现成方案拼起来非常别扭:Prometheus擅长拉取数值指标,可我们要判断“支付服务和订单服务的调用关系是否健康”,这需要事件数据+链路数据+指标数据放在同一个分析上下文里计算。硬靠PromQL硬写也能做,但规则一多、逻辑一复杂,维护成本会直线上升,最后变成只有几个人能看懂的“天书”。
所以我们的策略是:基础监控继续用现成组件,PLFM_RADAR专注于做平台层的关联分析和统一告警。它不替代Prometheus,而是在上面加了一层“雷达视角”——把下游监控数据汇进来,再用自己的规则引擎做二次加工。这个定位从一开始就很明确,后面所有开发都围绕这个边界推进。
3. 核心细节解析:数据模型与指标计算
3.1 用“实体+指标+标签”组织所有数据
雷达系统里最基础的概念是监控实体。一台机器、一个服务实例、一个数据库连接池,都是实体。每个实体带一组静态属性,比如所属机房、部署单元、负责人;同时带一系列动态指标,比如CPU使用率、请求量、错误数。
实体之上是标签体系,这部分是关键中的关键。我们用了五类标签:
- 环境标签:生产、预发、测试
- 归属标签:业务线、子系统、团队
- 位置标签:机房、机架、容器节点
- 角色标签:入口服务、中台服务、存储节点
- 版本标签:发布批次、代码版本
标签不是随便打的,它直接决定了雷达能画出什么样的“关联图”。比如某个预发环境的服务变慢,如果所有数据都有环境标签,就可以一键过滤出同一发布批次的所有实体,快速判断是不是这次发布引入的回归。
3.2 指标计算的三个关键公式与场景
实时计算层最常用到的有几个指标,看起来简单,但细节里全是坑。
第一个是滑动窗口请求成功率,业界通用的做法是取最近5分钟的样本:
成功率 = (5分钟内成功请求数 / 5分钟内总请求数) * 100%这个公式单独看不难,难在“5分钟”怎么定义。如果按自然时钟的整点切窗口,流量毛刺会剧烈抖动;我们改成了滚动窗口,每10秒计算一次最近5分钟的数据,相当于一个不断往前平移的窗口,这样曲线平滑很多,也更容易和真实用户体验对齐。
第二个是容量水位预估,这个我们用在磁盘和内存上比较多:
预计耗尽时间 = 当前剩余量 / 最近30分钟的平均消耗速率这个指标非常直观,比如磁盘还剩80GB,最近半小时平均每小时消耗2GB,那预计40小时会写满。它比单纯的“使用率超过80%”告警更有实际意义,能给运维留出精准的处置时间窗口。
第三个是依赖影响度的权值,这个稍微复杂点:
依赖影响度 = 被依赖方故障时间窗口内 / 调用方总请求数量 * 100%假如支付服务在10分钟内错误率飙升,那我们要看这10分钟里有哪些上游在调它、各自调了多少笔。影响度越高,说明事故的“辐射面”越大,告警级别也要相应上调。这个指标让雷达不只是报“谁坏了”,还能说“大概影响了谁”。
3.3 存储选型与数据生命周期
时序数据我们最终选了ClickHouse,原因是写入吞吐高、压缩比好、聚合查询快。数据按天分区,原始指标保留7天,5分钟聚合保留30天,1小时聚合保留半年。这样既保证近期排障时的精度,又控制了存储成本。
实测下来,单机写入速度能稳定在每秒十几万点,查询95%在200毫秒内返回,这个性能对内部平台完全够用。日常容量大约每天产生20GB原始数据,压缩后大概6GB,一年下来的存储成本完全可以接受。
事件数据则进了Elasticsearch,主要存告警触发记录、规则变更记录、人员确认操作。到这里出现了一个很常见的坑:时序库和文档库的数据对不上。比如告警记录显示10:05触发,可时序库里10:04到10:06的指标恰好因为采集器重启而缺了一段。后来我们统一了时间规范:所有时间戳一律用毫秒级UTC存库,展示层再转成服务器本地时间,并在告警记录里同时保存“触发批次ID”,这样两边数据就能严格对齐了。
4. 实操过程与核心环节实现
4.1 采集器的实现要点
采集器我们用Go写,部署方式很简单:编译成静态二进制,丢到机器上配一个systemd服务。每个采集器有唯一ID,启动时先从配置中心拉取自己的采集任务列表,然后按固定的间隔循环执行任务。
每个采集任务是一个插件,输出统一的指标结构:
{ "entity_id": "srv-order-prod-01", "metric": "request_total", "value": 43520, "timestamp_ms": 1704233600123, "tags": { "env": "prod", "biz": "order", "zone": "sh-01" } }上报走批量接口,每10秒攒一批,最多攒500条或10KB就推一次。这里有个“攒批”策略值得提一下:宁可本地稍微多攒几秒,也要减少HTTP连接次数。前期我们每2秒就推一次,网关和采集器之间的TCP连接数和垃圾回收压力明显偏大;后来改成10秒批量推送,整体CPU占用反而降了。
采集器自身有一个看门狗进程,每隔30秒检查主进程是否还响应,不响应就自动重启,重启时用状态文件续接,避免重复上报旧数据。另外,采集器不能把自身消耗的资源也当成系统异常报上来——我们为此专门把采集器的CPU、内存使用量放进了独立进程组并在上报之前过滤掉。
4.2 告警规则引擎:阈值不是拍脑袋定的
告警规则引擎是整个雷达的核心,研发过程中我们反复打磨。规则大致分为三类:
| 规则类型 | 触发条件示例 | 默认周期 | 告警级别 |
|---|---|---|---|
| 资源阈值 | CPU使用率连续5分钟>90% | 每60秒评估一次 | P2 |
| 聚合统计 | 某服务5分钟错误率>5% | 每30秒评估一次 | P1 |
| 联合判断 | 磁盘剩余预计<24小时 | 每10分钟评估一次 | P1 |
规则不是一次性写死,而是配置在规则表里,后续可以在控制台上调整阈值而不需要重启服务。比较关键的设计是连续N个周期都触发才真正告警。比如错误率规则设置“连续3个周期(每周期30秒)错误率大于5%才触发”,这能过滤掉绝大多数瞬时抖动造成的误报。代价是延迟变长,最多90秒才出告警,但实际操作中发现,这个延迟换来的是告警置信度的大幅提升,非常划算。
告警产生后进入“静默期”机制:同一实体+同一规则触发的告警,默认10分钟内不重复通知,除非级别升级。比如从P2升级到P1,会立即重新通知。如果同一个实体连续1小时不断触发同类告警,系统会发送“持续告警汇总”,把这段时间的触发次数、峰值、相关实体都汇总成一条,而不是刷屏。
4.3 控制台面板:给不同角色看不同的信息
控制台分成三个视角:
- 值班视角:只看当前正在处理的告警列表、待确认工单、以及平台整体健康分。健康分是我们自创的一个0到100的指数,由资源水位、服务可用性、依赖健康度加权计算得出,方便快速了解现在“大不大事”。
- 开发视角:按业务线隔离,研发只能看到自己负责的实体和对应的指标曲线,支持按标签下钻。这里用到了前面说的标签体系,数据权限也通过标签来控制,效率很高。
- 管理者视角:聚合展示SLA趋势、告警趋势、故障时长分布等周报型数据,不需要实时,数据从聚合表中跑预计算任务生成。
前端没有用太重度的方案,就是React + 一个开源时序图组件,加上WebSocket推送实时告警。整体代码量不大,但给使用者的体感非常专业——因为信息密度高、层级清楚。
5. 常见问题与排查技巧实录
到这里分享几个我们实际运营中遇到的经典问题,每一个背后都对应一个“别踩的坑”。
5.1 时序数据库的“写放大”问题
问题现象:ClickHouse磁盘占用增长速度远超预期,一周发现比预估大了好几倍。
排查过程:先是看分区大小,发现单日分区数据量异常。进一步查看发现,有一个采集任务误配了“每次上报都附带当前CPU的瞬时快照”,这个指标基数特别大——因为每台机器上有几百个进程,每个进程每秒一个点,一天数据量轻松上亿。
解决办法:把高基数指标全部降频,进程级别的指标从10秒采集改为5分钟采集;同时给写入链路加了采样规则,对于老机器上的非关键指标自动抽稀。这套组合拳之后,日增数据量降了70%。
这个问题的根因其实就是标签基数爆炸。给读者的一个建议:设计指标时一定要明确“指标是用来回答什么问题的”,如果答不上来,那就别采集,数据也是成本。
5.2 告警风暴:一次发布引发的几百条通知
问题现象:某次上线新版本后,相关服务因为日志里一个错误的等级配置,导致错误率计算全部超标,一瞬间触发几百条告警,值班手机直接被刷爆。
排查过程:回看告警记录,发现触发点全部集中在同一批次发布的十几个实体上。这个问题光看单条告警很难发现规律,但我们当时做了一个“告警聚类”的后处理任务:按标签(发布批次、部署单元、依赖服务)对告警做分组,如果同一分组内实体数量超过一定比例,就自动合并成一条“群体性告警”并在标题里标注批次信息。
改进后的效果非常明显:类似的发布问题再出现时,告警从几百条收敛为一两条,而且信息量反而更大——直接告诉你是“这批发布引起的”。
这里想说一个心得:告警系统的价值不是通知越多越好,而是通知越准、越少越好。调试告警规则时,宁可漏掉一两个无关紧要的边缘告警,也不要让平台产生“狼来了”效应——当值班人员发现每条告警都是真的问题、而且告警信息直接指向根因时,整个团队的响应速度会快一个量级。
5.3 时间不同步导致的分析错乱
问题现象:某次排查发现,A服务调用B服务的数据,在雷达里显示B服务响应时间正常,但A服务显示超时,两边时间线完全错位。
排查过程:查了一遍发现,有几台服务器的时间同步服务没配置好,系统时间和标准时间差了将近40秒。而链路数据是按时间戳做关联的,几十秒的偏差足以让整个调用链视图“断裂”。
解决办法:在所有机器上强制部署时间同步客户端,并在采集器里增加一个“时间偏移检测”能力——采集器会在心跳包里带上本机时间,网关收到后对比自己的时间,一旦偏移超过5秒就在该实体的标签里打上“clock_offset”标记,让它不再参与链路分析,并定时触发告警提醒运维修复。同时,链路数据关联不再只依赖时间戳,而是引入了“请求ID透传”,这也是彻底解耦的有效手段。
这个教训挺典型的:分布式系统里,时间不同步是一切疑难杂症的温床,不只是监控系统,很多业务问题排查到最后都会绕回时间上来。
5.4 常见问题速查表
| 问题 | 可能原因 | 优先排查动作 |
|---|---|---|
| 指标曲线出现断崖式下跌 | 采集器重启、网络分区、网关限流 | 查看采集器心跳日志,检查网关连接数 |
| 告警触发了但图表无数据 | 告警规则针对聚合表,而原始数据已过期清理 | 检查聚合任务是否执行成功,比对数据时间范围 |
| 历史数据对不上 | 服务器时区设置不一致 | 统一用UTC+8,检查服务器时间同步状态 |
| 面板加载极慢 | 查询了未加时间范围的大范围原始表 | 强制限制原始表查询时间范围,推荐使用聚合表 |
| 相同告警反复触发 | 静默期设置过短或规则评估粒度过细 | 把静默期延长至15分钟,调整评估周期对齐业务波动 |
6. 避坑指南与经验复盘
6.1 规则配置的“灰度”意识
告警规则上线之前,最好先设置成“观察模式”。观察模式的意思是:规则照常计算、照常生成告警事件,但不推送给任何人,只是落库。我们花了两周时间,把所有新规则都先用观察模式跑一遍,收集触发频率、误报比例,调稳之后再切到正式通知。这比直接上线然后反复调阈值要高效得多,也保护了值班团队有限的注意力。
这套做法本质上就是给规则引擎加了一层“预发布环境”——业务代码可以灰度,告警规则同样可以灰度。
6.2 权限和数据边界要前置设计
雷达这种平台型工具一旦用起来,各业务线都会想往里加数据。如果一开始没有权限和边界设计,后期一定会乱。我们的原则是“数据提供方负责打标签,数据消费方按标签取数”,平台自身不修改各业务线上报数据的归属标签,但是保留“越权提示”的审计能力。这样既保证了灵活性,又能在出问题时有据可查。
有一个比较实用的设计细节:每个接入雷达的业务系统都对应一个接入配置,包含业务线负责人、数据负责人、告警接收人三个角色。任何规则变更、数据变更都会通知这三个角色。刚开始大家觉得多此一举,后来某次有团队误删了采集任务,信息同步到位后问题10分钟就解决了,大家才意识到这个设计多重要。
6.3 自己和团队的时间投入要心里有数
给想要复现这套系统的人一个时间参考:
- 第一阶段(数据接入):大概需要2到3周,取决于有多少系统需要改造接入。
- 第二阶段(规则引擎开发):2周左右,这部分主要是设计规则表结构和评估逻辑。
- 第三阶段(控制台前端):1到2周,做一个够用的版本其实不难。
- 第四阶段(打磨稳定性):持续进行,每一步都会遇到真实的教训。
最容易被低估的是采集器本身的稳定性。它是最贴近生产环境的部分,一旦崩溃或资源泄漏,影响面是所有下游。建议把它当成一个发布要求极高的生产服务来对待,做好进程守护、配置灰度、日志回传和CPU/内存自监控。
7. 后续扩展的思路
PLFM_RADAR这套雷达目前已经在内部稳定运行了大半年,在线故障的发现时间从过去的“用户反馈后半小时”缩短到“系统触发后3到5分钟”,很多问题甚至还没影响用户就先被雷达拦下来了。对我个人来说,这个项目最大的收获倒不是技术本身,而是理解了“平台类工具”的建设节奏:先想清楚要解决谁的什么问题,再选型动手;小步快跑地迭代,比一上来憋大招稳妥得多。
接下来我们计划给雷达加一个简单的“根因推荐”模块:把历史告警和对应的处理记录沉淀成案例库,当新告警发生时,自动匹配相似的历史场景并推荐排查方向。这个事本质上不是算法问题,而是数据积累问题——雷达已经跑了大半年,案例数据够了,后面就是顺理成章的事。
如果你正打算做类似的东西,我的建议是:别贪大,从最让你痛的那一个场景切入,做一个能跑的最小闭环,把它用起来,再一步步往外扩。监控系统最怕的不是功能少,而是没人看。让它真正成为团队每天都会打开的工具,这套系统就算成功了一半。