前言

在微服务和云原生架构的世界中,一套强大的监控系统是保障服务稳定性的基石。Prometheus 作为 CNCF 的明星项目,凭借其简单高效的特性,已成为事实上的云原生监控标准。本文将深入剖析 Prometheus 的四大数据类型及其 PromQL 查询语言,帮助开发团队构建强大的可观测性系统。

结论先行:Prometheus 四大数据类型速览

特性 Counter Gauge Histogram Summary
定义 只增不减的累积计数器 可增可减的瞬时值 观测值分布的分桶统计 客户端计算的分位数统计
重置行为 服务重启时归零 保持当前值 桶计数归零 计数归零
典型应用 请求计数、错误数、流量统计 温度、内存使用、连接数 请求延迟、响应大小 请求延迟、队列等待时间
数据点 单一值 单一值 _bucket、_sum、count {quantile="x"}、_sum、_count
查询重点 rate()、increase() 直接使用、预测函数 histogram_quantile() 直接读取分位数
分布式聚合 可以(sum、rate) 可以(avg、max、min) 可以(百分位也可聚合) 有限(分位数不可聚合)
资源消耗 中(依赖桶数量) 中(客户端计算)

一、Prometheus 核心数据类型详解

1. Counter(计数器):持续增长的累积值

Counter 是最简单但也最常用的指标类型,代表一个只增不减的累积数值。每当事件发生,计数器增加;当监控目标重启时,计数器归零。

适用场景

  • API 请求总数
  • 错误发生次数
  • 处理任务的数量
  • 网络流量字节数

正确的代码实现

1
2
3
4
5
6
7
8
9
10
11
12
// 声明带标签的计数器
requestCounter := prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "http_requests_total",
Help: "Total number of HTTP requests",
},
[]string{"method", "path", "status"}, // 定义标签维度
)
prometheus.MustRegister(requestCounter)

// 使用标签记录请求
requestCounter.WithLabelValues("GET", "/api/users", "200").Inc()

PromQL 查询技巧

1
2
3
4
5
6
7
8
# 每秒请求率(5分钟窗口)
rate(http_requests_total{status="200"}[5m])

# 错误率计算
sum(rate(http_requests_total{status=~"5.."}[5m])) / sum(rate(http_requests_total[5m]))

# 1小时内的请求增量
increase(http_requests_total[1h])

最佳实践

  • 永远不要直接使用 Counter 的原始值,总是使用 rate()increase()
  • 使用有意义的标签进行多维度分析,但避免高基数标签
  • Counter 重置(如服务重启)会被 rate() 函数自动处理

2. Gauge(仪表盘):可变的瞬时值

Gauge 表示一个可增可减的瞬时测量值,反映系统的当前状态。

适用场景

  • 内存使用量
  • CPU 使用率
  • 当前活跃连接数
  • 队列深度
  • 温度等物理量

正确的代码实现

1
2
3
4
5
6
7
8
9
10
11
12
// 声明带标签的仪表盘
memoryGauge := prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "app_memory_usage_bytes",
Help: "Current memory usage in bytes",
},
[]string{"component", "instance"},
)
prometheus.MustRegister(memoryGauge)

// 设置当前值
memoryGauge.WithLabelValues("api-server", "instance-1").Set(float64(getCurrentMemoryUsage()))

PromQL 查询技巧

1
2
3
4
5
6
7
8
9
10
11
12
# 直接使用当前值
app_memory_usage_bytes{component="api-server"}

# 统计聚合
avg_over_time(app_memory_usage_bytes[1h])
max_over_time(app_memory_usage_bytes[24h])

# 趋势预测(线性回归)
predict_linear(app_memory_usage_bytes[6h], 4 * 3600)

# 计算变化率
(app_memory_usage_bytes - app_memory_usage_bytes offset 1h) / app_memory_usage_bytes offset 1h

