SpringBoot + Prometheus 指标基数爆炸治理:Label 乱打导致内存飙升?聚合采样方案!

做监控系统的同学肯定都遇到过这个问题:Prometheus 内存占用越来越高,监控面板加载越来越慢,最后甚至 OOM 崩溃。排查后发现,罪魁祸首居然是某个接口的 Label 值太多,导致指标基数爆炸。

我之前就遇到过这样一个案例:某个接口返回了用户 ID 作为 Label,结果线上有几百万活跃用户,这个 Label 的取值就有几百万种。单个指标瞬间膨胀到几百万个 time series,Prometheus 的内存和 CPU 直接被打爆。

今天我们就来聊聊 Prometheus 指标基数爆炸的问题,以及如何通过聚合采样方案来解决。

什么是指标基数爆炸?

1. Prometheus 指标模型回顾

Prometheus 的指标由以下几个部分组成:

指标名称 + Label组合 = 唯一的time series

例如:
api_request_total{method="GET", status="200", path="/users"} = 12345
api_request_total{method="POST", status="200", path="/orders"} = 67890

2. Label 的基数问题

每个 Label 的取值组合决定 了指标的数量:

指标基数 = method的取值数 × status的取值数 × path的取值数

假设:
- method: 5种(GET, POST, PUT, DELETE, PATCH)
- status: 10种(200, 201, 400, 401, 403, 404, 500, 502, 503, 504)
- path: 100种

基数 = 5 × 10 × 100 = 5000 个time series

这看起来还好,但如果 path 变成了 user_id

- path: 1,000,000种(每个用户ID都是不同的值)

基数 = 5 × 10 × 1,000,000 = 50,000,000 个time series!

3. 基数爆炸的危害

基数爆炸影响链:

1. Prometheus 内存暴涨
   ↓
2. 抓取时间变长,超时失败
   ↓
3. 查询性能下降,PromQL 超时
   ↓
4. 告警触发延迟或失败
   ↓
5. 严重时 Prometheus OOM 重启

常见的导致基数爆炸的 Label

1. 用户ID、订单ID等业务ID

// 错误示范:把业务ID作为Label
api_request_total{user_id="123456", path="/api/orders"}

// 正确做法:不要把业务ID作为Label
api_request_total{path="/api/orders"}

2. 具体的数值参数

// 错误示范:把分页参数作为Label
user_list_total{page="1", page_size="20"}

// 正确做法:把分页参数聚合或去掉
user_list_total{}

3. 具体的IP或主机名

// 错误示范:把具体IP作为Label
http_requests{source_ip="192.168.1.100"}

// 正确做法:按IP段或机房聚合
http_requests{datacenter="dc1", region="us-east"}

4. UUID、Token等随机值

// 错误示范:把Token作为Label
api_calls{token="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."}

// 正确做法:去掉这类Label
api_calls{}

解决方案:聚合采样方案

我们的方案采用"多层防护"策略:

┌─────────────────────────────────────────────────────────────────┐
│                     基数治理整体方案                             │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│   第一层:Label 设计规范(预防)                                  │
│   ┌─────────────────────────────────────────────────────────┐   │
│   │ • 禁止使用高基类Label(用户ID、IP等)                    │   │
│   │ • 使用业务维度Label(接口名、服务名)                    │   │
│   │ • 静态Label优先                                          │   │
│   └─────────────────────────────────────────────────────────┘   │
│                           ↓                                     │
│   第二层:运行时聚合(处理已有指标)                              │
│   ┌─────────────────────────────────────────────────────────┐   │
│   │ • 使用 Recording Rules 预聚合                           │   │
│   │ • 创建层级化的指标体系                                   │   │
│   │ • 定期清理历史数据                                       │   │
│   └─────────────────────────────────────────────────────────┘   │
│                           ↓                                     │
│   第三层:智能采样(极端情况)                                    │
│   ┌─────────────────────────────────────────────────────────┐   │
│   │ • 高基数指标自动采样                                     │   │
│   │ • 保留关键维度,过滤低价值Label                          │   │
│   │ • 采样数据定期落库分析                                   │   │
│   └─────────────────────────────────────────────────────────┘   │
│                           ↓                                     │
│   第四层:告警监控(发现问題)                                    │
│   ┌─────────────────────────────────────────────────────────┐   │
│   │ • 指标数量增长率告警                                     │   │
│   │ • 单指标 series数量告警                                  │   │
│   │ • 内存使用率告警                                         │   │
│   └─────────────────────────────────────────────────────────┘   │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

核心组件设计

1. Label 规范检查器

在编译期或运行时检查 Label 配置:

核心逻辑:

