从模糊代号到运行系统:自研轻量资源监控工具rea的完整实践

发布时间:2026/10/21 23:16:33
从模糊代号到运行系统:自研轻量资源监控工具rea的完整实践 很多朋友看到rea这个标题时大概率会以为打错了字或者觉得这是某个库的缩写。我要先坦白一件事——它最初真没有什么标准全称只是某天我在终端里随手敲的三个字母用来命名一个空目录。后来这个目录慢慢长成了一个完整的资源效率分析工具Resource Efficiency Analyzer内部代号缩写就是rea专门负责采集服务器的CPU、内存、磁盘与网络指标再把这些数据整理成实时看板帮我从被动救火式运维变成主动观察式管理。这篇文章就把一个模糊标题如何变成一个可运行系统的完整过程写出来适合手里有个想法、但还没想清楚架构的开发者参考也适合那些想自建一套轻量监控体系、又不想直接套用重型全家桶的朋友。1. 先给 rea 一个定义从模糊代号到明确需求1.1 代号背后的真实需求别再让服务器走在下线边缘见过凌晨两点的告警邮件吗说的不是那种CPU 忽高忽低再观察一下的假警报而是磁盘使用率冲到 98%数据库进程直接崩掉然后你在睡梦中被电话叫起来眯着眼睛查哪张表把空间吃光了。我经历过几次这种场景之后决定不能再靠感觉运维了。我需要一个工具能持续地告诉我每一台机器的资源消耗长什么样不是事后翻日志而是事前能看到趋势。rea 的第一个需求就这么定下来了采集多台服务器的系统资源指标存入数据库用图表展示历史趋势。听起来很简单但里面藏了几个硬约束。一是采集不能太影响业务agent 本身要轻二是数据量会持续增长存储和查询得接得住三是展示要让人能快速定位问题不能打开看板还要等十几秒。给项目起代号的时候我压根没花心思。rea 就是 resource efficiency analyzer 的缩写顺手打的也没打算对外发布。但正式动手前我给自己写了一段项目说明用最低的运维成本记录基础设施的资源水位让趋势可见、异常可查。这段话后来帮我挡掉了不少需求膨胀的冲动谁再说顺便加个 XX 功能我就把这段话翻出来给他看。1.2 谁是它的目标用户我为什么不做成通用监控平台市面上的监控平台很多从老牌开源方案到商业全家桶随便挑一个都能用。但我做 rea目标用户从一开始就只有两类一个是我自己另一个是和我类似的小团队——机器数量在 5 到 30 台之间没有专职运维预算有限又不想为了看几个指标把整套重型框架搬回家。自研的边界一定要划清楚。我不追求覆盖所有监控场景也不打算做高可用集群。rea 要解决的是 80% 的常见问题机器负载有没有异常、内存是不是长期吃紧、磁盘大概还有多少天会被写满、网络流量是不是有突发。至于分布式链路追踪、日志检索、APM 这类能力那是另外的战场硬塞进来只会让项目失焦。而且自建这条路有个隐形好处整个系统从采集到展示都是自己写的每个环节都清楚出了问题不会像黑盒一样无从下手。公司里如果买了商业产品排障时只能提工单用开源方案遇到不满足的场景得读源码改插件。而 rea 是我自己的我想让它支持什么业务指标直接加一个字段就行这种掌控感用钱买不到。2. 技术方案怎么定三层结构是小项目的保命符2.1 为什么第一反应不是全家桶监控系统有现成的开源监控组合可用为什么要自己写这个问题我在动手前认真回答过自己。最直接的原因是学习成本。全家桶方案确实强大但它的学习曲线很陡配置项非常多默认参数不一定适合你的场景踩坑时要在告警规则、服务发现、模板变量之间来回排查。我见过不少团队把监控系统装起来了结果没人会配告警阈值最后变成一个昂贵的图表库。另一个原因是业务指标接入的灵活性。系统监控指标可以用现成方案采集但业务指标比如某个核心接口的响应时间、某条消息队列的积压数量、今天新增的订单量这些需要业务代码主动上报。与其在自研系统之外再接一套上报通道不如把 rea 做成一个通用的指标接收器无论是系统数据还是业务数据只要按约定格式推过来就能落库、查询、展示。当然现成方案也有它的价值。我并没有完全否定它只是在 rea 这个特定项目里我希望它足够轻、足够可控。如果哪天机器规模涨到上百台需要服务发现、自动告警、多租户隔离这些能力到时候再评估迁移也不迟。先解决眼前的问题不为了未来可能的规模提前背上复杂度的包袱。2.2 前后端与存储选型的一个参考组合rea 的技术栈没有用花哨的新框架全是经得起时间考验的组合。前端选了 Vue3 加 TypeScript用 Vite 做构建工具图表库用 ECharts。后端用 Python 的 FastAPI接口写起来快性能也够用。存储这块是重头戏选的是 TimescaleDB跑在 PostgreSQL 之上既保留关系数据库的熟悉感又针对时序数据做了专门优化。选择依据其实很务实环节选型核心理由前端框架Vue3 TypeScript Vite组件化清晰类型安全开发效率高图表库ECharts折线图、面积图开箱即用大数据量渲染有内置优化后端框架Python FastAPI异步支持好自动生成接口文档开发速度快数据存储TimescaleDB原生支持时序表分区压缩率高SQL 查询降低成本部署方式Docker Compose Nginx一套命令拉起全部依赖适合小规模场景为什么不用 MySQL 或者 MongoDBMySQL 存时序数据不是不行但数据量上来后按时间范围删除旧数据、按时间窗口聚合这些操作都会变得别扭。MongoDB 文档模型灵活但聚合查询写起来不如 SQL 直观而且对时间序列的压缩能力几乎没有。TimescaleDB 本质上还是 SQL我可以用熟悉的 JOIN 和 GROUP BY同时获得自动分区和压缩带来的性能红利这是它最打动我的地方。2.3 一次架构的简化-膨胀-再简化很多个人项目死在想得太多上rea 也不例外。最初画架构图的时候我列了一堆组件消息队列缓冲采集流量流处理引擎做实时聚合NoSQL 存原始数据宽表存聚合数据再配一套 Redis 做缓存前端还要考虑 WebSocket 推送。画完之后自己看着都害怕这哪是个人工具这是照着大型互联网公司的基础设施在搭。后来我做了个减法把架构压缩成三条链路采集器定时把指标 POST 到后端接口后端写入数据库前端通过查询接口取数绘图。中间的消息队列和流处理全部去掉理由是数据量没有大到需要异步削峰的程度一台采集器 10 秒上报一次20 台机器也就每秒两条请求FastAPI 处理这点请求绰绰有余。这个砍需求的过程让我学到一点小项目最值钱的优势就是简单任何你觉得以后可能用得上的组件都先记在文档里而不是装进代码里。真到了需要消息队列的那天再把它加进来而不是一开始就为想象中的人流量修一条十车道的高速公路。3. 核心链路落地采集、写入、查询、展示一个都不能少3.1 数据表设计时序数据的第一课rea 落地的第一个关键环节是数据库表结构。时序数据的特点是写多读少、数据只追加、旧数据需要定期清理所以表结构不能照搬普通业务表。我建的第一张表是主机资源指标表核心字段包括采集时间、主机名、CPU 使用率、内存使用率、磁盘使用率、网络流入量、网络流出量以及一个 JSONB 类型的扩展字段。建表语句如下CREATE TABLE host_metrics ( time TIMESTAMPTZ NOT NULL, host TEXT NOT NULL, cpu_usage DOUBLE PRECISION, mem_usage DOUBLE PRECISION, disk_usage DOUBLE PRECISION, net_in BIGINT, net_out BIGINT, tags JSONB DEFAULT {} ); SELECT create_hypertable(host_metrics, by_range, time);这里有几个设计细节值得展开说一下。第一个是为什么用TIMESTAMPTZ而不是TIMESTAMP。时序数据最怕时区混乱如果用不带时区的TIMESTAMP采集器从不同时区的机器上报数据时时间轴会错位。统一用带时区的类型所有时间都按 UTC 存储展示层再按用户时区转换这是最不容易出错的方案。第二个是为什么保留一个tags字段。单台机器的指标字段是固定的但业务上可能想给不同机器打标签比如区分线上环境、测试环境或者标注机器所属的可用区。把这些维度塞进单独的列会很死板放在 JSONB 里就灵活多了查询时用tags-env就能做过滤。第三个是为什么time和host不加联合主键。刚开始我也习惯性地想加后来实测发现唯一约束会严重拖慢写入速度时序数据本身就带有时间戳这个天然标识重复数据靠应用层去重就够了数据库层的唯一性约束在这里性价比很低。3.2 写一个不添乱的采集器采集器是 rea 的神经末梢它要足够轻不能给被监控的机器添乱。我用 Python 的 psutil 库实现资源采集这个库的优势是跨平台、接口稳定、文档全Linux 和 Windows 都能跑对个人项目来说覆盖面足够了。核心采集逻辑比较直接import socket import time import requests import psutil INTERVAL 10 ENDPOINT http://rea-server:8080/api/metrics def collect_once(): return { host: socket.gethostname(), cpu_usage: psutil.cpu_percent(interval1), mem_usage: psutil.virtual_memory().percent, disk_usage: psutil.disk_usage(/).percent, net_in: psutil.net_io_counters().bytes_recv, net_out: psutil.net_io_counters().bytes_sent, ts: int(time.time()) } while True: try: data collect_once() requests.post(ENDPOINT, jsondata, timeout3) except Exception as exc: log_to_file(freport failed: {exc}) time.sleep(INTERVAL)代码看着简单但有几个细节如果处理不好后续会相当难受。第一个是cpu_percent的interval参数。psutil.cpu_percent(interval0)是绝大多数初学者的写法结果往往返回 0因为第一次调用没有历史采样数据。代码里传interval1含义是阻塞 1 秒、计算这 1 秒内的平均使用率这样第一次拿到的值就是可信的。当然这 1 秒阻塞是可以接受的毕竟采集频率本来就不高。第二个是网络流量读的是累积值而不是速率。bytes_recv和bytes_sent是从开机以来的累计字节数存到数据库里之后绘图时要用当前值减去上一时刻值再除以时间间隔才能得到平均速率。我可以选择在采集器里先把速率算好也可以把累积值存下来交给查询端计算。我选了后者理由是想保留最原始的计数这样即使后来想调整时间窗口也能灵活重算。第三个是采集器不需要花哨的并发能力单线程循环加异常隔离就足够了。上传失败不能影响采集日志要写到文件而不是控制台不然服务跑起来之后根本看不到报错。启动之后先用一次cpu_percent(None)做预热避免第一次上报的数据点失真这也是容易忽略的小坑。3.3 服务端接口别让单条写入拖垮写入性能后端接口是整个链路的中转站它要同时面对两个问题来自多台采集器的持续写入请求以及来自前端的时间范围查询请求。写入接口的写法一定要考虑批量处理。最直观的做法是每收到一条数据就 INSERT 一条但这样做数据库压力会大连接频繁开关TPS 一口就被压死了。我采用的方案是内存队列加定时 flush。接口收到指标后先把数据放进一个线程安全的队列后台任务每隔 5 秒或者队列累积到 500 条时统一执行批量插入。这样数据库每次操作面对的是一批数据写入吞吐量能差出一个数量级。下面是简化后的核心结构import asyncio import asyncpg from fastapi import FastAPI app FastAPI() _batch [] async def flush_batch(): if not _batch: return records _batch[:] _batch.clear() async with pool.acquire() as conn: await conn.executemany( INSERT INTO host_metrics (time, host, cpu_usage, mem_usage, disk_usage, net_in, net_out, tags) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) , records ) app.post(/api/metrics) async def receive_metrics(item: MetricItem): _batch.append(( datetime.fromtimestamp(item.ts, tztimezone.utc), item.host, item.cpu_usage, item.mem_usage, item.disk_usage, item.net_in, item.net_out, json.dumps(item.tags) )) if len(_batch) 500: await flush_batch() return {status: ok}这个设计的关键不只是批量还包括失败重试和容量控制。如果后台 flush 任务抛异常不能把队列里已有的数据丢掉要记录日志并保留备份否则会出现看板缺数据的诡异现象。队列本身也需要一个上限比如最大 5000 条超过上限的请求先返回错误而不是无限堆积内存否则采集器一旦异常大量上报后端的进程内存会被撑爆这时候反而需要采集端主动降频。3.4 查询与前端看板如何让页面不卡数据写进去之后查询接口要能支持时间范围、主机过滤、指标选择这些常见条件。最关键的一个优化是降采样。一张时序表里如果存了数十万条数据前端小屏幕根本画不下强行渲染只会让页面卡死。SQL 层用time_bucket函数把秒级数据聚合成分钟级或小时级数据曲线既平滑又轻量这才是正确姿势。一个典型的趋势查询是这样的SELECT time_bucket(1 minute, time) AS bucket, host, avg(cpu_usage) AS cpu_avg, max(cpu_usage) AS cpu_max FROM host_metrics WHERE host $1 AND time $2 AND time $3 GROUP BY bucket, host ORDER BY bucket;前端看图只关心大体趋势分钟级均值足够用了。如果用户想看某个瞬间的精确值再加一个下钻接口返回原始明细数据这样的两级策略兼顾体验和性能。前端看板用的是 Vue3 加 ECharts。整体布局分三块顶部是时间范围选择器左侧是主机列表中间是多个指标折线图。主机列表支持多选选中的每台机器用不同颜色区分时间范围改变后所有图表联动刷新。实现联动时有一个并发问题要注意多个请求同时发出返回顺序可能不一致后返回的旧数据不能覆盖新数据。我用请求序号做了一层校验只渲染最后一次发起的请求结果这个细节花了我一晚上才定位到。4. 部署细节与踩坑实录rea 从本机跑到服务器的过程4.1 Docker Compose 一键拉起整套环境rea 的部署目标是一台 2C4G 的云服务器所有组件用 Docker Compose 管理。这个选择意味着环境依赖被全部封进容器换机器、重新部署都很从容不用在一台新服务器上手动装 Python、Node、PostgreSQL也避免版本不一致的问题。docker-compose.yml 的骨架如下version: 3.8 services: timescaledb: image: timescale/timescaledb:latest-pg16 environment: POSTGRES_PASSWORD: rea_db_pass POSTGRES_DB: rea volumes: - rea_db_data:/var/lib/postgresql/data ports: - 5432:5432 rea-server: build: ./server environment: DATABASE_URL: postgresql://postgres:rea_db_passtimescaledb:5432/rea depends_on: - timescaledb ports: - 8080:8080 rea-web: build: ./web ports: - 80:80 depends_on: - rea-server volumes: rea_db_data:容器化部署里有几个容易踩的点。第一个是数据库数据卷必须单独挂载否则容器一删数据全没。第二个是容器之间通信用服务名而不是 IP因为每次重建容器 IP 都可能变而timescaledb这个服务名在 Compose 网络内是稳定的。第三个是整个链路的前端与后端分离部署前端服务不仅负责提供静态页面还要用 Nginx 反向代理后端接口这样页面里的请求地址可以统一写相对路径避免浏览器跨域问题。4.2 我踩过的四个坑每一个都是性能杀手rea 从能跑变成好跑的过程中我踩了不少坑捡四个印象最深的分享出来。第一个是唯一约束拖垮写入性能。开始建表时我习惯性地加了PRIMARY KEY (host, time)想着防止重复数据结果写入一压测TPS 只有几百完全不是 TimescaleDB 该有的水平。去掉唯一约束之后写入吞吐直接翻了几倍。时序场景下重复数据很少偶尔重复也可以容忍数据库不应为不可能发生的情况买单。第二个是时区混乱导致曲线偏移八小时。采集器上报的是 UTC 时间前端 ECharts 默认按浏览器本地时间格式化看起来所有曲线都平移了一段。折腾了半天才发现是时区问题。现在我的原则是通信全程用时间戳展示层转成用户本地时间数据库里统一存 UTC三层各管一段绝不混用。第三个是cpu_percent首次调用返回 0。这个问题说起来有点可笑但新手真的会碰到。psutil 的 CPU 使用率计算依赖前后两次调用之间的差值第一次调用没有基准直接返回 0。在采集器启动后先做一次预热调用问题就消失了。第四个是前端图表一次性拉全量数据导致浏览器崩溃。最初我没做降采样一次查 7 天秒级数据几万甚至十几万个点ECharts 直接卡成幻灯片。后来改成time_bucket降采样加 ECharts 自带的sampling: lttb图表不卡了加载速度也快了。在数据可视化里聪明地丢弃细节比死磕完整性重要得多。4.3 常见问题速查表把日常运行中出现过的问题整理成一张速查表方便自己排查也方便后来者现象可能原因处理方式看板长时间没有新数据采集器进程挂了或网络不通检查采集器日志telnet 测试接口连通性数据库磁盘快速增长保留策略未配置旧数据无限堆积添加定时任务删除超期数据图表曲线出现缺口批量写入失败且无重试检查后端日志确认 flush 队列是否丢失数据跨天数据时间轴错乱采集端时区与数据库不一致统一用 UTC 时间戳展示层再做时区转换多台机器曲线颜色重复前端只按主机名取色主机名过短改用主机列表的索引作为颜色区分依据查询接口响应越来越慢没有按时间范围建立索引确保time列有索引查询尽量带时间下推条件5. 给 rea 留出的后路从监控工具变成分析平台5.1 告警、预测、权限优先级怎么排rea 目前已经能稳定采集和展示了接下来要扩展的方向按优先级排告警第一预测第二权限第三。告警最好做因为数据已经有了只需要在后端加一个定时检查任务对最近一分钟的指标做阈值判断触发条件时把消息推到通知渠道。这个功能能极大降低人肉盯看板的频率算是对自由时间的解放。预测功能更有意思也更能体现数据分析的价值。比如磁盘使用率可以根据过去三十天的增长趋势做线性拟合估算出按当前速度还能用几天。这种预测不需要复杂的机器学习模型最小二乘法就够了但它的提醒效果比生硬的阈值告警好得多让人有充分的缓冲时间去清理或扩容。权限这块暂时不急因为目前只有我自己在看。但如果未来某个团队要用多用户体系就必须考虑了。我的思路是在 API 层面加一层 Token 鉴权前端每个请求带上身份标识后端按用户维度限制可见的主机范围。暂时不需要引入完整的关系权限模型保持简单等需求真的来了再细化。5.2 业务指标接入后看板才真正有生命力系统资源指标只能告诉你基础设施健不健康但业务指标才是决定要不要立刻起床的关键。rea 的上报接口是通用的这意味着业务代码可以把关键指标直接 POST 进来和系统指标存在同一张链路里看板就能把业务异常和资源异常对照着看。比如某个核心服务响应时间突然上涨很可能是部署机器 CPU 被打满导致的。只看业务指标你只知道慢了只看 CPU 指标你不知道它影响到什么。两个维度叠加在一个时间轴上看因果关系就清晰多了。我的做法是给业务指标加一个metric_type标识字段系统指标使用system业务指标使用business查询时既可以在同一张图上叠加展示也可以按类型分开过滤。我在实际使用中还有一个体会是自研监控最有价值的点不是省掉了采购成本而是在这个过程中建立的数据第一的观念。当你连续观察了一段时间的曲线之后你会开始相信数据而不是直觉。以前我总觉得机器快不行了打开看板一看流量平稳心里就踏实了有时候我感觉一切正常但曲线显示内存缓缓爬升反而能提前发现隐患。这种感觉用任何人都给你装不上的监控配方也换不来。最后再分享一个小技巧如果你也想做类似的项目名字叫什么并不重要哪怕像我一样随手敲三个字母也行。重要的是从第一天开始就把数据模型当成地基来打采集频率、存储格式、时间精度这些基础决策一旦定了后面很难改改一次就牵一发动全身。先把地基打稳再慢慢加楼层。

关于本文作者

来自尧图内容编辑团队

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

尧图内容编辑团队

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

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

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

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

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

网站改版的5个关键决策

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

获取专属建站方案

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

立即免费咨询