3. 数据采集层设计:Pull vs Push模型对比,Exporter设计模式,Client Library选型

数据采集层是整个监控体系的「眼睛」。眼睛要是瞎了,后面再好的分析工具也白搭。这一章我们聊聊采集层的三个核心问题:怎么拉数据、怎么暴露数据、用什么工具去采集。

3.1 Pull vs Push:两种模型的对决

先问个问题:你的监控系统是主动去拿数据,还是等着数据送上门?

这就是 Pull 和 Push 的本质区别。我早年做传统运维时,习惯用 Push 模型——每个机器上装个 Agent,定时把指标推到中心服务器。后来转到 Prometheus 生态,才真正体会到 Pull 模型的好处。

对比维度 Pull 模型(Prometheus) Push 模型(StatsD / Graphite)
数据流向 监控系统主动拉取目标 目标主动推送数据
服务发现 天然支持,可动态发现目标 需额外注册或配置
网络控制 监控端可控,防火墙好配 目标端发起,端口管理复杂
数据可靠性 拉取失败可重试 推送失败可能丢数据
适用场景 长期运行的服务、K8s 集群 短生命周期任务、批处理

我的建议:核心业务服务用 Pull,临时任务用 Push。别死磕一种模型,混用才是常态。

Pull 模型有个天然优势——你可以在监控端统一控制采集频率。比如线上出问题时,我习惯把采集间隔从 15s 降到 5s,快速定位问题。Push 模型就没这么灵活,你得去改每个客户端的配置。

避坑指南:我曾经在某个金融项目中全量使用 Push 模型,结果业务高峰期客户端集体推送,直接把监控网关打挂了。后来改成 Pull + 限流,再没出过类似问题。

3.2 Exporter 设计模式:让每个服务都能被「拉」

Pull 模型的核心是 Exporter。说白了,Exporter 就是一个 HTTP 端点,把内部指标翻译成监控系统能理解的格式。

我参与过一个电商平台的监控改造,当时需要监控 Redis 集群。官方 Redis Exporter 已经很好用了,但我们有自定义的缓存命中率算法,必须自己写 Exporter。

// 一个简单的自定义 Exporter 示例
package main

import (
    "net/http"
    "github.com/prometheus/client_golang/prometheus"
    "github.com/prometheus/client_golang/prometheus/promhttp"
)

var (
    cacheHits = prometheus.NewCounterVec(
        prometheus.CounterOpts{
            Name: "app_cache_hits_total",
            Help: "Total number of cache hits.",
        },
        []string{"cache_type"},
    )
)

func init() {
    prometheus.MustRegister(cacheHits)
}

func main() {
    http.Handle("/metrics", promhttp.Handler())
    http.ListenAndServe(":8080", nil)
}

写 Exporter 有几个原则:

  • 幂等性:每次拉取返回的数据应该是当前快照,不依赖历史状态
  • 轻量:采集逻辑不能影响业务性能,我一般控制在 10ms 以内
  • 可观测:Exporter 自己也要暴露健康指标,比如采集耗时、错误数

小技巧:写 Exporter 时,记得加上 up 指标。Prometheus 默认会用这个指标判断目标是否存活。我见过不少团队忘了加,结果服务挂了都不知道。

3.3 Client Library 选型:Prometheus SDK vs StatsD

选 Client Library 其实是在选生态。我这些年用过不少,最后稳定在 Prometheus SDK 和 StatsD 两个方向上。

Prometheus SDK

如果你在用 Prometheus 生态,那 Prometheus SDK 是首选。它支持四种核心指标类型:

  • Counter:只增不减,适合请求数、错误数
  • Gauge:可增可减,适合内存使用率、连接数
  • Histogram:分桶统计,适合请求延迟
  • Summary:分位数计算,适合 SLA 监控

我个人最喜欢 Histogram。有一次排查接口慢查询,就是靠 Histogram 的桶分布,一眼看出 99% 的请求在 200ms 以内,但有个别请求飙到了 5s。用 Summary 反而看不出这种分布特征。

StatsD

StatsD 是另一种思路。它用 UDP 协议,客户端只管发,不管收。适合对性能要求极高的场景。

对比维度 Prometheus SDK StatsD
传输协议 HTTP(Pull) UDP(Push)
数据格式 结构化文本 简单字符串
性能开销 中等 极低
聚合能力 服务端聚合 客户端聚合
适用语言 Go、Java、Python 等主流语言 几乎所有语言

选型建议:新项目优先 Prometheus SDK,生态好、易维护。如果对性能有极致要求(比如每秒百万级指标),或者需要支持老旧系统,再考虑 StatsD。

嗯,这里要注意一点。StatsD 用 UDP 意味着可能丢包。我在游戏公司时,用 StatsD 采集玩家在线数,高峰期丢包率到了 5%。后来加了本地缓存和重传机制才解决。

3.4 整体架构图

下面这张图展示了 Pull 和 Push 两种模型在整体架构中的位置,以及 Exporter 和 Client Library 的角色。

数据采集层架构:Pull vs Push 模型 Pull 模型(Prometheus 生态) Prometheus Server 服务发现 / 目标管理 Exporter A /metrics Exporter B /metrics 业务服务 A 业务服务 B Prometheus SDK Prometheus SDK Push 模型(StatsD 生态) 业务服务 C 业务服务 D StatsD Client StatsD Client UDP 推送指标数据 StatsD Server 聚合 / 转发 Graphite / InfluxDB 适用:短生命周期任务、批处理、 对性能要求极高的场景

从图上能看出来,Pull 模型的核心是 Exporter,每个服务都暴露一个 /metrics 端点,Prometheus 定期来拉。Push 模型则是客户端主动把数据扔给 StatsD Server,Server 做聚合后再写入后端存储。

两种模型各有优劣。我现在的做法是:核心服务用 Pull,方便管理和排障;临时任务和批处理用 Push,避免监控系统成为瓶颈。

最后说一句:不管选哪种模型,一定要给采集层加监控。我见过太多团队,监控系统本身挂了都不知道。给 Exporter 加存活检测,给 StatsD 加队列长度告警,这些基础工作别省。