class LabelValidator:
    def __init__(self):
        self.forbidden_patterns = [
            '.*_id$',      # 禁止 _id 结尾的Label
            '.*_token$',   # 禁止 _token 结尾的Label
            r'\d+\.\d+\.\d+\.\d+',  # 禁止IP地址
            '.*_uuid$',    # 禁止 UUID
        ]
    
    def validate(self, metric_name, labels):
        for label_name in labels.keys():
            for pattern in self.forbidden_patterns:
                if re.match(pattern, label_name):
                    raise LabelValidationError(
                        f"Label '{label_name}' in metric '{metric_name}' "
                        f"matches forbidden pattern '{pattern}'"
                    )
        
        # 检查Label值的长度
        for label_name, label_value in labels.items():
            if len(label_value) > 200:
                raise LabelValidationError(
                    f"Label value '{label_name}={label_value}' is too long"
                )

2. 指标注册表

统一管理所有指标,记录基线:

核心逻辑:

class MetricRegistry:
    def __init__(self):
        self.metrics = {}
        self.baselines = {}
    
    def register(self, metric_name, labels, baseline_series_count):
        if metric_name in self.metrics:
            current_count = self._estimate_series_count(labels)
            baseline = self.baselines[metric_name]
            
            if current_count > baseline * 1.5:
                warn(f"Metric {metric_name} series count increased significantly")
                self._trigger_alert(metric_name, current_count, baseline)
        
        self.metrics[metric_name] = labels
        self.baselines[metric_name] = self._estimate_series_count(labels)
    
    def _estimate_series_count(self, labels):
        count = 1
        for label_name, label_values in labels.items():
            count *= len(label_values)
        return count

3. 高基数指标采样器

对高基数指标进行智能采样:

核心逻辑:

class HighCardinalitySampler:
    def __init__(self, sample_rate=0.01):
        self.sample_rate = sample_rate  # 默认1%采样
        self.sampled_data = {}
    
    def record(self, metric_name, labels, value):
        # 判断是否是高基数指标
        if not self._is_high_cardinality(metric_name, labels):
            return False  # 不需要采样
        
        # 概率采样
        if random.random() < self.sample_rate:
            key = self._generate_sample_key(metric_name, labels)
            self.sampled_data[key] = {
                'value': value,
                'timestamp': time.time(),
                'labels': labels
            }
            return True
        
        return False
    
    def _is_high_cardinality(self, metric_name, labels):
        """判断是否是高基数指标"""
        estimated_series = 1
        for label_values in labels.values():
            estimated_series *= len(label_values)
        
        return estimated_series > 10000  # 超过10000个series认为是高基数
    
    def _generate_sample_key(self, metric_name, labels):
        """生成采样数据的key"""
        # 只保留关键Label,过滤高基类Label
        filtered_labels = {
            k: v for k, v in labels.items()
            if not self._is_forbidden_label(k)
        }
        return f"{metric_name}:{filtered_labels}"

4. 聚合规则管理器

通过 Recording Rules 预聚合指标:

核心逻辑:

class RecordingRuleManager:
    def __init__(self):
        self.rules = []
    
    def generate_rules(self, metrics):
        rules = []
        
        for metric in metrics:
            # 生成聚合规则
            if self._needs_aggregation(metric):
                rule = self._create_aggregation_rule(metric)
                rules.append(rule)
        
        return rules
    
    def _needs_aggregation(self, metric):
        """判断是否需要聚合"""
        # 高基数指标需要聚合
        if metric.series_count > 50000:
            return True
        
        # 经常用于查询的指标需要聚合
        if metric.name in ['api_request_total', 'api_request_duration']:
            return True
        
        return False
    
    def _create_aggregation_rule(self, metric):
        """创建聚合规则"""
        rule_name = f"{metric.name}_aggregated"
        
        # 按接口聚合,去掉具体路径
        if 'path' in metric.labels:
            expr = f"sum by (method, status) ({metric.name})"
        else:
            expr = f"sum({metric.name})"
        
        return {
            'name': rule_name,
            'expr': expr,
            'labels': {
                'aggregated': 'true',
                'aggregation_type': 'high_cardinality_reduction'
            }
        }

5. 基数监控告警器

实时监控指标基数变化:

核心逻辑:

class CardinalityAlerter:
    def __init__(self, prometheus_client):
        self.prometheus = prometheus_client
        self.thresholds = {
            'series_count_per_metric': 50000,
            'total_series_count': 500000,
            'growth_rate_per_minute': 0.1  # 10%
        }
    
    def check(self):
        alerts = []
        
        # 检查每个指标的series数量
        metrics = self.prometheus.get_all_metrics()
        for metric in metrics:
            series_count = self._get_series_count(metric)
            
            if series_count > self.thresholds['series_count_per_metric']:
                alerts.append({
                    'severity': 'critical',
                    'metric': metric.name,
                    'message': f"Metric {metric.name} has {series_count} series"
                })
            
            # 检查增长率
            growth_rate = self._calculate_growth_rate(metric)
            if growth_rate > self.thresholds['growth_rate_per_minute']:
                alerts.append({
                    'severity': 'warning',
                    'metric': metric.name,
                    'message': f"Metric {metric.name} growing too fast: {growth_rate*100}%/min"
                })
        
        return alerts
    
    def _get_series_count(self, metric):
        """获取指标的series数量"""
        return len(metric.time_series)
    
    def _calculate_growth_rate(self, metric):
        """计算指标增长率"""
        current = self._get_series_count(metric)
        previous = metric.previous_series_count
        
        if previous == 0:
            return 0
        
        return (current - previous) / previous

