ARTICLE · INTELLIGENCE

战地情报 · 详情页

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

从0到1搭建平台监控预警体系:PLFM_RADAR设计全复盘

从0到1搭建平台监控预警体系:PLFM_RADAR设计全复盘

PLFM_RADAR这套东西,说白了就是我们内部给平台监控预警体系起的代号。PLFM取自Platform Framework,RADAR不是雷达硬件,而是指这套框架的核心能力:主动扫描、异常识别、趋势预警。折腾了大半年,从最开始只是接了几台机器的CPU内存数据,到现在能覆盖几十个业务模块的接口健康度、中间件状态、甚至日志关键字的异常波动,把"服务挂了等用户反馈"变成了"还没彻底挂之前先收到提醒"。这篇就把整个从0到1的过程——设计思路、模块拆解、参数计算、踩坑记录——完整复盘一遍,给同样在搭监控体系的同学一个参考。

1. 项目背景与整体设计思路

1.1 为什么叫"PLFM_RADAR",它解决的到底是什么问题

任何一个稍微有点规模的后端团队,迟早会面临三个让人头疼的现状:

第一,告警滞后。服务因为连接池耗尽或者慢SQL堆积导致响应时间飙升,但业务量还没到直接触发熔断的程度,系统看起来"还能用",等到用户开始投诉、客服群里出现截图,运维才反应过来出事了。这是典型的"被动灭火"。

第二,定位困难。整个平台的服务分布在不同的机器上,日志散落在各自的磁盘里,指标数据存在不同的时序库里。真的出问题的时候,要在至少三个系统之间来回切换,先用A系统看CPU,再用B系统查日志关键字,最后去C系统翻链路追踪,才能拼出一个完整的故障画面。

第三,信息孤岛。业务指标归业务指标,基础设施指标归基础设施指标,中间件状态又独立一套,三者之间没有任何联动。某个接口响应时间突然变慢,有可能是代码逻辑问题,也有可能是它依赖的Redis主从切换了,单看任何一端都发现不了。

PLFM_RADAR的设计初衷,就是把这三个问题揉在一起解决。它不是一个单点工具,而是一套框架:负责把散落在各处的状态信息收拢到统一的地方,用"雷达扫描"的方式周期性刷新全平台的状态快照,一旦某个维度的信号偏离了正常范围,立刻给出分级预警。

在项目启动之前,我的原则是先定义清楚要做什么,再选技术栈。PLFM_RADAR的核心目标拆成四个词:发现、归一、关联、预警。发现是指能感知到平台内部和外部的各种信号源;归一是指把不同格式、不同协议、不同语义的数据统一成一套指标模型;关联是指把业务层和基础设施层的数据打通,能从一条异常链路一路追到根因;预警是指所有异常都能通过一套分级机制在合适的时间触达到合适的人。

1.2 方案选型:自研控制面,开源组件做数据面

在选型这个问题上,团队内部其实有过一段时间拉扯。主要的候选方案无非三类:直接买商业监控产品、全套用开源监控栈、自研。

商业产品的优点是不用操心太多底层细节,自带漂亮的界面和开箱即用的告警策略,但问题也很明显:一是贵,当监控的节点数和指标量上升到一定规模,授权费用会变得非常夸张;二是黑盒,大部分商业产品的内部存储格式、告警聚合逻辑、自定义能力都有边界,真遇到平台特定场景时,很难按照自己的想法去扩展。

开源监控栈(Prometheus + Grafana + Alertmanager这套)其实是很多人第一反应的选择。这套方案确实成熟,社区活跃,各种exporter应有尽有,PromQL写起来也很顺手。但我最后还是决定在开源组件的基础上,自研一套控制面,也就是PLFM_RADAR的调度与预警核心。

这里想多说两句我的考虑。Prometheus这种拉模式的监控系统本身做得很好,但在多集群、多租户、需要频繁调整抓取目标和告警规则的场景下,直接用原始配置管理会逐渐失控。配置分散、规则冲突、告警去重逻辑不够灵活,这些都是实际运维中特别消耗精力的事。我的思路是让开源组件去干它们最擅长的事——采集、存储、查询、可视化,而把规则编排、动态调度、告警收敛、通知路由这些需要结合业务定制的逻辑,放到PLFM_RADAR自己的控制面里。

