监控集群 8TB 失控治理:VictoriaMetrics + 降采样 + 告警去噪实录

Prometheus 集群 3 个月 200GB 涨到 8TB,1200w series 查询 OOM,日维度看板 30s+。一个月迁移 VictoriaMetrics 集群 + 分层降采样 + 高基数治理 + recording rule 预计算 + 告警去噪。P99 30s→800ms,存储 -70%,告警 200→30/天。

2024 年我们的 Prometheus 监控集群把所有业务和基础设施的指标全集中存储,3 个月时间数据量从 200GB 涨到 8TB,单机本地存储顶不住,查询经常 OOM,日维度看板加载要 30 秒以上。投了一个月迁移到 VictoriaMetrics + Thanos + 分层降采样,P99 查询从 30s 降到 800ms,存储成本下降 70%。本文复盘 Prometheus 长期存储 + 高基数指标治理,覆盖架构选型、降采样、记录规则、查询优化、告警去噪。

问题背景

规模:
- 200 个 K8s 节点 × 30 个 exporter = 6000 个采集目标
- 业务指标:5000 个微服务 × 30 个 metric = 15w series
- 总活跃 series:1200w
- 采集间隔 15s
- 数据保留 90 天

性能问题:
- 本地存储 8TB,inode 紧张
- 查询 30 天看板:超时(30s 限制)
- Grafana 经常 502
- Prometheus 进程占用 80GB 内存
- 高基数 metric 拖累所有查询

# Top 高基数(用 promtool 看)
$ promtool tsdb analyze /data/prometheus
Highest cardinality labels:
  url=12000           # 含路径 + 参数(高基数源头)
  user_id=80000       # 直接打到 label 里(不允许!)
  trace_id=...        # 应该走 trace 系统,不该在 metric
  request_id=...      # 同上

# 内存占用
prometheus_local_storage_memory_chunks  150,000,000     # 1.5 亿 chunk

架构方案选型

三个主流方案对比:

1. Thanos
   - 优:Prometheus 原生,生态完善
   - 优:对象存储成本低
   - 缺:架构复杂(Sidecar + Store + Compactor + Query + Ruler)
   - 缺:查询合并慢(多 Prom 实例聚合)

2. Cortex / Mimir(Grafana 系)
   - 优:水平扩展强
   - 缺:多组件复杂
   - 缺:运维成本高

3. VictoriaMetrics
   - 优:单二进制,部署简单
   - 优:压缩率 7x(比 Prometheus 节省 50% 存储)
   - 优:查询快 3-10x(MetricsQL 优化)
   - 优:支持 PromQL,几乎零迁移成本
   - 缺:不是 Apache 协议(影响某些公司选型)

我们选 VictoriaMetrics 集群 + 对象存储归档

VictoriaMetrics 集群部署

# 组件:
# - vminsert: 写入分发(无状态)
# - vmstorage: 存储(有状态,水平扩展)
# - vmselect: 查询(无状态)
# - vmagent: 替代 Prometheus 抓取(可选)

# K8s 部署(使用 helm)
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
  name: vmcluster
spec:
  chart:
    spec:
      chart: victoria-metrics-cluster
      version: "0.10.x"
  values:
    vmstorage:
      replicaCount: 6                       # 6 副本水平扩展
      retentionPeriod: 90d
      persistentVolume:
        size: 2Ti
        storageClassName: nvme-ssd
      resources:
        requests:
          memory: 32Gi
          cpu: 8
        limits:
          memory: 64Gi
          cpu: 16
      extraArgs:
        dedup.minScrapeInterval: 30s        # 去重(高可用 Prom 双采)
        memory.allowedPercent: 70

    vminsert:
      replicaCount: 4
      extraArgs:
        replicationFactor: 2                # 写两份(高可用)
        maxLabelsPerTimeseries: 30          # 限制 label 数
        maxLabelValueLen: 1024

    vmselect:
      replicaCount: 4
      extraArgs:
        search.maxQueryDuration: 60s
        search.maxConcurrentRequests: 16
        search.maxQueryLen: 16384

# Prometheus 远程写入到 VictoriaMetrics
# prometheus.yml
remote_write:
- url: http://vminsert.monitoring:8480/insert/0/prometheus/api/v1/write
  queue_config:
    capacity: 100000
    max_samples_per_send: 10000
    batch_send_deadline: 5s

# 本地保留 2 天(应对网络故障),长期存 VM
storage:
  tsdb:
    retention: 2d

降采样(Downsampling)

# VictoriaMetrics 自动降采样(企业版)
# 开源版:用 recording rule 自己做

# 1. 原始数据保留 30 天(15s 间隔)
# 2. 5min 粒度保留 1 年
# 3. 1h 粒度保留 5 年

