mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4
2130 字
6 分钟
指标体系:Prometheus 与度量类型
2025-07-23

指标是可观测性中成本最低、查询最快的信号。一个设计良好的指标体系可以让你在秒级回答”系统现在健康吗?”、“P99 延迟是多少?”、“错误率趋势如何?“——而无需搜索 TB 级日志或扫描百万条追踪。

但指标也是最容易出问题的信号:基数爆炸导致存储膨胀、Histogram 分桶不当导致 P99 失真、指标命名不一致导致查询困难。本章将深入 Prometheus 的四大度量类型,讨论 Histogram 的分桶原理、基数爆炸的治理策略,以及如何设计一个可持续演进的指标体系。

一、指标的本质:聚合的权衡#

1.1 为什么需要指标?#

指标的本质是预聚合——在存储之前将原始事件聚合为时间序列,用少量的存储空间保留足够的信息量。

graph LR subgraph 原始事件[" 原始事件(高成本)"] E1["请求 1: 150ms"] E2["请求 2: 200ms"] E3["请求 3: 50ms"] E4["请求 N: 180ms"] end subgraph 聚合指标[" 聚合指标(低成本)"] M1["Counter: 4 请求"] M2["Histogram P50: 150ms"] M3["Histogram P99: 200ms"] M4["Gauge: 并发 12"] end E1 --> M1 E2 --> M1 E3 --> M1 E4 --> M1 E1 --> M2 E2 --> M2 E3 --> M2 E4 --> M2 style 原始事件 fill:#fce4ec,stroke:#880e4f style 聚合指标 fill:#e8f5e9,stroke:#2e7d32
维度原始事件(日志/追踪)聚合指标
存储成本高(每事件一条记录)低(预聚合时间序列)
查询速度慢(需要扫描)快(直接查询序列)
信息保真度高(完整事件)低(聚合后丢失细节)
适用场景排查具体问题发现趋势异常

1.2 指标的结构#

一个 Prometheus 指标由三部分组成:

http_request_duration_seconds{method="GET", status="200", endpoint="/api/v1/orders"} 0.150
│ │ │
│ │ └─ 数值
│ └─ 标签(Labels / 维度)
└─ 指标名称(Metric Name)

指标名称描述”度量什么”,标签描述”度量的维度”。同一个指标名称 + 不同标签值 = 不同的时间序列。

二、四大度量类型#

2.1 Counter:只增计数器#

Counter 是最简单的度量类型——它只能递增(或重置为零)。适用于累计计数场景:

// Go: 使用 OTel SDK 定义 Counter
import (
"go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel"
)
var requestCounter metric.Int64Counter
func init() {
meter := otel.Meter("order-service")
requestCounter, _ = meter.Int64Counter(
"http.server.request.total",
metric.WithDescription("Total number of HTTP requests"),
)
}
func handleRequest(w http.ResponseWriter, r *http.Request) {
// 递增 Counter
requestCounter.Add(r.Context(), 1,
metric.WithAttributes(
semconv.HTTPMethodKey.String(r.Method),
semconv.HTTPStatusCodeKey.Int(200),
semconv.HTTPRouteKey.String("/api/v1/orders"),
),
)
}

Counter 的关键规则

规则说明反模式
只能递增Counter 不能递减用 Counter 记录当前并发数
用 rate() 查询Counter 的绝对值无意义,变化率才有意义直接查询 Counter 值
重置处理服务重启时 Counter 归零,rate() 自动处理忽略重置导致尖峰
# 正确:查询请求速率
rate(http_server_request_total{service="order-service"}[5m])
# 错误:查询 Counter 绝对值
http_server_request_total{service="order-service"}

2.2 Gauge:可增可减仪表#

Gauge 可以任意增减,适用于”当前值”场景:

var activeConnections metric.Int64UpDownCounter
func handleConnection(conn net.Conn) {
activeConnections.Add(context.Background(), 1)
defer activeConnections.Add(context.Background(), -1)
// 处理连接...
}
场景示例说明
当前并发数http.server.active_connections每个连接 +1,断开 -1
内存使用量process.runtime.go.memory.heap_inuseGo 运行时自动采集
温度/CPUsystem.cpu.utilization系统指标
队列深度queue.depth消息队列当前深度
Note

Gauge 适合”当前状态”,Counter 适合”累计事件”。一个常见的错误是用 Gauge 记录请求总数——这会导致重启后数据丢失,且无法使用 rate() 计算速率。

2.3 Histogram:延迟分析之王#

Histogram 是可观测性中最重要的度量类型——它是 P50/P90/P99 延迟计算的基础。

Histogram 的工作原理

Histogram 将观测值分配到预定义的桶(bucket)中,每个桶记录”≤该桶上界的观测次数”:

