跳到主要内容

Grafana 面板配置学习

Prometheus 学习

Prometheus 的指标类型的数据对象都是数字,如果要监控文本类的信息只能通过指标名称或者 label 来呈现

server_requests_duration_sec_bucket{kind="http",operation="/common.v1.ToolsService/GetQRCode",le="0.005"} 0

Prometheus 四种 metric 类型

  • Counter 单调递增的计数器
  • Gauge 仪表
  • Histogram 直方图
  • Summary

数据格式

Prometheus 会将所有采集到的样本数据以时间序列的方式保存在内存数据库中,并且定时保存到硬盘上。时间序列是按照时间戳和值的序列顺序存放的,我们称之为向量(vector),每条时间序列通过指标名称(metrics name)和一组标签集(labelset)命名。如下所示,可以将时间序列理解为一个以时间为 X 轴的数字矩阵:

  ^
│ . . . . . . . . . . . . . . . . . . . node_cpu_seconds_total{cpu="cpu0",mode="idle"}
│ . . . . . . . . . . . . . . . . . . . node_cpu_seconds_total{cpu="cpu0",mode="system"}
│ . . . . . . . . . . . . . . . . . . node_load1{}
│ . . . . . . . . . . . . . . . . . .
v
<------------------ 时间 ---------------->

在时间序列中的每一个点称为一个样本(sample),样本由以下三部分组成:

  • 指标(metric):指标名和描述当前样本特征的标签集合
  • 时间戳(timestamp):一个精确到毫秒的时间戳
  • 样本值(value): 一个 float64 的浮点型数据表示当前样本的值
<--------------- metric ---------------------><-timestamp -><-value->
http_request_total{status="200", method="GET"}@1434417560938 => 94355
http_request_total{status="200", method="GET"}@1434417561287 => 94334

http_request_total{status="404", method="GET"}@1434417560938 => 38473
http_request_total{status="404", method="GET"}@1434417561287 => 38544

http_request_total{status="200", method="POST"}@1434417560938 => 4748
http_request_total{status="200", method="POST"}@1434417561287 => 4785

Counter

Counter 是一个累计类型的数据指标,它代表单调递增的计数器。

其值只能在重新启动时增加或重置为 0,可以使用计数器来表示已响应的 HTTP 请求数,这个数一定是不断增长的。

例如 Prometheus自身 中 metrics 的 http 请求总数

client_requests_code_total{code="0",kind="http",operation="/common.v1.AdvertiseService/ForumPostList",reason=""} 18

20230302100116

例如在 Prometheus Go SDK

type Counter interface {
Metric
Collector

// 加1
Inc()

// 增加一个非负的float64类型的值
Add(float64)
}

可见 Counter 接口原始定义里,只对外暴露了 Inc()Add() 这两个增加接口,目的很明确,只希望这个值只增不减。

但是有一点格外需要注意:

若 Exporter 重启了,则 Counter 类型的 Metric 的值,必然是重新从 0 开始。

Gauge

Gauge 是可以任意上下波动数值的指标类型。

也即 Gauge 的值可增可减,可升可降。

例如:进程的堆内存的使用率,可大可小。

20230302100402

go_memstats_heap_released_bytes 6.946816e+06

20230302100542

例如在 Prometheus Go SDK

type Gauge interface {
Metric
Collector

// 指定为任意值
Set(float64)
// 加1
Inc()
// 减1
Dec()
// 加任意值,该值可正可负。<注意这里和Counter类型的定义是不同的>
Add(float64)
// 减任意值,该值可正可负
Sub(float64)

// 将值设置成当前时间戳,单位s
SetToCurrentTime()
}

Histogram

Histogram 中文的含义是直方图,我们在学习概率统计的时候都学习过直方图。直方图是对数据如何分布的一种总结方式——有多少值是高的,有多少是低的,有多少介于两者之间。

20230302103600

