轻量级监控告警系统PLFM_RADAR:自建服务器可观测平台的实践

发布时间:2026/11/2 10:36:13
轻量级监控告警系统PLFM_RADAR:自建服务器可观测平台的实践 做监控这事儿真不是闲得慌。年初那天凌晨两点我看了眼业务日志的写入量突然发现数字在往下掉。当时没当回事等我第二天爬起来再看业务已经断了快六个小时客户打过来的电话我都没接到。那种摸黑排查的感觉干过线上业务的人都能懂。后来我花了两周时间自己动手做了一套面向服务器集群的轻量级监控告警系统名字就叫PLFM_RADAR。PLFM是Platform的简写RADAR就是雷达合在一起的意思很直白给整个平台装一台雷达把所有主机、端口、关键业务指标全部扫到一块屏幕上一旦哪里不对劲第一时间告警不用再等人来投诉。PLFM_RADAR不是什么大项目定位非常明确面向中小团队、个人开发者解决从系统黑盒到可观测的最短路径问题。整套系统包含采集端Agent、服务端Server、告警通知和一块极简仪表盘核心代码不到两千行用Docker可以一键拉起。这篇文章把项目的来龙去脉、架构取舍、核心模块实现和我在实操中踩过的坑全部摊开讲想自建监控的同学可以参考着直接落地。1. 项目定位我要的不是监控大盘是一台能救命的雷达1.1 一次事故让我重新理解监控事故本身不复杂一台应用服务器的磁盘被日志写满了服务进程还在跑但已经无法正常写文件接口全部超时。之所以拖了六小时才发现是因为我当时没有任何主动监控手段只能靠用户投诉反向感知。复盘的时候我列了几条真实需求我需要知道每台主机的CPU、内存、磁盘、网络状态至少覆盖最近半小时的趋势业务端口挂了要有通知最好一分钟内就能收到告警不能光发不收敛同一台机器同一问题半夜不能反复轰炸部署要轻不要一上来就引三个中间件毕竟团队只有两个人。拿这些需求去套市面上的监控系统时我反而纠结了。Zabbix功能强但部署结构和数据库维护都不轻Prometheus加Grafana是社区明星不过概念多指标类型、服务发现、告警路由这些都要学云厂商的监控中心倒是省事但我们是混合部署部分机器不在云上没法统一纳管。最后我决定自己写一套把需求收窄到主机资源端口存活自定义脚本探测用最小成本换最高确定性。1.2 为什么方案最终选择了自研先放一个选型对比表是我当时纠结的真实依据方案优点短板适合场景Zabbix功能全、模板多部署重、需要MySQL、学习曲线陡大团队、已有专职运维Prometheus Grafana生态强、指标模型先进组件多、概念多、告警配置不算直观容器化、微服务规模较大云厂商监控中心接入快、UI好绑定云环境、跨云和自建机房难统一全部上云的团队自研PLFM_RADAR轻量、可控、逻辑透明功能边界小、需要维护代码中小团队、混合部署自研不等于重复造轮子而是把不需要的功能全部砍掉。我只需要一个Agent周期性地收集主机指标并上报Server端判断阈值并产生告警事件再通过Webhook把消息推给人。整个链路里没有任何一个环节是现成组件解决不了的但把它们捏合成一个内聚的小系统反而能做得非常贴合自己的使用习惯。1.3 雷达扫什么不扫什么PLFM_RADAR的监控范围从一开始就锁死了主机层CPU占用率、内存占用率、磁盘空间、磁盘I/O、网络出入流量服务层TCP端口连通性、HTTP接口状态码和响应时间业务层通过自定义命令比如检查队列积压数、检查数据库慢查询数。明确不做的东西同样重要。APM调用链追踪、分布式链路分析、日志检索这些我统统没有纳入第一版。原因是它们会快速拉高复杂度而团队现阶段最痛的永远是服务是不是挂了机器是不是满了。雷达先把这一层扫清楚其他东西等业务规模上来再补完全来得及。2. 整体架构设计三个组件把链路走通2.1 Agent、Server、Web三端模型PLFM_RADAR的整体结构非常像传统的数据采集系统分三端Agent部署在每一台被监控的机器上负责采集本机指标每10秒上报一次到ServerServer运行在中心节点提供HTTP接口接收Agent数据执行规则判定维护主机列表和告警状态同时给Web提供查询接口Web一个不依赖构建工具的原生页面定时从Server拉数据把主机状态和告警事件做成一个实时刷新的看板。数据流向是单向的Agent→Server→数据库→Web。Server既是数据入口也是告警判定中心不需要额外的消息队列。这个设计有意为之。对于几十台机器的规模引入Kafka或者Redis都是浪费运维精力单机的FastAPI进程在QPS几百的情况下毫无压力。2.2 数据模型四个表撑起一个雷达数据库我用的是SQLite后续会解释原因。先看表结构总共四张核心表-- 主机注册表 CREATE TABLE hosts ( id INTEGER PRIMARY KEY AUTOINCREMENT, host_id TEXT UNIQUE NOT NULL, hostname TEXT NOT NULL, ip TEXT, group_name TEXT DEFAULT default, status TEXT DEFAULT online, last_seen_at INTEGER ); -- 指标数据表 CREATE TABLE metrics ( id INTEGER PRIMARY KEY AUTOINCREMENT, host_id TEXT NOT NULL, ts INTEGER NOT NULL, data TEXT NOT NULL ); -- 告警规则表 CREATE TABLE rules ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, exp_key TEXT NOT NULL, op TEXT NOT NULL, threshold REAL NOT NULL, duration INTEGER DEFAULT 3, level TEXT DEFAULT warning, enabled INTEGER DEFAULT 1 ); -- 告警事件表 CREATE TABLE alerts ( id INTEGER PRIMARY KEY AUTOINCREMENT, host_id TEXT NOT NULL, rule_id INTEGER NOT NULL, level TEXT, status TEXT DEFAULT firing, created_at INTEGER, updated_at INTEGER );这套模型朴素但够用。hosts表负责管理哪些机器在线metrics表存原始时间序列data字段用JSON直接存一整批指标省去为每个指标建列的麻烦rules表存用户自定义的阈值规则alerts表记录当前处于告警状态的事件状态字段是后面做告警收敛的关键。2.3 技术选型Python FastAPI SQLite技术栈上我选了Python 3.10、FastAPI和SQLite原因不讲虚的Python生态里psutil是采集系统指标的事实标准不用我造轮子FastAPI写几个HTTP接口非常快自带OpenAPI文档调试Agent对接时直接在浏览器里看接口说明SQLite不需要独立服务一个文件搞定WAL模式下读写并发也能满足现在的上报频率全项目所有代码都是PythonAgent和Server的心智模型统一维护成本最低。有人会问为什么不换PostgreSQL或TimescaleDB。我也想换但那是规模到一定程度后的事。当前几十台机器、10秒粒度、每台每次上报约1KB数据算下来一天写入量才几十MBSQLite完全撑得住。监控系统的第一原则是先跑起来不是先优化到千万级。3. 核心模块实现详解每个环节怎么抠出来的3.1 Agent采集器让主机自己开口说话Agent用psutil做数据采集逻辑非常简单。我写了一个两个函数的版本被监控机器上只需要Python3和psutil。import psutil import socket import time import json import urllib.request def collect_system_metrics(): cpu psutil.cpu_percent(interval1) mem psutil.virtual_memory() disk psutil.disk_usage(/) net psutil.net_io_counters() return { cpu_percent: cpu, mem_percent: mem.percent, disk_percent: disk.percent, net_in_bytes: net.bytes_recv, net_out_bytes: net.bytes_sent, ts: int(time.time()) } def report(server_url, host_id): data collect_system_metrics() payload json.dumps({host_id: host_id, data: data}).encode(utf-8) req urllib.request.Request( f{server_url}/api/metrics/report, datapayload, headers{Content-Type: application/json}) urllib.request.urlopen(req, timeout5)有个细节特别容易踩坑psutil.cpu_percent()第一次调用返回0.0因为它需要两次采样才能算差值。如果Server端的判定逻辑不处理这个首采值Agent一启动就会产生一次CPU 0%的低负载误报。我的做法是在Agent启动时先预热调用一次丢弃首个返回值。端口探测我单独实现了一个函数在采集端口健康状态时不走psutil直接用socket连接测试def check_port(host, port, timeout2): try: with socket.create_connection((host, port), timeouttimeout): return 1 except Exception: return 03.2 服务端接收、存储、判定一体Server端核心就是一个POST接口。Agent每10秒请求一次/api/metrics/reportServer把数据落库后立刻跑一次针对该主机的所有启用规则。from fastapi import FastAPI, Request import sqlite3 import json import time app FastAPI() def get_db(): conn sqlite3.connect(plfm.db) conn.row_factory sqlite3.Row return conn app.post(/api/metrics/report) async def report_metric(request: Request): body await request.json() host_id body[host_id] data body[data] ts int(time.time()) db get_db() db.execute( INSERT INTO metrics (host_id, ts, data) VALUES (?, ?, ?), (host_id, ts, json.dumps(data))) db.execute( UPDATE hosts SET last_seen_at ?, status online WHERE host_id ?, (ts, host_id)) db.commit() check_rules_for_host(db, host_id, data, ts) return {code: 0}我特意在数据写入接口里同步做规则判断没有用后台任务。原因还是规模小一次写入后最多跑十条规则就是几次数值比较毫秒级完成。如果以后Agent数量上到几百再把这个过程改成异步队列也来得及。数据库写入这里有个实操优化单条单条插入在机械磁盘上会有大量fsyncCPU高的机器上甚至会让IO成为瓶颈。后来我改成先攒一批再批量插入插入用executemany效果立竿见影磁盘占用和IO延迟都明显下降。3.3 告警规则引擎阈值、持续时间和状态流转告警规则引擎是整个系统里最需要想清楚的模块。先看规则长什么样{ name: CPU使用率过高, exp_key: cpu_percent, op: , threshold: 80, duration: 3, level: critical }含义是当cpu_percent大于80时进入告警但必须连续3个采集周期都满足条件才真正触发告警。这个duration参数非常重要它可以滤掉偶发毛刺。规则引擎的状态流转我设计了三态OK正常状态PENDING已经超过阈值但还没达到连续周期数属于观察期FIRING连续超阈值达到duration生成告警事件并推送通知。PENDING这个中间状态是防告警风暴的第一道保险。比如某台机器编译时CPU猛地冲到100%持续4秒就降下来了PENDING状态会在第4秒直接复位回OK根本不会发通知。只有当CPU持续30秒以上超阈值才判定为真故障。代码层面我在Server里维护一个rule_state字典键是host_id rule_id值记录当前连续超阈值次数。每来一批指标就更新这个字典state_key f{host_id}:{rule_id} last_count rule_state.get(state_key, 0) if is_breach(data, rule): last_count 1 rule_state[state_key] last_count if last_count rule[duration]: create_alert(host_id, rule, firing) else: rule_state[state_key] 0 resolve_alert_if_exists(host_id, rule)判断是否超阈值我抽象成一个函数支持大于、小于、等于三种操作符实际用下来覆盖面足够了。3.4 通知分发把告警送到对的人手里告警不发出来等于白搭。PLFM_RADAR把通知做成一个简单的分发器目前支持企业微信机器人Webhook、钉钉机器人Webhook和邮件三种渠道。我设计了一个channel注册表每个channel实现同一个接口send(title, content)。CHANNELS {} def register_channel(name): def deco(func): CHANNELS[name] func return func return deco register_channel(wecom) def send_wecom(title, content): # 组装企业微信markdown消息 data {msgtype: markdown, markdown: {content: content}} requests.post(WECOM_URL, jsondata, timeout5) register_channel(email) def send_email(title, content): # smtp发送逻辑 pass分发器遍历某条规则绑定的渠道列表逐个调用。每次调用都设置超时和重试。Webhook这类通知服务偶尔会抖动我加了三次重试退避时间分别取1秒、5秒、30秒实测下来消息丢失率基本为零。通知消息的格式也有讲究。我总结了一套模板第一行写级别第二行写主机ID第三行写当前指标值第四行写阈值。少矫情多放事实。这样人收到消息一眼就能判断要不要爬起来处理。3.5 可视化一个文件撑起的实时仪表盘前端我没有用Vue或React而是直接写了一个原生HTML页面靠fetch定时拉Server的聚合接口。做成这样纯属故意的省掉Node.js构建链部署时少一个环节Server端启动后直接可以挂静态文件无需Nginx转发对于内部监控场景原生JS足够渲染表格和状态灯。仪表盘核心逻辑就三步10秒拉一次/api/overview遍历主机列表展示卡片每张卡片显示CPU、内存、磁盘进度条和警告状态。告警事件单独一个列表区按时间倒序排未恢复的置顶。页面底部放了一个当前告警总数数字变红就说明有事处理。整个页面约200行代码维护起来没有心理负担。4. 部署与实操从零到一跑通PLFM_RADAR4.1 Docker Compose一键部署整个Server端打包成一个Docker镜像用docker-compose管理。Compose文件非常短核心配置如下version: 3.8 services: plfm-server: image: plfm/radar-server:0.1.0 container_name: plfm-server ports: - 8080:8080 volumes: - ./data:/app/data - ./config:/app/config environment: - TZAsia/Shanghai - PLFM_DB_PATH/app/data/plfm.db - PLFM_WECOM_URL${WECOM_URL:-} - PLFM_MAIL_SMTP${MAIL_SMTP:-} restart: unless-stoppeddocker compose up -d启动后Server监听8080端口数据持久化到宿主机的data目录。升级时只需要把新镜像tag改下再up -d即可。全套下来没有任何外部依赖内网服务器不会部署SQLite直接一个容器跑完。4.2 接入第一台主机Agent的部署我更倾向直接用systemd守护保证机器重启后自动上报。接入命令很简单pip install psutil python agent.py --server-urlhttp://your-server:8080 --host-idweb-01Agent启动后会先请求Server的/api/hosts/register完成注册然后进入10秒上报循环。Server收到第一条数据后仪表盘上就会出现这台主机的卡片。这里要留意Agent机器上的防火墙必须放行到Server 8080端口的出口连接很多内网环境默认禁了非标准端口容易漏配。验证是否接入成功我习惯在Server上执行sqlite3 plfm.db select host_id, hostname, status, last_seen_at from hosts;看到status为online且last_seen_at是最近10秒内就算真正通了。4.3 配置告警规则并验证告警规则全部通过API动态管理不需要改代码重启。添加一条磁盘告警示例curl -X POST http://localhost:8080/api/rules \ -H Content-Type: application/json \ -d { name: 磁盘使用率超90%, exp_key: disk_percent, op: , threshold: 90, duration: 3, level: warning }Server端会把这个规则存进rules表并立即生效。为了验证告警链路是通的我在一台测试机上故意用dd塞满磁盘dd if/dev/zero of/tmp/fill bs1M count1024等S磁盘超过90%后60秒内就能在企业微信群里收到告警通知。这条验证路径建议每接入一台新机器都跑一遍很多配置问题都是在验证环节暴露出来的。4.4 压测这套系统到底能扛多大规模我自己在测试环境压过一轮四台8C16G的Agent机器和一台2C4G的Server机器每台Agent按10秒粒度上报。Server端CPU占用稳定在15%以下SQLite的数据库文件每天增长约80MB一个月下来接近2.4GB。这个体量对我目前几十台机器的场景完全可接受。如果机器规模超过一百台我建议直接把metrics表按天做分表或者切换到时序数据库。但那是后话在一百台以内保持当前架构反而是最高性价比的选择。5. 踩坑记录与排查技巧实录5.1 采集器内存涨个不停线程泄漏的教训上线第三天我发现Agent的RSS内存从30MB涨到200MB一周后直接OOM被杀。排查过程很有代表性先用tracemalloc统计内存分配发现大量对象停留在psutil.net_io_counters()的结果里。原因是老版本psutil在某些内核上每次调用会缓存对象必须手动释放引用。把采集函数改为每次重新生成变量并主动del后内存稳定在45MB左右。经验就是监控程序自己首先要稳给自己做监控。5.2 半夜告警轰炸收敛策略不能少初期我没有做告警收敛某次一台机器磁盘IO卡了半小时CPU、磁盘、端口三条规则同时触发每条规则每10秒推一条Webhook结果一晚上群里刷了上千条消息。后来我做了三个措施同一个host_id rule_id在FIRING状态下不重复推送只有恢复后才允许下一次触发告警级别低于critical的凌晨按静默窗口处理FIRING超过30分钟仍未恢复的主动重推一次防止值班人员漏看。这套收敛策略上线后告警群基本安静了。真正故障时该收到的消息一条不少非故障的噪声全部挡住。5.3 容器里时间不准导致误报Docker容器默认使用UTC时间而我们的应用日志和告警时间都用北京时间。最开始没设置TZ环境变量结果告警消息里显示的时间和实际差8小时排查时把人绕晕了。根因就是Compose文件里没加TZAsia/Shanghai。加上之后所有容器时间统一告警判定和消息时间戳才对齐。5.4 SQLite写入锁优化报表接口和写入接口并发时SQLite偶尔会报数据库锁定。我的修复方案是开WAL模式并且把高频写入改成批量事务。连接数据库后执行PRAGMA journal_modeWAL; PRAGMA synchronousNORMAL;WAL模式下读和写在锁上互不阻塞实际压力测试中写入性能和并发都提升明显。这条对任何用SQLite做采集存储的人都适用。5.5 Webhook消息悄悄丢失企业微信机器人偶尔会静默丢消息尤其是消息体里带了超大JSON时。排查后发现主要是URL没有做转义消息被截断。统一模板后所有告警内容控制在200个汉字以内并且用markdown格式发送丢失问题再没复现。5.6 常见问题速查表症状可能原因处理方式Agent启动后仪表盘不显示防火墙未放行8080端口在Agent机器上telnet测试端口连通性CPU指标一直为0psutil首个采样值未预热Agent启动时主动调用一次cpu_percent群消息重复刷屏缺少告警收敛逻辑确认同一事件只推送一次告警时间差8小时容器未设置时区设置TZ环境变量为Asia/ShanghaiSQLite报database is locked高频并发写入开启WAL模式批量写入Scanner显示主机离线Agent进程退出检查systemd守护和Agent日志这些坑不踩一遍很难意识到但踩完之后PLFM_RADAR的稳定度确实是肉眼可见地提升。尾声这套雷达下一步还能怎么长我自己在实际使用中最大的体会是监控系统做得再漂亮如果告警不准、不及时那它就是个摆设。PLFM_RADAR把三个基础环节——采集、判定、通知做扎实了比上任何花哨的看板都管用。后面我会给Agent加上自动发现能力让新机器部署完自动注册再把告警静默窗口做得更细支持按主机组配置。PLFM_RADAR这套思路本身很简单核心价值在于它把监控这件事拆到足够小小到每个人都能自己掌控。如果有团队正好缺一个轻量监控入口不妨照这个框架试一遍大概率会比上个全家桶方案更顺手。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询