# recording rule(prometheus 配置)
groups:
- name: downsample_5m
  interval: 5m
  rules:
  - record: instance:node_cpu_utilization:rate5m_avg
    expr: avg_over_time(instance:node_cpu_utilization:rate5m[5m])

  - record: instance:node_memory_used_bytes:avg5m
    expr: avg_over_time(node_memory_MemTotal_bytes{}[5m] - node_memory_MemAvailable_bytes{}[5m])

  - record: api:http_request_duration_seconds:rate5m
    expr: rate(http_request_duration_seconds_sum[5m]) / rate(http_request_duration_seconds_count[5m])

- name: downsample_1h
  interval: 1h
  rules:
  - record: instance:node_cpu_utilization:rate5m_avg_1h
    expr: avg_over_time(instance:node_cpu_utilization:rate5m_avg[1h])

# Grafana 看板按时间范围选数据源
# 7 天内:原始数据
# 30 天内:5m 降采样
# 1 年内:1h 降采样

# 效果:
# - 1 年看板查询从 30s 降到 500ms
# - 长期存储减少 95%

高基数指标治理

# 高基数 = label 取值组合爆炸
# 1 个 metric 100 个 label 值 = 100 series
# 1 个 metric 含 user_id + url + ... = 千万级 series

# 不好的指标设计
http_requests_total{
  method="GET",
  url="/api/users/12345/orders/67890",  ← URL 含 ID
  user_id="12345",                      ← 用户 ID 不该是 label
  trace_id="abc...",                    ← trace 不该是 label
}

# 好的指标设计
http_requests_total{
  method="GET",
  endpoint="/api/users/:userId/orders/:orderId",   ← route pattern
  status="200",
  service="user-service",
}

# 业务标识查询用 trace 系统(Jaeger / Tempo)

# 找出高基数 metric
$ promtool tsdb analyze /data
Top 10 highest cardinality metric names:
   nginx_http_requests_total: 234,567
   http_server_request_duration_seconds_bucket: 1,234,567   ← 第一名
   process_cpu_seconds_total: 12,345

# 限制 label 取值
# Prometheus relabel_configs
scrape_configs:
- job_name: 'app'
  metric_relabel_configs:
  # 删除高基数 label
  - source_labels: [__name__, user_id]
    regex: '.*;.+'
    action: drop_label

  # URL 规范化
  - source_labels: [url]
    regex: '/api/users/[0-9]+/.*'
    target_label: endpoint
    replacement: '/api/users/:id/...'

  # 直接丢弃高基数 metric
  - source_labels: [__name__]
    regex: 'http_server_request_duration_seconds_bucket'
    action: drop

# VictoriaMetrics 直接限制
-maxLabelsPerTimeseries=30
-maxUniqueTimeseries=10000000
# 超过限制写入失败,强制业务方规范

记录规则(Recording Rules)

# 复杂查询提前算好,看板秒开
groups:
- name: api_sli
  interval: 30s
  rules:
  # SLI:成功率
  - record: api:request_success_ratio:5m
    expr: |
      sum by (service, endpoint) (rate(http_requests_total{status!~"5.."}[5m]))
      /
      sum by (service, endpoint) (rate(http_requests_total[5m]))

  # SLI:P99 延迟
  - record: api:request_duration_p99:5m
    expr: |
      histogram_quantile(0.99,
        sum by (service, endpoint, le) (rate(http_request_duration_seconds_bucket[5m]))
      )

  # SLI:P50 延迟
  - record: api:request_duration_p50:5m
    expr: |
      histogram_quantile(0.50,
        sum by (service, endpoint, le) (rate(http_request_duration_seconds_bucket[5m]))
      )

  # 实例资源使用率
  - record: instance:cpu_usage:rate5m
    expr: |
      1 - avg by(instance) (
        rate(node_cpu_seconds_total{mode="idle"}[5m])
      )

# Grafana 直接用 record:api:request_duration_p99:5m
# 不要现场算 histogram_quantile(..) — 看板会卡到怀疑人生

告警去噪

# 问题:每天 200 条告警,80% 是噪音
# 治理:多级阈值 + 时间窗口 + inhibit + 路由

groups:
- name: api_alerts
  rules:
  # 单条告警:用 for 避免抖动
  - alert: HighErrorRate
    expr: api:request_success_ratio:5m < 0.99
    for: 5m                              # 持续 5 分钟才告警
    labels:
      severity: warning
      team: backend
    annotations:
      summary: "{{ $labels.service }} 成功率 {{ $value | humanizePercentage }}"
      runbook: "https://wiki/runbook/{{ $labels.service }}-error-rate"

  # 严重级:阈值更低
  - alert: VeryHighErrorRate
    expr: api:request_success_ratio:5m < 0.95
    for: 2m
    labels:
      severity: critical
      team: backend
      page: "true"                        # 触发 PagerDuty

  # 资源类:用 deriv 看趋势,避免抖动
  - alert: MemoryLeakSuspected
    expr: |
      deriv(process_resident_memory_bytes[1h]) > 10*1024*1024 / 3600
      and process_resident_memory_bytes > 500*1024*1024
    for: 30m
    annotations:
      summary: "内存持续上涨 > 10MB/h,可能泄漏"