如图所示,Histogram 显示了数据集的分布。有些值低(<= 10),有些值中等(> 10 和 <= 100),有些值高(> 100 和 <= 1000)。

对应到代码里面就是设置不同的桶(bucket),每个桶代表一个范围,然后统计每个桶里面有多少个值。

package main

import (
"log"
"math/rand"
"net/http"
"time"

"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
"github.com/prometheus/client_golang/prometheus/promhttp"
)

func main() {
// 定义一个Histogram类型的指标
histogram := promauto.NewHistogram(prometheus.HistogramOpts{
Name: "server_requests_duration_sec",
// 公司的桶设置的是 0.2, 0.5, 1, 2, 5, 10, 30
Buckets: []float64{0.005, 0.01, 0.025, 0.05, 0.1, 0.250, 0.5, 1}, // 根据场景需求配置bucket的范围
})

go func() {
for {
// 这里搜集一些0-1之间的随机数
// 实际应用中,这里可以搜集系统耗时等指标
histogram.Observe(rand.Float64() * 1)
time.Sleep(1 * time.Second)
}
}()
// 指标上报的路径,可以通过该路径获取实时的监控数据
http.Handle("/metrics", promhttp.Handler())
log.Fatal(http.ListenAndServe(":8080", nil))
}

上面这段程序做的事情就是 模拟了一个后端服务的接口,每次调用都会有不同的耗时(这里使用随机数生成),耗时的数据会被 Prometheus 的客户端采集。

20230302104837

注意:这里的 le 为小于等于。例如 le=”5“,即pod启动耗时 <= 5s的有87次

可以看到采集了 0.005, 0.01, 0.025, 0.05, 0.1, 0.250, 0.5, 1 这几个桶的数据。由此可以看出这个接口的耗时的分布情况,即全部请求在 0.25s 以内。

注意:这里的 +inf 表示为无穷时

上面只展示了 bucket 但是实际上 Prometheus 通过三个 metrics 名称来完整暴露一组 Histogram

  • 桶累积计数器,\_bucket{le=""}
  • 所有观察值的总和,\_sum
  • 已观察到的事件计数,\_count 与上述相同 \_bucket{le="+Inf"}

如下:

server_requests_duration_sec_bucket{kind="http",operation="/common.v1.AdvertiseService/ForumPostList",le="+Inf"} 18
server_requests_duration_sec_sum{kind="http",operation="/common.v1.AdvertiseService/ForumPostList"} 0.008255371 # 总耗时
server_requests_duration_sec_count{kind="http",operation="/common.v1.AdvertiseService/ForumPostList"} 18 # 服务器请求持续时间

首先,从上面的指标收集图中,可以看到 Histogram 类型的数据还会收集了一个 _count 类数据,我们可以用它来了解每秒监控的频率。可用于观测每秒收到多少请求。使用以下 Prometheus 查询可以进行观察:

例如用上面的数据观察变化率:

rate(server_requests_duration_sec_count{operation="/common.v1.AdvertiseService/ForumPostList"}[1d])

此查询获取最后一天 ([1d]) 的观察结果并计算计数的每秒变化率。(一般是统计最后一分钟的)

20230302110020

rate 函数参考 https://yunlzheng.gitbook.io/prometheus-book/parti-prometheus-ji-chu/promql/prometheus-promql-functions#ji-suan-counter-zhi-biao-zeng-chang-lv

但是 Histogram 最主要的用途是观测分布:

这是实际使用中,我们最需要 Histogram 的地方。我们有一些高频事件(再次以Web请求来举例),会在 Prometheus 的每个抓取间隔之间进行了大量数据采集(例如请求持续时间),然后使用 Histogram 来记录观察结果,我们想查询该度量,以深入了解值在抓取间隔内和随时间的分布情况。

这里使用 histogram_quantile 函数聚合,计算 Prometheus http 所有请求中80百分位的值

histogram_quantile(0.8, sum(rate(server_requests_duration_sec_bucket[1d])) by (le))