最终落地的技术栈是这样:

  • 数据采集层:标准exporter(node_exporter、mysql_exporter、redis_exporter等)加自研的HTTP探针和业务指标SDK
  • 数据存储层:VictoriaMetrics,兼容Prometheus查询语法,但存储压缩率更高、单机性能更好,省资源
  • 控制面:PLFM_RADAR核心服务,负责任务调度、规则管理、告警收敛、通知路由
  • 展示层:Grafana,所有数据接入后统一通过它做可视化面板

这样搭配的好处是,每一层都足够成熟,不需要去重复造轮子;而控制面掌握在自己手里,后续要加能力(比如动态基线、智能降噪、自动根因分析)都很方便。

1.3 整体架构与数据流转

整套框架的数据流可以简化成一条线:信号源 -> 采集器 -> 存储层 -> 控制面 -> 通知渠道。

信号源是原始数据的来源,包括各台机器的系统指标、内网服务的接口指标、中间件状态、日志关键字计数、拨测探针的可用性数据等等。采集器负责把这些信号源的数据定时拉取或者接收。存储层负责把采集到的时序数据按时间维度组织起来,支撑后续查询。控制面做的事情最核心:它周期性从存储层查询指标,根据配置好的规则判断是否异常,如果异常则进入告警流程,做去重、路由、升级,最后送到通知渠道。

比较关键的一个细节是,控制面的"扫描"周期和采集周期是分开的。采集频率决定数据能多快进入存储,扫描频率决定从异常发生到告警发出的延迟。这两个参数如果设置得不好,要么告警太慢没有意义,要么数据量太大把存储压垮。我后面在实操章节会具体讲到怎么计算。

2. 核心模块拆解与关键参数

2.1 雷达扫描模块:主动探测与被动采集的配合

提起RADAR这个词,很多人会惯性觉得"监控系统嘛,不就是定时采数据"。但真正做过生产环境监控的人会明白,被动等待数据上报和主动去探测目标,两者缺一不可。

被动采集解决的是"目标自身愿意暴露状态"的场景。比如每台机器上的node_exporter,它会持续暴露CPU、内存、磁盘、网络等指标,我们的监控框架定时去抓取就行。这种方式覆盖率广,指标丰富,但有一个前提——目标进程本身还活着。如果目标机器宕机了,或者进程僵死了,采集器会发现"抓不到数据",但这时候其实已经晚了。

主动探测解决的正是这个盲区。PLFM_RADAR里维护了一批探针任务,定时从探针节点向目标发送真实的请求,然后根据返回结果判断目标是否健康。我常用的探针类型有三种。

HTTP拨测:构造一个真实的HTTP请求打到目标接口上,记录状态码、响应时间、返回内容是否包含预期关键字。这个能检测到"进程活着但接口逻辑已经出问题"的情况。

TCP端口探测:对关键中间件的端口做TCP连接测试,比如Redis的6379、MySQL的3306、Kafka的9092,确认端口能正常建立连接。

自定义探测脚本:这个更灵活,比如对数据库执行一条轻量查询确认主从同步状态正常,对消息队列尝试写入一条探针消息并确认消费成功。

主动探测和被动采集的配合逻辑是:被动采集负责识别"指标越界",主动探测负责识别"服务失联"。两条路径互不替代,任何一个方向的告警都不能单独作为服务下线的唯一依据,必须两者结合着看。

这里有个经验:拨测频率不要设太高,尤其是带有业务请求的HTTP拨测。我们在初期把拨测频率设成每10秒一次,结果某几个下游系统直接打电话过来问"为什么你那边访问量涨了这么多"。原因是拨测请求被打到了生产环境的核心接口上,虽然每次只多一两个请求,但它会触发下游的统计报表。后来把所有拨测频率统一下调为每30秒一次,并且拨测任务固定走独立的测试账号和探针路由,彻底和线上正常请求隔离。

2.2 异常检测:基线阈值与滑动窗口结合