# Alertmanager 配置:抑制 + 静默 + 分组
route:
  group_by: ['cluster', 'service']
  group_wait: 30s
  group_interval: 5m
  repeat_interval: 4h
  receiver: 'team-default'
  routes:
  - matchers:
      - severity = critical
      - page = "true"
    receiver: 'pagerduty'
    continue: false
  - matchers:
      - team = backend
    receiver: 'backend-team-slack'

inhibit_rules:
# 服务全挂时,不报具体接口错误
- source_matchers:
    - alertname = ServiceDown
  target_matchers:
    - alertname = HighErrorRate
  equal: ['service']

# 大节点故障时,不报子任务告警
- source_matchers:
    - alertname = NodeDown
  target_matchers:
    - alertname =~ "Pod.*"
  equal: ['node']

receivers:
- name: 'pagerduty'
  pagerduty_configs:
  - service_key: '...'
- name: 'backend-team-slack'
  slack_configs:
  - api_url: '...'
    channel: '#alerts-backend'

查询优化技巧

# 1. 避免现场算 histogram_quantile
# 不好(慢)
histogram_quantile(0.99, sum by(le, service) (rate(http_request_duration_seconds_bucket[5m])))

# 好(快):用 recording rule 提前算
api:request_duration_p99:5m{service="order"}

# 2. 减少 series 选择
# 不好:扫描所有 service
rate(http_requests_total[5m])

# 好:加 service 过滤
rate(http_requests_total{service="order"}[5m])

# 3. 不要用 regex(慢)
# 不好
http_requests_total{service=~"order|product|user"}

# 好(or):
http_requests_total{service="order"} or http_requests_total{service="product"}

# 4. 大时间范围用降采样数据
# 1 年看板用 1h 降采样,不用 15s 原始数据

# 5. 设置 vmselect 资源限制
search.maxQueryDuration: 60s
search.maxQueryLen: 16384
search.maxSamplesPerQuery: 1000000000
search.maxConcurrentRequests: 16

# 6. 利用 cardinality limit
# 单查询返回 series > 1w 直接拒绝

优化效果

指标                  优化前         优化后
=========================================================
活跃 series           1200w          400w(治理高基数)
存储占用              8TB            2.4TB(VM 压缩 + 降采样)
P50 查询延迟          2s             80ms
P99 查询延迟          30s(超时)    800ms
日维度看板加载         30s+           1-2s
1 年维度看板          不可用         3-5s(降采样)
Prometheus 内存       80GB           Prom 4GB(只本地 2 天)
                                     VM 32GB × 6 = 192GB
告警条数(天)        200            30(去噪后)
误报率                30%            < 5%

成本:
- 本地 SSD 减少 5TB
- 对象存储归档替代,成本降 70%
- 整体存储成本 8000/月 → 2400/月

业务影响:
- SRE 看板秒开,故障定位快 10x
- 老旧数据可查(1 年内任意时间)
- 告警准确率高,oncall 不被打扰
- 长期趋势可分析(SLO 达成率、容量规划)

避坑清单

  1. label 值必须有限:URL 用路由 pattern,不要含 ID
  2. user_id / trace_id / request_id 永远不要放 metric label
  3. 恒用 recording rule 预计算 P99/P95/成功率
  4. VictoriaMetrics 部署简单,7x 压缩,首选长期存储
  5. maxLabelsPerTimeseries / maxUniqueTimeseries 必设上限
  6. 降采样:7d 原始 / 30d 5m / 1y 1h / 5y 1h
  7. 告警 for: 5m 起步,避免抖动
  8. inhibit_rules 抑制级联告警(NodeDown 时不报 PodDown)
  9. cardinality 限制单查询返回 series,防止"查死"
  10. 本地 Prom 只保留 2 天,长期存 VM/Thanos

总结

Prometheus 长期存储 + 高基数治理是监控系统从"能用"到"可用"的关键一跃。这次治理最大的认知改变:VictoriaMetrics 不是 Prometheus 的简单替代,是个全面更优的方案 — 部署更简单(单二进制 vs Thanos 5 组件)、压缩更高(7x vs 3x)、查询更快(3-10x)、配置更少。最被低估的是 recording rule,P99 现场算 30s,record 预算 100ms — 凡是 Grafana 看板用到的复杂表达式,都该预算好。最容易踩的坑是高基数:开发把 user_id 当 label 加进去,一个 metric 千万 series,Prometheus 直接 OOM;label 必须低基数(method / status / endpoint pattern),业务标识走 trace 系统。最后,告警去噪是 SRE 工作的核心:200 条 → 30 条,oncall 不再被打扰,这是真正的可持续运维 — for: 5m + inhibit_rules + 分级告警三件套,缺一不可。

—— 别看了 · 2026
声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理 邮箱1846861578@qq.com。
技术教程

RocketMQ 月丢 387 笔订单事故复盘:零丢失零重复消费全链路修法

2026-5-19 13:15:59

技术教程

Elasticsearch 集群 50TB 治理:索引合并 + 冷热分层 + JVM 调优实录

2026-5-19 13:22:06

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索