使用vm-agent替代prometheus
在云原生监控领域,Prometheus 是老牌的行业标准,而 vm-agent(VictoriaMetrics Agent)则是后起之秀 VictoriaMetrics 生态中的一个轻量级组件。
简单来说:Prometheus 是一个“全能型监控系统”(能刮取、能存储、能查询);而 vm-agent 则是一个“纯粹的指标搬运工”(只负责刮取和发送,不负责本地持久化存储和查询)。
# vm-agent 与 Prometheus 的核心区别
| 维度 | Prometheus | vm-agent |
|---|---|---|
| 定位 | 完整的监控与时序数据库系统 | 轻量级的指标采集代理(Agent) |
| 本地存储 | 有(自带高性能 TSDB,重度依赖磁盘 I/O) | 无(仅有临时磁盘 Buffer 用于断网容灾) |
| 资源消耗 | 较高。随着采集指标量增加,内存和 CPU 消耗会大幅上升。 | 极低。内存占用通常只有 Prometheus 的几分之一,对 CPU 非常友好。 |
| 查询功能 | 支持(提供原生的 PromQL 查询接口) | 不支持(它只管发,不读数据,没有查询接口) |
| 报警计算 | 支持(本地配置并计算 rule) | 不支持(需要依赖远端存储或 vm-alert 来计算) |
| 集群与扩展性 | 原生不支持分片集群,需要靠 Thanos/Cortex 等外挂方案或联邦模式。 | 天生支持分布式部署。可以通过多个 vm-agent 配合 hash_mod 实现轻松横向扩展。 |
# 区别一:集群
在原生的 Prometheus 官方架构哲学里,所谓的“集群部署”,本质上就是多活(Multi-HA)的高可用冗余,它根本不具备任何“分布式分片(Sharding)”和横向扩展性能的能力。
当你为了高可用,在同一个 K8s 集群里部署了两个完全一模一样的 Prometheus 实例(Replica A 和 Replica B)时,它们的工作和去重工作流是这样的:
| 阶段 | Prometheus Replica A (实例 A) | Prometheus Replica B (实例 B) | 远端分布式存储(如 Thanos / VM) |
|---|---|---|---|
| 1. 抓取 (Scrape) | 拿着相同的配置,去拉取 Target 1 的指标。 | 同时拿着相同的配置,去拉取 Target 1 的指标。 | (此时毫不知情) |
| 2. 发送 (Push) | 通过 remote_write 把带有时戳、标签和 replica=A 的数据发往远端。 | 同时通过 remote_write 把带有时戳、标签和 replica=B 的数据发往远端。 | 远端存储同时收到了两份内容完全一样、但唯一标识(Replica 标签)不同的数据流。 |
| 3. 存储与去重 | (完成任务,继续拉取) | (完成任务,继续拉取) | 核心点:远端存储(如 Thanos-Receive 或 VictoriaMetrics)在落盘或查询时,根据配置的 deduplication.replica-label=replica,强行把 B 的数据丢弃掉,只留 A 的数据。 |
解决方案:
# 方案 1:使用 Prometheus Operator 的自研 Hash 分片
如果你必须用 Prometheus,官方的 Prometheus Operator 引入了基于 Hashmod 的分片机制。
- 工作模式:你在配置文件里声明
shards: 3。 - 底层原理:Operator 会启动 3 个 Prometheus 实例。实例 1 只抓取
Hash(Pod_IP) % 3 == 0的 Pod,实例 2 抓取% 3 == 1的 Pod。 - 结果:这才是真正的提高性能(横向扩展),每个 Prometheus 实例只承担1/3 的内存和 CPU 压力。
# 方案 2:完美的终极解法 —— vmagent 自带分布式集群分片
这也是为什么你之前测试 vm-agent 那么爽的另一个隐藏原因:vm-agent 天生支持在采集端直接做分布式分片,并且不需要任何复杂的第三方组件。
┌──> vmagent-0 (只拉取 1/3 的 Target) ──┐
[海量监控目标 Target] ├──> vmagent-1 (只拉取 1/3 的 Target) ──┼─(remote_write)─> [VictoriaMetrics]
└──> vmagent-2 (只拉取 1/3 的 Target) ──┘
你只需要在部署 vm-agent 的多副本集群时,给它们加上两个极其简单的启动参数:
-remoteWrite.cluster.membersCount=3(告诉它总共有 3 个分片)-remoteWrite.cluster.memberNum=0/1/2(告诉当前实例是第几个分片)
底层表现:这 3 个 vm-agent 实例会自动打散、平摊所有的监控目标。
- 不仅提高了性能:每个实例的内存和网络开销直接暴跌到 1/3。
- 而且在源头上干净利落:发往 VictoriaMetrics 的数据本身就是不重复且完美分片的,远端存储层甚至连“去重计算”的 CPU 损耗都省下来了。
| 时间点 (T) | vm-agent 实例 0 (memberNum=0) | vm-agent 实例 1 (memberNum=1) | vm-agent 实例 2 (memberNum=2) | 远端存储 (VictoriaMetrics) 接收状态 |
|---|---|---|---|---|
| T1 | 内部利用一致性 Hash 算法计算,认领并抓取 Pod-A、Pod-D 的指标。 | 计算后,认领并抓取 Pod-B、Pod-E 的指标。 | 计算后,认领并抓取 Pod-C、Pod-F 的指标。 | 接收到 100% 完整但互不重复的全国/全集群业务指标流。 |
| T2 | remote_write 发送 A, D。 | remote_write 发送 B, E。 | remote_write 发送 C, F。 | 存储层极其轻松,无需做任何去重计算(Deduplication),来什么直接落盘什么。 |
- 底层表现:如果集群里的 Pod 指标量有 3000 万,那么现在分摊到每个 vm-agent 身上只有 1000 万。内存和 CPU 开销直接暴跌到原来的1/3,实现了真正的横向可扩展(Scale Out)。
# 区别二、TSDB持久化
相比之下:
Prometheus 无法关闭 TSDB。它必须把数据解析成对象、建内存索引、塞入 Head Block,最后才能通过
remote_write吐出来。Prometheus Agent 可关闭TSDB,官方文档: https://prometheus.io/docs/prometheus/latest/prometheus_agent/ (opens new window)
2.32.0 版本后引入,Prometheus Agent 依然会写磁盘,但它彻底禁用了向本地 TSDB 历史块(Block)的写入,只在磁盘上保留最基础的 WAL(预写日志)用于断电容灾。
vm-agent 彻底扔掉了 TSDB 的概念。它在代码层面上彻底免去了“建索引”、“本地持久化”的逻辑,数据拉回来就是纯字节流,在环形缓冲区停留零点几秒后直接网络冲刷(Flush)给 VictoriaMetrics。
vm-agent的设计要绝情得多:- 关于磁盘 WAL:
vm-agent默认连 WAL 都不写。它认为“为了极少发生的断电而让磁盘每秒承受几百兆的 I/O 写入”是本末倒置。数据拉回来直接进内存环形队列,网络发走,原地蒸发。只有在远端 VictoriaMetrics 真的挂了、网络彻底断开时,它才会临时把数据写入磁盘 Buffer。 - 关于内存索引:
vm-agent不需要维持全量的内存时序树。它把指标看作纯粹的字节流,利用高度优化的无锁算法,拉过来就塞网络管子,根本不在内存里“定居”。
- 关于磁盘 WAL:
# 区别三、内存索引和历史缓存
# prometheus - 内存索引
prometheus中 内存索引本质上是一棵在内存中维护的、极为庞大的倒排索引树(Inverted Index Tree)。它的职责是:当你用一组标签去查指标时,能瞬间告诉你这个指标在内存里的哪块地方。
里面塞了什么?
每当你抓取一条带有新标签的指标,Prometheus 就会在内存索引里塞入三样东西:
- LabelName 到 LabelValue 的映射:比如
pod——>nginx-abcd-1234。 - LabelValue 到 Series ID 的映射:由于字符串太长,Prometheus 会给每个唯一的指标组合分配一个全局唯一的 64 位数字 ID(叫做 Series ID)。
- 倒排索引表:记录哪些 Series ID 包含了
status="200"。
如果你的 K8s 集群非常大,Pod 经常发生漂移、重建(比如每次发布、或者 HPA 自动扩缩容),Pod 的名字就会不断改变(nginx-v1 变成 nginx-v2)。
- 对 Prometheus 来说,只要有一个标签变了,这就是一条全新的指标序列(New Series)。
- 结果就是:这棵内存索引树会只增不减地疯狂膨胀。即使那些死掉的 Pod 已经没有新数据进来了,它们的标签和索引依然永久常驻在你的 128G 内存里,直到内存被无情耗尽。
# prometheus - 历史缓存(Memory Chunks / Head Chunks)
当一个 Chunk 被 120 个数据点塞满后,它就会变成“只读”状态,并在内存里变成历史缓存(Memory Chunks)。
同时,Prometheus 会在内存里开辟一个新的、空的 Head Chunk 继续接收新数据。
这些塞满的只读 Chunks,会继续在内存里驻留很长时间。只有当时间累积到 2 小时后,Prometheus 内部的本地存储引擎才会启动一个大动作:把这 2 小时内所有指标的“历史缓存”统一打包,刷写到磁盘上(生成一个正式的 TSDB Block),随后才释放这部分内存。
对于vm-agent来说,它不要倒排索引,也不要在内存里憋 2 小时的历史 Chunk,它把指标当成冷酷的字节流,拉到就立刻吐给远端。
# 总结
| 指标维度 | 原生 Prometheus | Prometheus Agent | vm-agent (VictoriaMetrics) |
|---|---|---|---|
| 本地历史块落盘 | 🟢 有(每 2 小时生成 Block) | ❌ 无 | ❌ 无 |
| 本地磁盘 WAL 预写 | 🟢 强制开启(高 I/O 吞吐) | 🟢 强制开启(用于短时容灾) | 🟡 默认关闭(仅在远端故障时触发 Buffer) |
| 内存开销模型 | 极重(内存索引 + 历史缓存) | 依旧较重(必须常驻全量内存时序索引) | 极轻(流式无状态,内存只跟网络吞吐正相关) |
| 大并发千万级指标表现 | 容易 OOM(需要 128G+ 甚至打爆) | 缓解了磁盘,但高并发下依旧会 OOM | 4C4G 稳如泰山 |