// 定义 Histogram
var requestDuration metric.Float64Histogram
func init() {
meter := otel.Meter("order-service")
requestDuration, _ = meter.Float64Histogram(
"http.server.request.duration",
metric.WithUnit("s"),
metric.WithDescription("HTTP request duration in seconds"),
// 自定义分桶(默认分桶通常不够用)
metric.WithExplicitBucketBoundaries(
0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0,
),
)
}
func handleRequest(w http.ResponseWriter, r *http.Request) {
start := time.Now()
// 处理请求...
duration := time.Since(start).Seconds()
requestDuration.Record(r.Context(), duration,
metric.WithAttributes(
semconv.HTTPMethodKey.String(r.Method),
semconv.HTTPRouteKey.String("/api/v1/orders"),
),
)
}

Histogram 在 Prometheus 中的存储

# 一个 Histogram 产生 N+2 个时间序列(N = 桶数)
http_server_request_duration_bucket{le="0.005"} 10 # ≤5ms: 10 个请求
http_server_request_duration_bucket{le="0.01"} 25 # ≤10ms: 25 个请求
http_server_request_duration_bucket{le="0.025"} 50 # ≤25ms: 50 个请求
http_server_request_duration_bucket{le="0.05"} 80 # ≤50ms: 80 个请求
http_server_request_duration_bucket{le="0.1"} 95 # ≤100ms: 95 个请求
http_server_request_duration_bucket{le="0.25"} 99 # ≤250ms: 99 个请求
http_server_request_duration_bucket{le="0.5"} 100 # ≤500ms: 100 个请求
http_server_request_duration_bucket{le="+Inf"} 100 # 总请求数
http_server_request_duration_sum 3.5 # 总耗时
http_server_request_duration_count 100 # 总请求数

P99 计算

# 使用 histogram_quantile 计算 P99
histogram_quantile(0.99,
sum(rate(http_server_request_duration_bucket[5m])) by (le, service, endpoint)
)

2.4 Histogram 分桶设计#

分桶设计直接影响 P99 的准确性。Prometheus 默认分桶为:

0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10, +Inf

这些默认分桶是为”通用 Web 服务”设计的。如果你的服务延迟特征不同,需要自定义分桶:

场景推荐分桶说明
通用 Web 服务默认分桶覆盖 5ms ~ 10s
低延迟 API0.001, 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5侧重 1ms ~ 500ms
批处理任务1, 5, 10, 30, 60, 120, 300, 600侧重秒级 ~ 分钟级
数据库查询0.001, 0.005, 0.01, 0.05, 0.1, 0.5, 1, 5侧重毫秒级
Warning

Histogram 的 P99 是估算值,不是精确值。误差取决于 P99 落在哪个桶中。如果 P99 恰好落在两个桶之间,估算值会是桶的上界——这意味着 P99 会被高估。例如,如果真实 P99 = 180ms,而桶上界是 250ms,则 histogram_quantile 返回 250ms。

2.5 Summary vs Histogram#

维度HistogramSummary
计算位置服务端(Prometheus)客户端(应用)
聚合性可跨实例聚合不可跨实例聚合
分桶配置可动态调整固定
存储成本N+2 个序列/指标3+N 个序列/指标
准确性估算(受分桶影响)精确
推荐推荐仅特殊场景

结论:永远优先使用 Histogram。Summary 的不可聚合性是致命缺陷——当你有 10 个实例时,Summary 无法计算全局 P99。

三、基数爆炸:指标的阿喀琉斯之踵#

3.1 什么是基数爆炸?#

基数(Cardinality)是指一个指标的所有标签组合产生的唯一时间序列数量。当基数过高时,存储和查询性能急剧下降——这就是基数爆炸。

graph TB subgraph 低基数["低基数(可控)"] L1["service: 10 个服务"] L2["endpoint: 50 个路由"] L3["status_code: 5 个状态码"] L4["总计: 10 × 50 × 5 = 2,500 序列"] end subgraph 高基数[" 高基数(爆炸)"] H1["user_id: 1,000,000 个用户"] H2["request_id: 无限"] H3["ip_address: 2^32 个地址"] H4["总计: 百万~十亿序列"] end style 低基数 fill:#e8f5e9,stroke:#2e7d32 style 高基数 fill:#ffcdd2,stroke:#c62828

3.2 基数爆炸的数学#

总序列数 = 指标数 × ∏(每个标签的唯一值数)
标签组合唯一值数总序列数评估
service × endpoint × status10 × 50 × 52,500可控
service × user_id10 × 1,000,00010,000,000爆炸
service × endpoint × status × pod10 × 50 × 5 × 100250,000偏高
service × request_id10 × ∞致命

