在云原生和微服务架构盛行的今天,监控系统的重要性不言而喻。Prometheus 作为其中的佼佼者,凭借其强大的数据模型和查询语言,成为了监控领域的标准。
然而要真正发挥 Prometheus 的威力,首先需要深刻理解其核心——四大指标类型。这篇文章我们将深入实践,彻底搞懂 Counter、Gauge、Histogram 和 Summary,并结合实际场景,让你知道在何时何地选择合适的指标类型。准备好了吗?让我们开始这场监控之旅! 😎
🛒 实践场景想象你正在负责一个电商订单系统的监控,老板天天问你:
"今天卖了多少单?" 📊
"库存还剩多少?" 📦
"API响应慢不慢?" ⏱️
"订单金额分布如何?" 💰
这时候,Prometheus的四大指标类型就派上用场了!让我们通过一个真实的电商系统来搞懂它们。
📊 四大指标对比Prometheus 是一个开源的系统监控和告警工具包,它以其多维数据模型、强大的查询语言 PromQL、高效的时间序列数据库以及灵活的告警机制而闻名。
Prometheus 的核心是其时间序列数据。一个时间序列由一个指标名称(metric name)和一组标签(labels)唯一确定。
例如,http_requests_total{method="POST", handler="/api/messages"} 就是一个时间序列,它记录了对 /api/messages 路径的 POST 请求总数。
理解 Prometheus 的第一步,就是理解它如何收集和分类这些数据,这就要从它的四种基本指标类型说起。
指标类型
特性
电商案例
适用场景
关键操作
Counter
只增不减的累计值
订单总量
计数类指标
Inc()
Gauge
可增可减的瞬时值
库存数量
状态类指标
Set(), Inc(), Dec()
Histogram
观察值的分布统计
API响应时间
性能分析
Observe()
Summary
观察值的分位数统计
订单金额分布
业务分析
Observe()
🤔 Counter vs Gauge:什么时候用哪个?Counter: 像计步器,只会往上加 📈
Counter 是一个只增不减的累积型指标。它通常用于记录应用启动以来发生的事件总数,例如处理的 HTTP 请求总数、完成的任务数或出现的错误数。
比如,订单数量:今天1000单,明天1050单,错误次数:累计错误500次
Gauge: 像温度计,可上可下 🌡️
Gauge 是一个可以任意增减的瞬时值指标。它通常用于表示那些可以上下波动的测量值,例如当前的内存使用量、CPU 负载、队列中的任务数或活动的线程数。
比如,库存数量:现在100件,卖了10件剩90件,在线用户:当前1000人在线
🤔 Histogram vs Summary:都是分布,有啥区别?Histogram: 服务端聚合,客户端分桶 📊
Histogram 主要用于观察和分析事件的分布情况,最常见的场景是监控请求延迟或响应大小。它会将一段时间内的数据采样,并将其计入可配置的存储桶(bucket)中,同时也会提供所有采样值的总和(sum)和总数(count)。
优点是可以聚合多个实例的数据,但缺点主要在于分位数是估算的。主要适合API响应时间、请求大小等场景。
Summary: 客户端聚合,精确分位数 🎯
Summary 与 Histogram 类似,也用于观察事件的分布。但它不通过分桶,而是在客户端直接计算和存储分位数(Quantile)。
其优点在于分位数精确,缺点在于无法跨实例聚合,主要适合于业务指标、SLA监控。
🏗️ 系统架构💻 代码实现指标定义与初始化我们的电商系统定义了四种核心指标:
代码语言:go复制// Counter: 订单总量 - 只增不减的累计指标
var orderTotal = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "ecommerce_orders_total",
Help: "电商系统订单总数",
},
[]string{"status", "payment_method"}, // 标签:订单状态、支付方式
)
// Gauge: 库存数量 - 可增可减的瞬时指标
var inventoryStock = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "ecommerce_inventory_stock",
Help: "商品库存数量",
},
[]string{"product_id", "category"}, // 标签:商品ID、分类
)
// Histogram: API响应时间分布 - 观察值的分布情况
var apiDuration = prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Name: "ecommerce_api_duration_seconds",
Help: "API请求响应时间分布",
Buckets: prometheus.DefBuckets, // 默认桶
},
[]string{"method", "endpoint", "status_code"}, // 标签
)
// Summary: 订单金额分布 - 观察值的分位数统计
var orderAmount = prometheus.NewSummaryVec(
prometheus.SummaryOpts{
Name: "ecommerce_order_amount_yuan",
Help: "订单金额分布统计",
Objectives: map[float64]float64{
0.5: 0.05, // 50分位数,误差5%
0.9: 0.01, // 90分位数,误差1%
0.99: 0.001, // 99分位数,误差0.1%
},
},
[]string{"user_type", "promotion"}, // 标签
)业务逻辑实现创建订单 - 四大指标的综合应用
代码语言:go复制func (e *ECommerceSystem) CreateOrder(productID string, quantity int, amount float64, userType, paymentMethod string) {
start := time.Now()
// 模拟API处理时间
processingTime := time.Duration(rand.Intn(500)+50) * time.Millisecond
time.Sleep(processingTime)
// 检查库存
if stock, exists := e.products[productID]; exists && stock >= quantity {
// 库存充足,创建订单成功
e.products[productID] -= quantity
// 🔢 Counter: 订单成功计数 +1
orderTotal.WithLabelValues("success", paymentMethod).Inc()
// 📊 Gauge: 更新库存数量
inventoryStock.WithLabelValues(productID, "electronics").Set(float64(e.products[productID]))
// 💰 Summary: 记录订单金额
orderAmount.WithLabelValues(userType, "none").Observe(amount)
// ⏱️ Histogram: 记录API响应时间
apiDuration.WithLabelValues("POST", "/api/orders", "200").Observe(time.Since(start).Seconds())
log.Printf("✅ 订单创建成功: 商品=%s, 数量=%d, 金额=%.2f元", productID, quantity, amount)
} else {
// 库存不足,订单失败
orderTotal.WithLabelValues("failed", paymentMethod).Inc()
apiDuration.WithLabelValues("POST", "/api/orders", "400").Observe(time.Since(start).Seconds())
log.Printf("❌ 订单创建失败: 商品=%s库存不足", productID)
}
}关键代码解析Counter使用技巧:
代码语言:go复制// ✅ 正确:使用标签区分不同状态
orderTotal.WithLabelValues("success", "alipay").Inc()
// ❌ 错误:Counter不能减少
// orderTotal.Dec() // 这会panic!为什么用 Counter? 因为 订单数量是一个不断累积的量。我们不关心某个瞬间的绝对值(因为它只会越来越大),而是关心它在单位时间内的变化率,比如“每秒请求数”(RPS)。Counter + rate() 函数的组合完美地满足了这一需求。
Gauge使用技巧:
代码语言:go复制// ✅ 设置绝对值
inventoryStock.WithLabelValues("iphone15", "electronics").Set(100)
// ✅ 增加/减少
inventoryStock.WithLabelValues("iphone15", "electronics").Inc()
inventoryStock.WithLabelValues("iphone15", "electronics").Dec()为什么用 Gauge? 因为库存数量不是一个累积值,它会随着系统的负载情况动态变化。我们关心的是它在某个时间点的确切值,以便判断系统是否健康(例如,队列长度是否过长,是否需要扩容处理节点)。
Histogram vs Summary:
代码语言:go复制// Histogram: 适合可聚合的性能指标
apiDuration.WithLabelValues("POST", "/api/orders", "200").Observe(0.123)
// Summary: 适合业务分位数分析
orderAmount.WithLabelValues("vip", "double11").Observe(15999.0)为什么用 Histogram? 因为它提供了数据的分布视图。通过观察不同延迟区间的请求数量,我们可以更精确地定位性能瓶颈。例如,如果我们发现大量请求落在了 500ms-1s 的桶里,这就明确指出了一个需要优化的方向。
为什么用 Summary? 如果你的核心需求是获取精确的分位数,并且可以接受其不可聚合的限制,Summary 是一个直接的选择。
Histogram vs. Summary 对比直方图与汇总对比
特性
Histogram
Summary
分位数计算
服务端 (histogram_quantile)
客户端 (直接暴露)
聚合能力
可聚合(跨实例计算分位数)
不可聚合
性能开销
客户端较低,服务端较高
客户端较高,服务端较低
配置
需要预先定义 buckets
需要定义 quantiles 和其误差
在绝大多数情况下,优先选择 Histogram。它的灵活性和可聚合性使其在现代分布式系统中更为实用。只有在你明确知道不需要聚合,并且对客户端性能开销不敏感时,才考虑使用 Summary。