最佳实践

  • Gauge 可以直接使用其瞬时值,不需要像 Counter 那样使用 rate
  • 对于容易波动的指标,考虑使用 avg_over_time 平滑数据
  • 利用 predict_linear 进行容量规划和趋势预测

3. Histogram(直方图):观测值分布的分桶统计

Histogram 允许对观测值(如请求延迟)进行分布式统计,将数据分散到预定义的桶中,是分析性能分布的理想工具。

自动生成的指标

  • <metric>_bucket{le="<upper bound>"}: 小于等于特定阈值的观测值计数
  • <metric>_sum: 所有观测值的总和
  • <metric>_count: 观测值总数

适用场景

  • 请求延迟分布
  • 响应大小分布
  • 批处理任务执行时间
  • 任何需要百分位数分析的场景

正确的代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
// 声明带标签的直方图
durationHistogram := prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Name: "http_request_duration_seconds",
Help: "HTTP request duration in seconds",
Buckets: prometheus.ExponentialBuckets(0.001, 2, 10), // 从1ms开始指数增长
},
[]string{"method", "path"},
)
prometheus.MustRegister(durationHistogram)

// 记录请求延迟
durationHistogram.WithLabelValues("GET", "/api/users").Observe(responseTime)

PromQL 查询技巧

1
2
3
4
5
6
7
8
9
10
11
# 计算平均响应时间
rate(http_request_duration_seconds_sum[5m]) / rate(http_request_duration_seconds_count[5m])

# 计算P90延迟
histogram_quantile(0.9, rate(http_request_duration_seconds_bucket[5m]))

# 按API路径分析P95延迟
histogram_quantile(0.95, sum by(path, le) (rate(http_request_duration_seconds_bucket[5m])))

# 计算SLO:延迟小于100ms的请求比例
sum(rate(http_request_duration_seconds_bucket{le="0.1"}[5m])) / sum(rate(http_request_duration_seconds_count[5m]))

最佳实践

  • 仔细设计桶边界,覆盖关键分位数区域
  • 对于延迟指标,通常使用指数桶比线性桶更合理
  • 利用 histogram_quantile 计算任意分位数
  • 桶的数量会影响存储和性能,权衡精度和开销

4. Summary(摘要):客户端计算的分位数统计

Summary 与 Histogram 类似,但在客户端直接计算并存储分位数,无需服务器端计算。

自动生成的指标

  • <metric>{quantile="<φ>"}: φ 分位数的值
  • <metric>_sum: 所有观测值的总和
  • <metric>_count: 观测值总数

适用场景

  • 需要高精度分位数的场景
  • 客户端计算分位数更高效的情况
  • 对服务器端聚合要求不高的场景

正确的代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
// 声明带标签的摘要
durationSummary := prometheus.NewSummaryVec(
prometheus.SummaryOpts{
Name: "http_request_duration_seconds_summary",
Help: "HTTP request duration in seconds",
Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001},
},
[]string{"method", "path"},
)
prometheus.MustRegister(durationSummary)

// 记录请求延迟
durationSummary.WithLabelValues("POST", "/api/login").Observe(responseTime)

PromQL 查询技巧

1
2
3
4
5
6
7
8
# 直接读取P99延迟
http_request_duration_seconds_summary{quantile="0.99", method="GET", path="/api/users"}

# 计算平均响应时间
rate(http_request_duration_seconds_summary_sum[5m]) / rate(http_request_duration_seconds_summary_count[5m])

# 每个服务的中位数延迟
max by(service) (http_request_duration_seconds_summary{quantile="0.5"})

最佳实践与限制

  • Summary 预计算的分位数不能跨实例聚合(这是关键限制)
  • 适用于分位数精度要求高且实例相对独立的场景
  • 客户端计算分位数会增加应用资源消耗
  • 分位数设置后不可更改,需提前规划好监控需求

二、PromQL 查询语言精通

PromQL 是 Prometheus 的强大武器,掌握它能让我们精确提取所需的监控数据。