3.3 常见高基数标签#

标签唯一值数问题解决方案
user_id百万级每个用户一条序列移到日志/追踪,指标中不包含
request_id无限每个请求一条序列绝对不能出现在指标中
ip_address百万级每个客户端一条序列聚合为地区/ASN
session_id百万级每个会话一条序列移到日志
pod_name百级每次部署产生新序列deployment 替代
error_message千级每个错误消息一条序列error_code 替代

3.4 基数治理策略#

策略 1:标签白名单

# OTel Collector: 只允许低基数标签通过
processors:
filter:
error_mode: ignore
metrics:
metric:
- 'name == "http.server.request.duration" and attributes["user_id"] != nil'
# 移除高基数标签
transform:
error_mode: ignore
metric_statements:
- context: datapoint
statements:
- delete_key(attributes, "user_id")
- delete_key(attributes, "request_id")
- delete_key(attributes, "ip_address")

策略 2:高基数标签降维

// 将 IP 地址降维为地区
func ipToRegion(ip string) string {
// 使用 GeoIP 数据库将 IP 映射为地区
region := geoip.Lookup(ip).Region
return region // "us-east-1", "eu-west-1" 等
}
// 使用降维后的标签
requestDuration.Record(ctx, duration,
metric.WithAttributes(
semconv.HTTPMethodKey.String(method),
attribute.String("client_region", ipToRegion(clientIP)),
),
)

策略 3:高基数指标分离

graph LR METRIC["所有指标"] --> LOW["低基数指标<br/>→ Prometheus"] METRIC --> HIGH["高基数指标<br/>→ 日志/追踪"] style LOW fill:#e8f5e9,stroke:#2e7d32 style HIGH fill:#fff3e0,stroke:#e65100
Warning

每个时间序列大约占用 3-5 KB 内存(Prometheus)。100 万序列 = 3-5 GB 内存。如果你的 Prometheus 内存使用率持续上升,很可能是基数爆炸。使用 prometheus_cardinality_exporter 监控基数。

四、指标命名约定#

4.1 Prometheus 命名规范#

<namespace>_<subsystem>_<name>_<unit>
规则正确错误
小写下划线http_server_request_duration_secondshttpServerRequestDuration
单数形式http_server_request_durationhttp_server_request_durations
带单位后缀http_server_request_duration_secondshttp_server_request_duration
不带类型后缀http_server_request_durationhttp_server_request_duration_histogram

4.2 OTel 语义约定#

OpenTelemetry 定义了标准化的指标名称和属性:

OTel 指标说明对应 Prometheus
http.server.request.durationHTTP 请求耗时http_server_request_duration_seconds
http.server.request.totalHTTP 请求总数http_server_request_total
http.server.active_requests当前活跃请求http_server_active_requests
db.client.operation.duration数据库操作耗时db_client_operation_duration_seconds
process.runtime.go.memory.heap.inuseGo 堆内存使用go_memory_heap_inuse_bytes

五、PromQL 查询实战#

5.1 常用查询模式#

# 1. 请求速率(QPS)
sum(rate(http_server_request_total[5m])) by (service)
# 2. 错误率
sum(rate(http_server_request_total{status=~"5.."}[5m])) by (service)
/
sum(rate(http_server_request_total[5m])) by (service)
# 3. P50 / P90 / P99 延迟
histogram_quantile(0.50, sum(rate(http_server_request_duration_bucket[5m])) by (le, service))
histogram_quantile(0.90, sum(rate(http_server_request_duration_bucket[5m])) by (le, service))
histogram_quantile(0.99, sum(rate(http_server_request_duration_bucket[5m])) by (le, service))
# 4. 延迟 SLO 达成率(P99 < 500ms)
sum(rate(http_server_request_duration_bucket{le="0.5"}[5m])) by (service)
/
sum(rate(http_server_request_duration_count[5m])) by (service)
# 5. RED 方法(Rate-Errors-Duration)
# Rate
sum(rate(http_server_request_total[5m])) by (service, endpoint)
# Errors
sum(rate(http_server_request_total{status=~"5.."}[5m])) by (service, endpoint)
# Duration
histogram_quantile(0.99, sum(rate(http_server_request_duration_bucket[5m])) by (le, service, endpoint))
# 6. USE 方法(Utilization-Saturation-Errors)— 适用于资源指标
# Utilization: CPU 使用率
1 - avg(rate(node_cpu_seconds_total{mode="idle"}[5m]))
# Saturation: 运行队列长度
avg(node_load5)
# Errors: 设备错误
rate(node_disk_io_time_seconds_total[5m])

5.2 RED vs USE 方法#

方法适用对象三个指标说明
RED请求/服务Rate、Errors、Duration微服务排障首选
USE资源/硬件Utilization、Saturation、Errors基础设施排障首选