20230302112200

即一天内 80% 的请求响应时间 <= 0.07s

Histogram 的使用场景

使用时也很简单,Histogram 类型一共包含一个常规方法,如下:

  • Observe(float64) 将一个观察值添加到直方图。

Histogram 类型在应用场景中非常的常用,因为其代表的就是分组区间的统计,而在分布式场景盛行的现在,链路追踪系统是必不可少的,那么针对不同的链路的分析统计就非常的有必要,例如像是对 RPC、SQL、HTTP、Redis 的 P90、P95、P99 进行计算统计,并且更进一步的做告警,就能够及时的发现应用链路缓慢,进而发现和减少第三方系统的影响。

我们模仿记录 HTTP 调用响应时间的应用场景:

var HttpDurationsHistogram = prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Name: "http_durations_histogram_seconds",
Buckets: []float64{0.2, 0.5, 1, 2, 5, 10, 30},
},
[]string{"path"},
)

func init() {
prometheus.MustRegister(HttpDurationsHistogram)
}

func main() {
...
engine.GET("/histogram", func(c *gin.Context) {
purl, _ := url.Parse(c.Request.RequestURI)
HttpDurationsHistogram.With(prometheus.Labels{"path": purl.Path}).Observe(float64(rand.Intn(30)))
})
engine.GET("/metrics", gin.WrapH(promhttp.Handler()))
engine.Run(":10001")
}

1、接口的 QPS: 接口的 QPS = query per second, 每秒接收的请求量,这个是反映业务指标的。我们一般用 counter 定义指标收集,但是也可以直接对 Histogram 收集的 count 进行查询处理。

rate(server_requests_duration_sec_count[1m])

2、接口的耗时

一般来说,我们会监控核心接口的 p50,p95 和 p99 耗时,这些都可以通过 histogram_quantile 函数来对 Histogram 类型的数据进行查询直接得到。

histogram_quantile(0.50, sum(rate(server_requests_duration_sec_bucket{operation="/common.v1.AdvertiseService/ForumPostList"}[5m])) by (le))
histogram_quantile(0.95, sum(rate(server_requests_duration_sec_bucket{operation="/common.v1.AdvertiseService/ForumPostList"}[5m])) by (le))
histogram_quantile(0.99, sum(rate(server_requests_duration_sec_bucket{operation="/common.v1.AdvertiseService/ForumPostList"}[5m])) by (le))

20230302113146

Summary 的使用

Summary 和 Histogram 主用用于统计和分析样本的分布情况

# HELP go_gc_duration_seconds A summary of the GC invocation durations.
# TYPE go_gc_duration_seconds summary
go_gc_duration_seconds{quantile="0"} 3.4503e-05
go_gc_duration_seconds{quantile="0.25"} 4.177e-05
go_gc_duration_seconds{quantile="0.5"} 5.6712e-05
go_gc_duration_seconds{quantile="0.75"} 0.000106047 第75分位的GC耗时
go_gc_duration_seconds{quantile="1"} 0.003085382
go_gc_duration_seconds_sum 0.203466011 总共的GC耗时
go_gc_duration_seconds_count 1382 总共的GC次数

两者的区别在于,Summary 记录的是百分位值,类似 P99,P95 的接口请求时间,而 histogram 是柱状图,横坐标是自己定义的桶,纵坐标而是落到桶里的数量,如将请求时间分为 0~1秒,1~2秒,2 秒以上,histogram则体现分别落在这三个区间的请求数量

取得接口响应时间的排行

常用的指标场景之一就是排行,例如取得最慢的接口,最常用的接口等等。这里我们使用 Prometheus 的 topk 函数来实现。

topk(15, sum by (operation)(rate(server_requests_duration_sec_sum{job="$job"}[5m])))

PromQL 学习

基本概念

PromQL 是一种嵌套的函数式语言,比如下面的查询语句:

# 查询的根,最终结果表示一个近似分位数。
histogram_quantile(
# histogram_quantile() 的第一个参数,分位数的目标值
0.9,
# histogram_quantile() 的第二个参数,聚合的直方图
sum by(le, method, path) (
# sum() 的参数,直方图过去5分钟每秒增量。
rate(
# rate() 的参数,过去5分钟的原始直方图序列
demo_api_request_duration_seconds_bucket{job="demo"}[5m]
)
)
)

同样的的,作为两个维度的数据库,它和关系型数据库一样都有聚合操作,但是它聚合的是值,一般是根据时间取得区间

比如要查询最近 5 分钟的可用内存,可以执行下面的查询语句:

demo_memory_usage_bytes{type="free"}[5m]

可以使用的有效的时间单位为:

  • ms -毫秒
  • s -秒
  • m - 分钟
  • h - 小时
  • d - 天
  • y - 年

将得到如下所示的查询结果:

20230302152340

有时我们还需要以时移方式访问过去的数据,通常用来与当前数据进行比较。要将过去的数据时移到当前位置,可以使用 offset <duration> 修饰符添加到任何范围或即时序列选择器进行查询(例如 my_metric offset 5mmy_metric[1m] offset 7d)。

例如,要选择一个小时前的可用内存,可以使用下面的查询语句:

demo_memory_usage_bytes{type="free"} offset 1h

20230302152525

变化率

通常来说直接绘制一个原始的 Counter 类型的指标数据用处不大,因为它们会一直增加,一般来说是不会去直接关心这个数值的,因为 Counter 一旦重置,总计数就没有意义了,比如我们直接执行下面的查询语句:

demo_api_request_duration_seconds_count{job="demo"}

用于计算变化率的最常见函数是 rate() 它用于计算在指定时间范围内计数器平均每秒的增加量。因为是计算一个时间范围内的平均值,所以我们需要在序列选择器之后添加一个范围选择器。

例如我们要计算 demo_api_request_duration_seconds_count 在最近五分钟内的每秒平均变化率,则可以使用下面的查询语句:

rate(demo_api_request_duration_seconds_count[5m])

20230302152955

对于 rate() 和相关函数有几个需要说明的:

当被抓取指标进的程重启时,Counter 指标可能会重置为 0,但 rate() 函数会自动处理这个问题,它会假设 Counter 指标的值只要是减少了就认为是被重置了,然后它可以调整后续的样本,例如,如果时间序列的值为 [5,10,4,6],则将其视为 [5,10,14,16]

聚合操作

例如我们想知道我们的 demo 服务每秒处理的请求数,那么可以将单个的速率相加就可以。

sum(rate(demo_api_request_duration_seconds_count{job="demo"}[5m]))

20230302153409

但是我们可以看到绘制出来的图形没有保留任何标签维度,一般来说可能我们希望保留一些维度,例如,我们可能更希望计算每个 instance 和 path 的变化率,但并不关心单个 method 或者 status 的结果,这个时候我们可以在 sum() 聚合器中添加一个 without() 的修饰符:

sum without(method, status) (rate(demo_api_request_duration_seconds_count{job="demo"}[5m]))

上面的查询语句相当于用 by() 修饰符来保留需要的标签的取反操作:

sum by(instance, path, job) (rate(demo_api_request_duration_seconds_count{job="demo"}[5m]))

现在得到的 sum 结果是就是按照 instance、path、job 来进行分组去聚合的了

阈值监控

PromQL 通过提供一组过滤的二元运算符(><== 等),允许根据其样本值过滤一组序列,这种过滤最常见的场景就是在报警规则中使用的阈值。比如我们想查找在过去 15 分钟内的 status="500" 错误率大于 20% 的所有 HTTP 路径,我们在 rate 表达式后面添加一个 > 0.2 的过滤运算符:

rate(demo_api_request_duration_seconds_count{status="500",job="demo"}[15m]) > 0.2

References