1. 基础查询与标签选择

1
2
3
4
5
6
7
8
# 基本查询与精确匹配
http_requests_total{status="200", method="GET"}

# 正则表达式匹配
http_requests_total{path=~"/api/v1/.+", method!="OPTIONS"}

# 范围查询(返回时间序列)
http_requests_total{status="500"}[5m]

2. 操作符与函数

算术运算符

1
2
# 计算内存使用率百分比
100 * (node_memory_MemTotal_bytes - node_memory_MemAvailable_bytes) / node_memory_MemTotal_bytes

聚合函数

1
2
3
4
5
# 按服务和路径分组求和
sum by(service, path) (rate(http_requests_total[5m]))

# 丢弃instance标签求最大值
max without(instance) (node_cpu_seconds_total)

瞬时向量函数

1
2
3
4
5
# 标签替换
label_replace(up, "host", "$1", "instance", "(.*):.*")

# 按标签分组取topk
topk by(path) (5, http_request_duration_seconds_sum / http_request_duration_seconds_count)

3. 复杂查询模式

SLI/SLO 监控

1
2
3
4
5
# 服务可用性SLI
sum(rate(http_requests_total{status=~"2..|3.."}[5m])) / sum(rate(http_requests_total[5m]))

# 延迟SLO
histogram_quantile(0.99, sum by(le) (rate(http_request_duration_seconds_bucket[5m]))) < 0.3

异常检测

1
2
3
# 相对于历史同期的异常增长
rate(http_requests_total[5m])
> 2 * avg_over_time(rate(http_requests_total[5m])[1d:5m] offset 1d)

预测分析

1
2
# 磁盘空间预测
predict_linear(node_filesystem_free_bytes{mountpoint="/"}[6h], 7 * 24 * 3600) < 10 * 1024 * 1024 * 1024

三、实战应用场景

1. 服务健康度监控

RED 方法实现

1
2
3
4
5
6
7
8
# Rate - 请求率
sum by(service) (rate(http_requests_total[5m]))

# Error - 错误率
sum by(service) (rate(http_requests_total{status=~"5.."}[5m])) / sum by(service) (rate(http_requests_total[5m]))

# Duration - P95延迟
histogram_quantile(0.95, sum by(service, le) (rate(http_request_duration_seconds_bucket[5m])))

服务依赖健康度

1
2
3
4
5
# 数据库查询错误率
sum(rate(database_query_errors_total[5m])) / sum(rate(database_queries_total[5m]))

# 第三方API调用延迟
histogram_quantile(0.99, sum by(api_name, le) (rate(api_request_duration_seconds_bucket[5m])))

2. 性能瓶颈分析

热点 API 发现

1
2
3
4
5
6
7
# 延迟最高的10个接口
topk(10,
histogram_quantile(0.95, sum by(method, path, le) (rate(http_request_duration_seconds_bucket[5m])))
)

# 请求量最大的接口
topk(10, sum by(method, path) (rate(http_requests_total[5m])))

数据库性能分析

1
2
3
4
5
# 平均查询时间趋势
rate(db_query_duration_seconds_sum[5m]) / rate(db_query_duration_seconds_count[5m])

# 慢查询比例
sum(rate(db_query_duration_seconds_bucket{le="+Inf"}[5m])) - sum(rate(db_query_duration_seconds_bucket{le="0.1"}[5m])) / sum(rate(db_query_duration_seconds_bucket{le="+Inf"}[5m]))

3. 容量规划与告警

资源预测

1
2
3
4
5
# CPU使用率预测
predict_linear(avg by(instance) (rate(node_cpu_seconds_total{mode!="idle"}[6h])) [3d:], 7 * 24 * 3600) > 0.85

# 内存压力告警
(node_memory_MemTotal_bytes - node_memory_MemAvailable_bytes) / node_memory_MemTotal_bytes > 0.9

流量容量规划