六、指标体系设计#

6.1 分层指标体系#

graph TB subgraph L1[" 业务指标层"] B1["订单创建速率"] B2["支付成功率"] B3["用户活跃度"] end subgraph L2[" 服务指标层(RED)"] S1["请求速率"] S2["错误率"] S3["P99 延迟"] end subgraph L3[" 基础指标层(USE)"] I1["CPU 使用率"] I2["内存使用率"] I3["磁盘 I/O"] end L1 --> L2 --> L3 style L1 fill:#e8eaf6,stroke:#283593 style L2 fill:#e0f2f1,stroke:#00695c style L3 fill:#fff3e0,stroke:#e65100
层级指标类型消费者更新频率
业务指标业务 KPI产品经理、业务方分钟级
服务指标RED开发者、SRE秒级
基础指标USE运维秒级

6.2 指标与 SLO 的关系#

指标是 SLI(Service Level Indicator)的实现,SLI 是 SLO 的度量:

SLO: 99.9% 的请求在 500ms 内完成
└─ SLI: 请求延迟 < 500ms 的比例
└─ 指标: histogram_quantile(0.999, rate(http_server_request_duration_bucket[5m]))

第 11 章:SLO 与告警中深入讨论 SLO 方法论。

七、动手实践:搭建指标体系#

7.1 部署 Prometheus#

# 使用 Docker Compose 启动 Prometheus
docker compose up -d prometheus
# 验证 Prometheus 运行
curl http://localhost:9090/api/v1/query?query=up

7.2 Prometheus 配置#

prometheus.yaml
global:
scrape_interval: 15s
evaluation_interval: 15s
scrape_configs:
- job_name: 'otel-collector'
static_configs:
- targets: ['otel-collector:8889']
metric_relabel_configs:
# 移除高基数标签
- source_labels: [__name__]
regex: '.*_bucket'
action: keep
- source_labels: [user_id]
regex: '.+'
action: drop # 丢弃包含 user_id 的样本
rule_files:
- 'recording_rules.yaml'
- 'alerting_rules.yaml'

7.3 Recording Rules#

# recording_rules.yaml — 预计算常用查询
groups:
- name: service:red
interval: 30s
rules:
# 请求速率
- record: service:http_requests:rate5m
expr: sum(rate(http_server_request_total[5m])) by (service, endpoint)
# 错误率
- record: service:http_errors:rate5m
expr: sum(rate(http_server_request_total{status=~"5.."}[5m])) by (service, endpoint)
# P99 延迟
- record: service:http_request_duration:p99
expr: histogram_quantile(0.99, sum(rate(http_server_request_duration_bucket[5m])) by (le, service, endpoint))

7.4 验证查询#

# 在 Prometheus UI 中验证
open http://localhost:9090
# 查询示例
# 1. 所有服务的请求速率
curl 'http://localhost:9090/api/v1/query?query=sum(rate(http_server_request_total[5m]))%20by%20(service)'
# 2. P99 延迟
curl 'http://localhost:9090/api/v1/query?query=histogram_quantile(0.99,sum(rate(http_server_request_duration_bucket[5m]))%20by%20(le,service))'
# 3. 基数检查
curl 'http://localhost:9090/api/v1/query?query=count(http_server_request_duration_bucket)'

7.5 验证清单#

检查项验证方式预期结果
Prometheus 采集数据up 查询targets 全部为 1
Histogram 数据正确histogram_quantile 查询P99 值合理
Recording Rules 生效查询 recording rule 名称返回预计算结果
基数可控count(metric) 查询序列数 < 预期阈值

八、本章小结#

上一章剖析了结构化日志与关联 ID。

主题核心要点关键词
四大度量类型Counter(只增计数)、Gauge(可增可减)、Histogram(延迟分析之王)、Summary(避免使用)。四大度量类型
Histogram 分桶分桶设计直接影响 P99 准确性,需要根据服务延迟特征自定义分桶。Histogram 分桶
基数爆炸高基数标签(user_id、request_id、IP)是指标的阿喀琉斯之踵,必须治理。基数爆炸
命名约定遵循 OTel 语义约定,确保跨服务一致性。命名约定
RED/USE 方法服务用 RED(Rate-Errors-Duration),资源用 USE(Utilization-Saturation-Errors)。RED/USE 方法
Recording Rules预计算常用查询,降低 Dashboard 和告警的查询延迟。Recording Rules

支持与分享

如果这篇文章对你有帮助,欢迎支持作者或分享给更多人

指标体系:Prometheus 与度量类型
https://blog.souloss.com/posts/observability/metrics-system/
作者
Souloss
发布于
2025-07-23
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时