阈值告警是所有监控系统的基础,但直接用固定阈值会让人特别崩溃。典型的例子是QPS:白天业务高峰的时候QPS能到8000,晚上低谷可能只有500,如果固定设置"超过7000就告警",那每天晚上都会被漏报,因为真正出问题的时候QPS反而是从500异常掉到50;如果设置"低于400就告警",白天又会出现大量误报。所以PLFM_RADAR里的阈值不是死的,它是跟历史基线去对比,看偏离程度。

我用的异常检测算法说穿了一点都不玄乎:对每个指标,系统维护一个滑动时间窗口(默认最近7天的同时间段数据),基于窗口内的历史值计算出一个预期区间,当实时值和预期区间的偏离度超过设定倍数时,才判定为异常。

具体计算过程是这样:

  1. 将一天按5分钟粒度切分,统计每个5分钟周期内指标的历史平均值和标准差。
  2. 针对当前时间点,取过去7天相同5分钟周期的数据,组成一个样本集合。
  3. 计算样本均值和标准差,根据标准差倍数确定异常阈值。一般我会用均值加减3倍标准差作为正常范围,超过这个范围才触发。
  4. 实时值进来之后,计算它和样本均值的差值,除以标准差,得到偏离系数。这个系数就是告警规则里可以调节的灵敏度参数。

举个例子,假设某个接口在过去7天里,每天10:00-10:05这个周期的平均响应时间都是100ms,标准差是10ms。当前最新的响应时间突然变成了200ms,偏离系数就是(200-100)/10=10,远远超过3倍标准差的阈值,触发告警。但如果这个接口本身就一直在波动,标准差是80ms,那么同样的200ms响应时间偏离系数只有1.25,系统会认为这只是在正常波动范围内,不会告警。

这个参数设计解决的痛点非常明显:它让系统学会了"自适应"。同样的CPU使用率,在业务高峰和业务低谷意味着完全不同的健康状况,用偏离度表达比用绝对值表达通用得多。

滑动窗口的数据量计算也要提一下。7天窗口、5分钟粒度,意味着每个指标每次计算就需要拉取72412=2016个历史数据点。如果告警规则有一百条,每30秒扫描一次,对存储层的查询压力是比较大的。我的做法是,把历史样本的均值和标准差计算做成小时级的预聚合任务,提前算好存起来,实时检测的时候只做一次简单查询,大幅降低扫描开销。

2.3 告警收敛与通知分级

告警系统做起来之后,最怕的不是没有告警,而是告警发了等于没发——因为噪音太多了。PLFM_RADAR的告警收敛逻辑,我总结成三条原则:去重、聚合、分级。

去重解决的是"同一条故障重复发"的问题。比如一台机器连着MySQL超时,采集探针可能同时抓到CPU升高、MySQL连接数上升、接口响应时间变长,如果每条规则各自独立发出告警,用户邮箱瞬间会收到几十条。PLFM_RADAR的做法是给告警计算一个"指纹",指纹由告警来源、目标对象、异常信号三个字段共同决定,相同指纹的告警会在一个时间窗口内合并成一条。

聚合解决的是"从故障通知变成故障上下文"的问题。合并后的告警会自动附带这个对象的关联状态——比如CPU告警的同时,有没有伴随内存告警、网络波动、依赖组件异常,把这些信息拼成一个上下文卡片,让收到通知的人一眼能看出这是孤立的机器问题,还是整个机房级别的故障。

分级解决的是"不同故障用不同力度"的问题。我们内部把告警划分为P0、P1、P2三级:

级别定义通知方式响应时效
P0核心业务不可用,用户主链路完全中断电话 + 短信 + 群机器人立即
P1核心功能受影响但有降级方案,或非核心模块不可用短信 + 群机器人15分钟
P2可容忍的局部异常,不影响主流程群机器人 + 邮件当天

分级的依据不是技术维度,而是业务影响维度。同样是MySQL连不上,如果这个MySQL支撑的是注册登录服务,那大概率是P0;如果支撑的是后台数据报表服务,可能是P1。这个映射关系在规则配置阶段就要提前梳理清楚,不能等到告警来了再判断。

3. 实操过程与关键环节实现

3.1 环境准备与组件部署