1
2
# 带宽使用预测
predict_linear(rate(node_network_transmit_bytes_total[12h])[7d:], 30 * 24 * 3600)

四、最佳实践与性能优化

1. 指标命名与标签设计

命名规范

  • 使用 snake_case
  • 包含单位后缀(_bytes, _seconds, _total)
  • 保持风格一致性

标签最佳实践

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 合理设计标签维度
apiLatency := prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Name: "api_request_duration_seconds",
Help: "API request duration in seconds",
Buckets: prometheus.ExponentialBuckets(0.001, 2, 10),
},
[]string{"service", "endpoint", "status_code"}, // 合理的低基数标签
)

// 不可变标签使用ConstLabels
prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "service_info",
Help: "Service information",
ConstLabels: prometheus.Labels{"version": "v2.1.3", "environment": "production"},
},
[]string{"instance"},
)

2. 客户端性能优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 缓存常用标签组合以提高性能
getCounter := requestCounter.WithLabelValues("GET", "/api/users", "200")
for i := 0; i < 100; i++ {
getCounter.Inc() // 重用标签组合,避免重复创建
}

// 批量更新方式
var rpcDurations = prometheus.NewSummaryVec(
prometheus.SummaryOpts{
Name: "rpc_durations_seconds",
Help: "RPC latency distributions.",
Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001},
},
[]string{"service"},
)

func ObserveBatch(durations map[string]float64) {
for service, duration := range durations {
rpcDurations.WithLabelValues(service).Observe(duration)
}
}

3. 查询优化

1
2
3
4
5
6
7
8
9
10
# 优化前:高基数查询
sum(rate(http_requests_total{path=~"/api/.*"}[5m])) by (path, method, status)

# 优化后:降低基数,按需聚合
sum(rate(http_requests_total{path=~"/api/.*"}[5m])) by (method, status)

# 优化聚合顺序(先聚合再求和)
sum(
avg by(instance) (rate(node_cpu_seconds_total{mode!="idle"}[5m]))
)

五、常见陷阱与解决方案

1. 高基数问题

问题:标签组合过多导致时间序列爆炸 解决方案

  • 限制标签基数,避免使用 UserID、SessionID 等作为标签
  • 使用label_replace和正则表达式转换高基数标签
  • 考虑使用 Exemplars 而非标签存储高基数数据

2. 数据类型选择误区

Counter vs Gauge:请求数应使用 Counter 而非 Gauge Histogram vs Summary:需要聚合分析请使用 Histogram,精确分位数可选 Summary

3. 查询性能问题

问题:复杂查询导致 Prometheus 高负载 解决方案

  • 使用记录规则预计算常用查询
  • 合理设置 scrape 间隔,避免过度采集
  • 对高请求量接口使用客户端聚合

总结与展望

Prometheus 的四种数据类型各有所长:Counter 适合累积事件计数,Gauge 适合瞬时状态测量,Histogram 适合分布统计和百分位分析,Summary 适合客户端精确分位数计算。与之配合的 PromQL 提供了强大的数据查询和分析能力,共同构成了完整的监控解决方案。

随着云原生技术的发展,Prometheus 生态也在不断壮大,与 Grafana、Alertmanager、Thanos 等工具集成,能够构建更完善的监控告警平台。在微服务架构中,结合 RED(Rate、Error、Duration)和 USE(Utilization、Saturation、Errors)方法论,可以构建全面的可观测性系统。

无论你是刚开始使用 Prometheus 的新手,还是寻求优化监控系统的资深工程师,希望本文对你理解和应用 Prometheus 有所帮助。记住,好的监控不仅能及时发现问题,更能预测和防范问题,最终服务于业务可靠性和用户体验的提升。


参考资源:

  • Prometheus 官方文档: https://prometheus.io/docs/
  • Google SRE 书籍: https://sre.google/sre-book/monitoring-distributed-systems/
  • Prometheus 实战: https://prometheusbook.com/