完整工作流程

指标治理完整流程:

┌─────────────────────────────────────────────────────────────────┐
│                        治理工作流程                              │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  1. 开发阶段:Label 规范检查                                      │
│     ┌──────────────────────────────────────────────────────┐     │
│     │ • 代码审查时检查Label配置                             │     │
│     │ • CI/CD流程中添加Label验证                            │     │
│     │ • 禁止使用高基类Label                                  │     │
│     └──────────────────────────────────────────────────────┘     │
│                           ↓                                      │
│  2. 运行时:指标注册与监控                                        │
│     ┌──────────────────────────────────────────────────────┐     │
│     │ • 记录指标基线                                        │     │
│     │ • 实时监控series数量                                  │     │
│     │ • 检测异常增长                                         │     │
│     └──────────────────────────────────────────────────────┘     │
│                           ↓                                      │
│  3. 发现问题:高基数指标处理                                       │
│     ┌──────────────────────────────────────────────────────┐     │
│     │ • 自动触发采样                                        │     │
│     │ • 创建聚合规则                                        │     │
│     │ • 发送告警通知                                        │     │
│     └──────────────────────────────────────────────────────┘     │
│                           ↓                                      │
│  4. 持续优化:定期审查与清理                                       │
│     ┌──────────────────────────────────────────────────────┐     │
│     │ • 分析高基数指标来源                                   │     │
│     │ • 优化Label设计                                        │     │
│     │ • 清理无效指标                                         │     │
│     └──────────────────────────────────────────────────────┘     │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Label 设计最佳实践

1. 推荐使用的 Label

✅ 静态分类Label:
• 接口类型:path, method, operation
• 环境信息:env, region, datacenter
• 服务信息:service, application, component
• 业务维度:user_type, plan_type, product_line

✅ 有限取值Label:
• status: 10-20种
• method: 5-10种
• level: 3-5种

✅ 布尔类型Label:
• is_mobile: true/false
• is_cached: true/false
• is_authenticated: true/false

2. 禁止使用的 Label

❌ 高基类Label:
• user_id, order_id, session_id
• token, jwt, access_token
• ip_address, client_id
• request_id (在高并发下)

❌ 动态变化Label:
• timestamp
• random_value
• 任何包含UUID的Label

❌ 过长值的Label:
• 完整URL
• 完整SQL语句
• 长字符串描述

配置建议

# Prometheus 配置
global:
  scrape_interval: 15s
  evaluation_interval: 15s

# 指标基数限制
metric_relabel_configs:
  - source_labels: [__name__]
    action: drop
    regex: '.*_id$'

# Recording Rules
groups:
  - name: aggregation_rules
    interval: 30s
    rules:
      - record: api_request_total:sum
        expr: sum by (method, status, path) (rate(api_request_total[5m]))
        
      - record: api_request_duration:p99
        expr: histogram_quantile(0.99, sum by (le, path) (rate(api_request_duration_bucket[5m])))

# 告警规则
groups:
  - name: cardinality_alerts
    rules:
      - alert: HighCardinalityMetric
        expr: cardinality(api_request_total) > 50000
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "指标 {{ $labels.name }} 基数过高"
          
      - alert: MetricSeriesGrowthRate
        expr: rate(cardinality(api_request_total[1m])) > 0.1
        for: 2m
        labels:
          severity: warning

效果对比

场景优化前优化后改善
单指标最大series数5,000,00050,000-99%
Prometheus内存占用64GB16GB-75%
抓取时间120s15s-87.5%
查询平均响应时间30s500ms-98.3%
OOM频率每周1-2次基本消除

总结

Prometheus 指标基数治理的核心原则:

  1. 预防为主:在开发阶段就避免高基类Label
  2. 分层治理:Label规范 → 运行时监控 → 智能采样 → 聚合规则
  3. 持续监控:实时监控series数量,设置增长率告警
  4. 定期优化:分析高基数指标来源,持续优化Label设计

记住:一个好的Label设计,胜过十个优化方案。从源头控制基数,才能从根本上解决基数爆炸问题。


源码获取

文章已同步至小程序博客栏目,需要源码的请关注小程序博客。

公众号:服务端技术精选

小程序码:


标题:SpringBoot + Prometheus 指标基数爆炸治理:Label 乱打导致内存飙升?聚合采样方案!
作者:jiangyi
地址:http://jiangyi.space/articles/2026/05/21/1779116460342.html
公众号:服务端技术精选
    评论
    0 评论
avatar

取消