PLFM_RADAR的控制面实际上是几个服务组合,我以一套最小可用配置为例说明部署步骤。

首先是存储层VictoriaMetrics。单机版部署非常简单,一个二进制文件就能跑起来。生产环境我建议以systemd服务方式托管。官方默认监听8428端口,数据落盘在设置的存储目录下。启动参数里比较关键的几个:

-retentionPeriod=60 // 数据保留60天 -storageDataPath=/data/victoria-metrics -httpListenAddr=0.0.0.0:8428

数据保留期不建议设太长,时序数据越老价值越低,60天足够回溯一个完整业务周期的趋势。日增量数据量可以这样估算:一个节点采集500个指标,每个指标每分钟一个点,一天就是5006024=72万条样本,VictoriaMetrics单机处理这个量级毫无压力。

然后是抓取采集数据的任务调度。我们用Prometheus承载抓取任务,VictoriaMetrics作为远端存储。Prometheus的配置文件里指定remote_write地址指向VictoriaMetrics的/api/v1/write接口即可。这套架构的好处是抓取逻辑配置仍然由Prometheus负责,规则和查询可以无缝兼容PromQL。

最后是Grafana展示层。导入VictoriaMetrics数据源,通过PromQL查询画面板。展示层是PLFM_RADAR的"仪表盘",核心面板我后面会细讲。

3.2 关键配置与参数计算过程

配置采集频率是一个需要认真做数学题的环节。采集频率太高,数据量和存储成本成倍上升,拨测频率高了还可能影响生产环境业务;太低,又会让告警延迟变大。

我的经验是分场景区别对待:

系统指标(CPU、内存、磁盘),采集频率设为30秒一次。这类指标变化缓慢,30秒足够捕捉状况变化。

业务接口指标(响应时间、错误率、QPS),采集频率设为10秒一次。这些指标波动快,直接关联用户体验,需要更细的粒度。

拨测探针,采集频率设为30秒一次。探针涉及发送真实请求,频率太高容易影响线上,30秒是平衡点。

告警扫描频率固定为30秒。这意味着从指标异常出现到告警发出,理论上最长延迟是"采集周期+扫描周期",即最坏情况下60秒左右。

存储量估算可以按这个公式手算:单个指标一天的数据点 = 86400秒 / 采集间隔秒数。30秒间隔就是2880个点,10秒间隔就是8640个点。假设总指标数5000个,混合间隔后日均新增样本约2000万条,每条样本在VictoriaMetrics中压缩后平均占1-2字节,日均新增存储约40MB。这个量对磁盘几乎没压力,所以可以放心把采集频率设得稍微密一点。

再来看告警阈值的计算。以接口错误率为例子,假设过去7天的历史数据中有明显的高低峰周期性,但整体波动不大。系统会先计算出当前时间窗口的基线均值为0.1%,标准差为0.08%。通过调节偏离倍数(默认3倍),告警触发线就是0.1% + 3*0.08% = 0.34%。如果实时错误率达到0.5%,偏离系数达到5倍,就会触发P1级告警。

实际操作中我建议你先跑两周观察数据,用真实历史数据验证阈值是否合理,再打开规则开关。千万不要一上来就凭直觉设阈值,否则你会被误报淹没。

3.3 从接入第一个服务到收到第一条告警

我以一个典型服务"用户服务(user-service)"为例,讲一遍完整接入流程。

第一步,确认服务暴露了标准指标接口。如果服务是用Go或Java写的,需要在代码里引入Prometheus客户端,把HTTP监听端口挂到/metrics路径上。这一步不难,但要注意鉴权,避免生产环境的指标接口裸露在内网其他不可信环境中。

第二步,在Prometheus配置里增加抓取任务:

scrape_configs: - job_name: 'user-service' scrape_interval: 10s metrics_path: '/metrics' static_configs: - targets: ['10.1.1.10:8080'] labels: service: user-service env: production

这里labels不要乱加,尽量保持简约。label是后续告警分组和聚合的重要维度,加的越多,告警拆分得越碎,去重越难做。

第三步,在VictoriaMetrics里验证数据是否写入成功。直接用浏览器或curl访问VictoriaMetrics的查询接口:

curl 'http://localhost:8428/api/v1/query?query=http_requests_total%7Bservice%3D%22user-service%22%7D'

能返回时间序列数据,说明链路通了。

第四步,在PLFM_RADAR控制面注册告警规则。规则文件的格式大致是这样的:

alerts: - name: UserServiceHighErrorRate description: "user-service错误率异常,当前值: {{ $value }}%" metric: "error_rate" match_labels: service: "user-service" window: 5m condition: type: deviation # 用偏离度检测 baseline_days: 7 deviation_factor: 4 # 超过4倍标准差触发告警 severity: P1 notify: - channel: "group-bot" - channel: "sms"

这里有个细节:window: 5m表示系统会先对原始指标做一个5分钟的平均平滑,避免某个瞬间的尖刺直接触发告警。平滑窗口太小,容易抖动;太大,告警变迟钝。5分钟是我在大多数场景下都觉得合适的值。

第五步,测试告警链路。在测试环境里直接停掉user-service其中一个实例,或者模拟一个高错误率的请求进来。正常情况下,10秒后数据采集到异常,30秒内扫描周期命中规则,1分钟内你应该能在群里收到告警卡片。

3.4 雷达视图与总览面板设计

告警只是手段,PLFM_RADAR最终的目标是让值班人员打开一张面板就能对全平台健康状况了然于心。这个总览面板我们内部叫"雷达视图",借用了雷达术语里的几个核心概念:方位、距离、速度。

方位,指告警影响的范围。图上按业务域划分,每个区块代表一个模块,区块的高亮程度代表这个模块的告警等级。距离,指异常发生的"远近",也就是影响深度。比如同样是用户服务出问题,在入口网关层可见还是只在后端进程内部可见,展示上会有层级区分。速度,指异常变化的趋势。面板上用迷你趋势图展示最近一小时的指标走势,能帮助判断是突发的尖峰还是持续恶化的趋势。

在Grafana里实现时,我用的是PromQL聚合再加阈值映射:把每个业务的错误率、P95延迟、饱和度三个指标映射到一个0-100的"健康分"上,然后用单值面板显示。颜色规则:

  • 健康分80-100:绿色,正常
  • 健康分50-80:黄色,观察
  • 健康分0-50:红色,异常

单个面板就能承载"全平台哪个模块有问题"的信息,值班同学不用逐台机器看,一屏扫过就能定位优先级。

4. 常见问题与排查技巧实录

这半年里遇到的坑,比顺利跑通的部分更有分享价值。我把高频问题整理出来,每条都附上排查思路。

4.1 告警轰炸:一个节点的抖动导致几百条通知

问题现象:某个实例的CPU升高,结果同一时间其他所有依赖这台机器的服务都开始报超时,告警群瞬间刷了上百条。

根因:告警规则的label组合太细,三个服务实例、五个下游依赖项全部各自匹配到了独立规则,导致一个根因被拆成了几十条互相独立的告警。

解决方式:在告警聚合逻辑里增加"上游依赖故障"的信号识别。PLFM_RADAR会在告警触发时拉取目标对象近10分钟所有指标的异常信号,如果发现同一个源头对象的多个维度同时偏离,则只保留最高级别的一条,其余降级为附加上下文。

实际调整后,之前动辄上百条的告警,现在压缩到最多6条,分别对应故障源、受影响的入口流量、受影响的数据库连接、受影响的缓存节点、对外测活失败、根因已被追踪到。值班人员处理起来舒服太多了。

4.2 漏报:服务核心接口挂了但指标表现正常

问题现象:有几次服务进程没退出,但内部线程池全满,接口全部超时,可告警系统没有任何反应。

根因分析:标准指标采集到的数值可能还在"正常"范围内,比如进程数正常、TCP连接数正常、内存占用正常,但这些指标无法反映线程池耗尽这种应用层面的故障。

解决思路:核心接口必须配主动拨测探针。我在所有关键业务接口上加了HTTP拨测,探针脚本检查以下三个要素:HTTP状态码是否200、响应时间是否超过预定义阈值、响应体里是否包含核心业务字段。三要素任一不通过,直接触发P1告警。

