
上周三下午四个仓库的 CI 同时跑完最后一步release 页面都挂上了 v1.0.0 的 tag。我盯着屏幕松了一口气这套从去年 Q4 开始折腾的监控链路总算在同一天齐刷刷发版了。四个项目分别是采集端 probe-agent、时序存储 mintdb、可视化看板 vizboard 和告警网关 alert-hub拆开是四个独立小工具合起来就是一条完整的监控链路。这篇文章不聊 PPT 架构只讲我从零把它们搭起来的过程以及这条路上踩过的坑——如果你也在纠结自己动手搭监控到底值不值可以拿这篇做个参考。1. 四个项目一天全发我为什么非要凑齐整条链路1.1 从脚本报警到自动化差的是这一整条链路先说背景。我们团队负责的业务高峰期 QPS 大概几千核心服务分散在 4 台云主机和一个小型 K8s 集群上。以前监控两个字对我们来说就是几个 shell 脚本crontab 每分钟跑一次curl 一下接口再 grep 一下日志有问题发一封邮件、往钉钉群里扔一条消息。这个东西能用但问题非常清楚——脚本是分散的指标是割裂的历史数据几乎留不下去。想看一眼昨天同一时段的 CPU 曲线比登天还难系统出一个诡异故障排查基本靠人肉翻日志。后来也引入过现成的监控平台运维成本倒是不高但有几个点让我始终不太舒服。一是告警通知的通道太固定想接自家 IM 机器人、短信网关都得改代码。二是多套监控系统并存时数据口径对不上同一套服务在 A 系统显示正常在 B 系统显示过载对排查来说反而是负资产。三是团队本身有 Go 开发能力与其一个劲儿地给开源方案打补丁不如把链路里最核心的几个环节握在自己手里。我当时的判断很明确我们缺的并不是某一项监控能力而是一条从采集、存储到展示、告警的完整链路——链路通了监控方法论才算真正落地。1.2 不仅是一条链路还是四个可独立交付的开源项目我给自己定了一个稍微贪心的目标这条链路要拆成四个可以被社区独立使用的开源项目。每一块单拿出来都有价值组合起来又能形成完整闭环。于是就有了下面这张规划表环节项目名解决的问题采集probe-agent把系统、应用、网络指标标准化地采集出来存储mintdb承接多源时序数据提供统一且长期的查询接口展示vizboard把指标变成直观、可交互的监控看板告警alert-hub统一告警规则、去重、路由和多渠道触达四个项目同步发版其实有一个很现实的原因版本之间的协议是联动的。probe-agent 的指标协议如果升级mintdb 的数据解析层也要跟着升级vizboard 依赖 mintdb 的查询接口alert-hub 要消费 Prometheus 风格的告警消息。单独发任何一个都可能打破整条链路的兼容性所以干脆定在同一时间点四个仓库一起打 v1.0.0把协议层冻结成一个快照。同步发版本质上是在替用户守住链路可用这条底线。有人可能会问市面上不是已经有夜莺监控这种全家桶式的开源项目吗确实有而且做得很好。但我们当时做技术选型不追求一步到位更希望把链路里的每一环拆成可插拔的零件按自己团队的节奏演进。后来的实践证明这种做法前期会累一些但维护和二次开发的时候边界非常清晰。2. 链路拆解一条完整监控链路到底包含哪几个环节2.1 采集环节指标不会自己跑到数据库里很多人谈监控开口就是 Grafana 面板多炫、PromQL 多强力但真正的入口是采集。采集环节要回答两个问题采集什么以及怎么采集。采集什么取决于监控对象。服务器层面的 CPU、内存、磁盘、网络流量是基础指标应用层面的 QPS、延迟、错误率、JVM GC是业务指标网络连通性、证书过期时间、DNS 解析耗时属于探测指标。对象不同采集方式也不同。如果不统一格式后面的存储和展示就得给每种格式分别做适配。所以我们最开始就在协议层定了规矩所有指标统一用 OpenMetrics 文本格式输出标签语法对齐 Prometheus 那套这样后面所有环节都只需要认一种格式。怎么采集这里有个经典的推拉之争。Prometheus 用 Pull 模式由服务端主动去目标拉取指标Zabbix 这类平台则更习惯 Agent 主动推送。Pull 的好处是服务端可控性强目标挂了立刻就能感知采集节奏由服务端统一调度坏处是要求目标能被服务端网络访问到有些云下场景难以直连。probe-agent 在终端做被动暴露同时内置一个轻量 Push 模式两种通道都支持纯内网和跨组网的情况都能把数据送出来。这里必须强调一个容易犯的低级错误不要在指标标签里塞随机 ID、用户昵称之类的高基数字段。我第一次上线采集器的时候图省事把一个业务单号塞进标签结果 Prometheus 内存直接翻了六倍。这是典型的数据格式设计失误后面存储和查询全在替这个错误买单。2.2 存储环节时序数据为什么不能用普通数据库采集到的指标本质上是一串随时间变化的数字属于时序数据。它的特点很鲜明写入几乎只追加极少更新和删除查询通常是按时间段做聚合和降采样数据量巨大一台机器几十个指标、15 秒存一个点存一年就是千万甚至上亿行。如果拿 MySQL 去存写性能先被拖垮查询更是灾难——类似SELECT AVG(cpu) FROM metrics WHERE time ...的查询走普通索引效率低得没法看。所以业内几乎没有分歧直接用专门的时序库。常见选择有 Prometheus 自带的 TSDB也有对多租户和长期存储支持更好的 Thanos、VictoriaMetrics、Mimir。我自研的 mintdb 不是要从零做一个列式存储引擎而是在 Prometheus 协议兼容层之上解决两个问题一是接收 Remote Write 数据把来自多个 Prometheus 实例的数据统一落到一个长期存储里二是提供可水平扩展的查询能力让看板不依赖某一个 Prometheus 实例的本地数据。时序存储这块我吃过最大的亏是分片和压缩策略。最开始把每个指标当成独立分片存储内存里维护一棵巨大的索引树数据量到 3000 万条就频繁 OOM。后来改成按时间窗口分块、按标签哈希分片再把标签字典单独拎出来压缩才真正跑得动长期数据。索引膨胀这个问题只要你用 InfluxDB、Prometheus 这类工具早晚都会遇到提前设计好能省很多麻烦。2.3 展示环节看板不只是画图采集和存储是地基到了展示环节才算真正面向人。一个监控看板要做的事情不是把一堆折线图拼在一起而是解决三个问题。第一信息组织。一台服务器 CPU 有 idle、system、user、iowait 一堆指标全部画上去正常曲线和异常曲线混在一起人根本看不出哪里有问题。我们预置的看板模板里每张图都有明确的主题容量趋势、延迟分布、错误比例。模板好不好就体现在打开一张图的 3 秒内你能不能判断出今天这个系统健不健康。第二操作效率。监控系统不只是给运维看的开发排查问题时也会用。如果看板只能看首页那一屏想看某个实例的详细数据还要手动改查询语句基本等于废了。做 vizboard 的时候我特意支持了从总览下钻到具体实例的能力点击卡片切换视角配合自动生成的 PromQL 模板让不懂 PromQL 的开发也能完成大部分排查。第三实时性。传统看板靠前端定时刷新10 秒一次已经算不错。但在故障发生时10 秒延迟会让人非常焦虑。vizboard 后端接了 WebSocket指标变化秒级推送到浏览器。后来线上连接数一多WebSocket 压力变大又改成 WebSocket 低频轮询兜底的双通道方案既保证体验又防止监控服务自己被压垮。2.4 告警环节再好看的看板也比不上一条准确的报警看板是给人看的但半夜没人一直盯着它。监控链路的最后一环是告警把异常迹象翻译成一条能让值班人快速行动的准确消息。告警要解决的核心不是怎么把消息发出去而是哪些事件需要发、什么时候发、怎么避免打扰。一条告警从产生到触达会经过四个步骤规则评估定时跑查询判断阈值是否触碰、收敛降噪同一问题不要反复轰炸、路由分发按告警级别和模块发给对应团队渠道、通知送达钉钉、企业微信、飞书、邮件等适配。做 alert-hub 的时候我刻意把规则引擎和通知引擎拆成两个模块。规则引擎负责算通知引擎负责发。这样做的好处是告警规则可以在 alert-hub 里统一管理不用散落在 Prometheus 配置文件通知渠道的对接只需写一个新的 adapter完全不碰规则逻辑。我个人认为告警系统最值得投入精力的永远是降噪——一个每天发 200 条告警的系统和一天只发 5 条但每条都能让人行动的系统后者的价值高出一个数量级。链路拆到这里就清晰了probe-agent 采集数据 → Prometheus 收一轮做短存和规则评估 → mintdb 长期保存 → vizboard 从 mintdb 取数展示 → alert-hub 统一分发告警。下面逐个说一下四个项目具体做了什么、怎么用。3. 四个项目逐个拆解从 probe-agent 到 alert-hub3.1 probe-agent用一半代码干完两件事probe-agent 的定位是轻量采集器覆盖两类能力本机基础指标采集和主动探测。我没有采用 node_exporter blackbox_exporter 两套工具并存的方案而是把它们合并成一个二进制。启动后probe-agent 默认暴露一个/metrics端口输出 OpenMetrics 格式的基础指标同时按配置文件里的任务列表定时执行 HTTP、TCP、ICMP、DNS 探测把结果也写进指标流。为什么合并而不是各用各的因为大多数人监控的都是几十台服务器的中小规模场景不需要为每种目标各装一个 exporter。一个 agent 干完所有事部署和运维成本最低。配置长这样# probe-agent.yaml sources: - type: system interval: 15s - type: http name: api_health url: http://127.0.0.1:8080/healthz expect: status: 200 labels: module: order - type: script name: redis_replication command: /opt/scripts/redis_repl_lag.sh interval: 60stype: system是内置的系统指标采集type: http是 HTTP 探测type: script允许用户跑任意脚本并把输出的 key value 解析成指标。script 能力非常重要团队的运营同学算完订单转化率也是靠这个通道把业务指标打进来的。一个很值得说的设计所有采集任务都支持单独配置interval。系统指标 15 秒采一次没问题脚本类任务如果太重放宽到 1 分钟或 5 分钟也完全可以。这看起来不是什么高端特性但直接决定了 agent 对宿主机的影响。实测一个标准 agent 在四核机器上 CPU 占用不到 1%内存约 40MB可以放心铺到每台服务器上。3.2 mintdb给 Prometheus 生态做一套长期数据底座mintdb 最初叫 mini-tsdb后来觉得不好听才改名。它的核心任务是承接多个 Prometheus 实例通过 Remote Write 推过来的时序数据沉淀成一份长期可查的监控数据。为什么必须做这一层因为 Prometheus 自带 TSDB 但默认只留 15 天单机内存和磁盘开销受数据量限制。哪怕按照网上教程把 retention 改到 180 天几千万条数据堆进去之后查询慢、内存爆炸迟早会来。mintdb 的技术栈比较克制底层用 Badger 嵌入式 KV 库存原始数据块上层做标签索引和时间聚合层。数据按2 小时时间窗口 标签哈希分片落盘窗口内数据在内存里攒批满了就压缩成不可变文件类似 LSM 的 SSTable。查询走 HTTP API兼容 PromQL 的常用函数和区间查询。协议兼容是我最看重的一件事所以 mintdb 直接实现了 Prometheus 的 remote read 协议。这意味着只要打开 Prometheus 的--web.enable-remote-read-receivermintdb 就能作为 PromQL 数据源挂进任何支持 Prometheus 数据源的看板vizboard 对接的也是它。存储大小控制我做过一次痛苦调优最初一个月的原始数据约 2.6 亿个数据点占了将近 80GB 磁盘后来把时间块压缩算法从简单的 delta-of-delta 改成 gorilla 编码风格体积直接降到 28GB 左右。这种优化属于典型的性能问题只有数据量大之后才会浮出水面早期根本意识不到。3.3 vizboard开箱即用但又能下钻的监控看板vizboard 的定位很明确装完就能用不需要从零拖面板。它预置了三类模板——主机资源CPU、内存、磁盘、网络、中间件Redis、MySQL、Nginx 基础指标、应用探测HTTP 成功率、延迟、证书剩余天数。模板里写好了常用的 PromQL 查询用户接好数据源后第一屏就能看到清晰的状态总览。实现不算复杂前端 Vue3 ECharts后端 Go 读取 mintdb 或 Prometheus 的 HTTP API把查询结果转成 ECharts 需要的结构化数据。但有两个点我认为才是它能在团队里活下来的关键。第一看板和目录是 YAML 文件定义的可以直接提交 Git 做评审和版本管理而不是只能在 UI 上慢慢拖。第二它支持变量联动模板里预留了instance、module这些变量总览页选择某个实例后下面所有图表会自动重新查询。开发排查问题时路线就变成了总览发现问题 → 选择实例 → 看细节指标心流很顺畅。3.4 alert-hub把告警做成人话alert-hub 负责整条链路里最后一公里的体验。它不是一个简单的告警机器人而是独立的告警处理服务。收到来自 Prometheus Alertmanager Webhook 的消息后依次处理四件事聚合、去重、路由、发送。聚合5 分钟内同一规则引发的告警合并成一条附带触发实例列表。去重同一实例的同一告警在静默期内不会重复发送。路由根据告警名里的模块前缀比如order、pay找到对应的钉钉群、企业微信群、邮件组。发送走各渠道的 Webhook 或 SMTP失败自动重试 3 次。配置文件很直观routes: - match: module: order notify: - dingtalk: https://oapi.dingtalk.com/robot/send?access_tokenxxxx - email: opsexample.com - match: severity: critical notifications: - phone_call: 86-13800138000配置文件支持热加载改完不需要重启服务。每次调告警路由我最怕的就是改个配置还要整个服务重启alert-hub 从 V0.2 开始支持 SIGHUP 热加载这条经验强烈建议所有做类似工具的团队采纳。前面讲的是四个项目各自的定位和核心逻辑。接下来是整篇文章里最实操的部分如果你想从零搭一条可用的监控链路具体该怎么落地。4. 从零组装整条链路一台云主机上的落地全记录4.1 最小化部署清单与版本匹配先说原则自研再多也没必要重复造所有轮子。我的落地组合是 probe-agent Prometheus mintdb vizboard alert-hub。Prometheus 作为规则计算引擎和短期存储其他环节用自研项目。这个组合的好处是即使先只上 Prometheus alert-hub也能立刻获得一套可用的告警系统再补上 mintdb 和 vizboard链路就完整了。部署方式推荐直接上 Docker Compose。机器规模不大时4G 内存的云主机就够但建议给 mintdb 单独规划一块 50GB 以上的数据盘。版本匹配需要留意Prometheus 建议 2.40 以支持最新的 remote write 特性mintdb 和 vizboard 要与它保持同一次发版的 tag。生产环境全部锁定版本号不要用 latest。services: prometheus: image: prom/prometheus:v2.50.1 volumes: - ./prometheus.yml:/etc/prometheus/prometheus.yml probe-agent: image: your-registry/probe-agent:v1.0.0 network_mode: host mintdb: image: your-registry/mintdb:v1.0.0 volumes: - ./mintdb-data:/data vizboard: image: your-registry/vizboard:v1.0.0 ports: [3000:3000] alert-hub: image: your-registry/alert-hub:v1.0.0 ports: [8089:8089]这里一个容易踩的点probe-agent 最好用network_mode: host因为系统指标采集依赖宿主机/proc下的信息容器网络模式下/proc是隔离的采到的 CPU、内存数据会严重失真。如果用 Kubernetes 部署则要单独配置/proc的 hostPath 挂载。这个坑我们当时踩得莫名其妙数据平平的看了半天才发现是容器文件和宿主机的对不上。4.2 打通采集到存储的数据通路链路的第一根管道是采集端到 Prometheus。在 prometheus.yml 里添加 scrape 任务scrape_configs: - job_name: probe-agent scrape_interval: 15s static_configs: - targets: [你的宿主机IP:9118]端口 9118 是 probe-agent 默认 metrics 端口。这里有个细节Prometheus 是拉取模式宿主机防火墙必须放行 9118 端口否则 scrape 永远报 dial tcp 超时这是新手上手最容易卡住的地方。第二步是让 Prometheus 把数据转给 mintdb。在 prometheus.yml 里加remote_write: - url: http://mintdb:9100/api/v1/write queue_config: max_samples_per_send: 500 batch_send_deadline: 5sRemote Write 的队列参数值得细说。max_samples_per_send500是每次 HTTP 请求最多携带的样本数批太小会导致写请求频繁、CPU 浪费批太大一次写失败丢的数据就多。batch_send_deadline5s强制每 5 秒攒一批避免没攒够 500 条就永远不发送的尴尬。实际运行中如果 mintdb 偶尔慢几秒Prometheus 队列会自动重试和缓冲但远端一直挂的话本地 WAL 会逐渐膨胀记得给 WAL 所在的磁盘留足空间。4.3 配置第一条能干活的告警规则告警规则建议放在 Prometheus 里而不是放在看板或单独的系统里。原因很简单Prometheus 规则引擎会把复杂查询算好还自带for持续时间过滤告警确认窗口天然就在这个位置。比如 CPU 超过 85% 持续 5 分钟才告警少一分钟都不算。groups: - name: instance-alerts rules: - alert: HighCPUUsage expr: sum(rate(node_cpu_seconds_total[5m])) by (instance) 0.85 for: 5m labels: severity: warning module: platform annotations: summary: 实例 {{ $labels.instance }} CPU 使用率超过 85%配置好后记得热加载curl -X POST http://localhost:9090/-/reload。告警会进入 Alertmanager再通过 Webhook 转发到 alert-hub 处理。这里又有一个常见坑Alertmanager 的聚合分组配置如果没调好同一条告警反复发的问题就会出现。我们默认在 alert-hub 层做静默窗口同一指纹告警名 实例 模块在 30 分钟内最多发一条silence: enabled: true window: 30m4.4 看板联动与上线验证数据通了、告警通了最后一步是看板。vizboard 配置页只需要填一个数据源地址比如http://mintdb:9100。保存后在主机资源模板里选择刚接入的 probe-agent 实例CPU 曲线应该立刻就能看到。如果图表空白我的排查顺序是先 curl mintdb 的 API 手动查指标是否存在再用 vizboard 的查询调试器跑一遍 PromQL最后才怀疑前端。90% 的情况是前两步没通过比如 instance 标签值和你 select 的不一致。上线验证我会做一个人为造故障的测试在测试机跑一个stress把核压满看 5 分钟内告警能不能到达钉钉群看板曲线能不能出现尖峰。这一步别偷懒监控系统只有在故障场景下真正验证过才算上线。当时我们压测发现告警延迟比预期高了 40 秒顺藤摸瓜排查出是 Prometheus 规则评估的evaluation_interval默认 1 分钟太慢调整到 15 秒后问题才解决。到这一步一条能从服务器采集 CPU 指标、存三个月、画出趋势图并在 CPU 跑满时自动通知值班同学的链路就算实际跑起来了。5. 同步发版背后的技术账踩过的坑与修补方式5.1 高基数指标差点拖垮 TSDB我前面说不要在标签里塞高基数数据但实际犯的错比我讲的严重得多。当时给用户模块做埋点采集设计了user_request_count指标标签直接带了 user_id。一个月后 mintdb 写入延迟从 10ms 涨到 300ms查出来是因为标签字典里存了上百万个 user_id整个索引都膨胀了。解决方案粗暴但有效把 user_id 从标签降级成普通字段指标只统计总量需要明细数据走日志系统。如果你自己设计指标记住一个黄金法则标签取值空间越小监控系统越稳定取值空间接近用户量、订单量这种级别的就不该出现在标签里。5.2 Remote Write 背压与乱序数据有一次 Prometheus 一直报 remote write 积压mintdb 的数据还出现了乱序。原因是两个 Prometheus 实例同时向同一个 mintdb 写同一组序列而时序数据要求按时间严格递增。最简单的解法是让 mintdb 在写入端做时间戳排序缓冲并开启accept_out_of_order配置。我最后两个都做了但反思下来根本原因是多实例重复采集同一目标导致数据双写。正确姿势应该通过服务发现和分组避免重复采集而不是把希望寄托在存储端兜底。5.3 告警风暴的三次修复上线第一周alert-hub 在三个不同晚上各炸了一次告警风暴每次都是半小时内几百条消息。第一次是因为规则表达式有问题sum(rate(...))忘加by (instance)集群所有实例聚合成一条总量曲线一超阈值就整片告警。第二次是静默窗口的指纹设计太宽把不同实例的相同告警错误合并真实异常的实例反而被淹没了。第三次最哭笑不得夜间发布观察期开发在实例上手动跑了一个高 CPU 压测脚本又没贴静默标签于是所有渠道被打爆。后来我们补了维护窗口配置在 CI 发布流程里自动给对应实例打静默标记。这三次修复让我意识到告警降噪不是一次性配置而是伴随团队协作方式的持续迭代。5.4 同一天发版的版本联动与发布纪律最后说说四个项目同日发版这件事本身的风险。同步发版最怕的是协议不兼容而测试环境没跑通。我最终的做法是先在 release 分支上冻结协议文档用一份 JSON Schema 定义指标格式、API 路径、告警消息体然后四个项目用同一份 Schema 生成各自的代码Go 代码生成工具最后在 CI 里加了一个链路冒烟测试——拉起全链路容器注入模拟指标断言告警能到达测试 Webhook。这套流程走完后同步发版的恐惧感就消除了一大半。没有这套纪律四个仓库各自为战发版当天必然是灾难现场。6. 关于这套链路我最想说的事自建监控链路技术难度其实不是最大挑战真正的挑战是耐住性子把每个环节的细节磨好——从指标格式约定到告警静默策略再到看板的模板设计。四个项目同一天发版表面上是一个时间点背后是半年的协议迭代和全链路冒烟测试。这些事说起来平淡但每一项都在真实故障中被一遍遍验证。如果你也想走这条路我的建议是不要一上来就想做一个全家桶平台。先把采集端和告警端做扎实这两个环节离真实故障最近存储和看板可以先借力开源生态哪怕一开始用 Prometheus Grafana 起步也完全够用。等数据和告警链路稳定了再逐步深化成自己的长期存储和看板。我这次敢四块一起做是因为有一个愿意持续投入的人而不是因为这条路更快。最后分享一个小技巧监控体系最容易被忽视的资产是你积累下来的故障复盘看板。每次线上出大故障我都会把故障前后的指标变化存成一个专用的 vizboard 模板。下次再出类似问题直接套模板对比当前曲线定位速度会快非常多。链路搭起来只是开始让链路在一次次故障中成长才是自建监控真正的价值。