实测效果:某次下游数据库连接池耗尽引发接口大面积超时,拨测探针在30秒内检测到响应时间超标,告警比用户投诉提前了7分钟左右。这7分钟就是两个概念:一条被动的"用户反馈",和一条主动的"故障预警"。

4.3 时钟不同步导致的数据错位

问题现象:告警时间和服务实际异常时间对不上,甚至出现某个中间件自己报障的时间和监控显示的偏差达到几分钟。

根因:部分机器没有配置NTP同步,系统时钟存在漂移,导致探针打点时间戳和存储服务本机时间不一致。

解决方式:所有机器统一配置NTP客户端,并加了校验任务,每天定时比对目标机器的当前时间和服务端时间差,超过200ms就直接标记为异常。这个时间偏差很重要,尤其是在做滑动窗口基线检测时,几分钟的偏差会让当前数据和历史数据对不上,造成大量误报。

4.4 常见问题速查表

现象可能原因排查命令/方法
某些指标在Grafana查不到抓取任务没配或目标端口不通curl <target>/metrics确认可访问
告警延迟严重采集周期/扫描周期配置不合理检查scrape_interval和扫描频率
刚配的规则立刻误报阈值未结合业务周期设定调大偏离系数,观察7天窗口数据
静默时段仍有告警规则未排除发布窗口配置maintenance_window,发布期间暂不告警
告警内容显示NaN指标名称或label写错在VictoriaMetrics查询页验证expr
通知渠道未收到消息webhook地址失效或签名过期先在测试环境手动触发一条通知

4.5 发布窗口的特殊处理

这算是一个经验彩蛋。服务发布期间,蓝绿切换、流量重新负载,指标会大幅波动,此时触发告警基本全是噪音。PLFM_RADAR里设置了发布窗口机制:

在发布预发到生产的时间段,指定服务的告警规则自动进入静默模式,静默时长不是写死的,而是根据发布时间弹性调整。发布完成后,系统自动恢复正常告警。这样做的好处是减少了大量 "发布过程中正常波动导致的误报",也可能让真正的问题在发布窗口内暂时被掩盖。所以发布窗口结束后15分钟是关键观察期,我会让系统自动生成一份"发布前后核心指标对比"简报,推给发布人。

5. 写在最后:几次折腾下来最值得说的经验

这部分不讲架构,不讲代码,只讲团队踩过几次坑之后沉淀下来的做事方式。

第一,新接入的服务不要立刻开启全量自动告警。至少让它在观察模式下跑一周,只记录异常不发送通知,用收集到的数据校准基线和阈值。一周之后打开告警的时候,你会发现误报率低到可以接受。

第二,告警消息里一定要带完整的上下文。一个合格的告警通知至少要有以下六要素:什么服务、什么指标、当前值、预期范围、影响面、查看详情链接。缺任何一项,收到告警的人都得先花几分钟去查资料,那这个告警的质量就大打折扣。

第三,监控系统本身也需要被监控。PLFM_RADAR的控制面服务是单实例运行的,有一次它自己所在的宿主机磁盘写满,导致告警全部中断,但谁也没有发现,直到一次真实故障时大家才注意到"怎么一条通知都没收到"。后来加了针对控制面自身的存活拨测和指标采集,用另一套完全独立的第三方可用性拨测来监控这套监控系统。

第四,不要一开始就追求花哨的告警能力,先把最基础的做扎实。所谓最基础,就是每个核心服务都有三条必配规则:进程存在性检测(进程是否还活着)、接口可用性检测(接口是否正常响应)、接口延迟检测(响应时间是否越界)。这三条覆盖了80%的故障场景。基线检测、趋势预测、关联分析这些,是在基础稳定的前提下逐步叠加的事情。

PLFM_RADAR到现在还在迭代,我也清楚它仍有不少粗糙的地方,比如动态阈值的收敛速度还不够快,新服务自动发现还没做好。但这一路走来最大的收获,是让我明白了监控体系真正的价值不在于堆了多少工具,而在于把"提前发现风险"变成一个稳定的、系统化的能力。希望这篇复盘能让你少走一些弯路。

RELATED READING

延伸阅读

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