PromQL教程(一)初识 PromQL

巴辉特 2024-09-29 14:08:18

在 2024 年的当下,Prometheus 生态基本已成为监控领域事实上的标准,学习 Prometheus 是每个运维人员的必修课,也是每个关注服务稳定性的研发人员的必修课。PromQL 是 Prometheus 的查询语言,全称是 Prometheus Query Language,想要学习 Prometheus,PromQL 是必学知识。本文是 PromQL 系列教程的第一讲,先介绍一些基础概念。

什么是指标、数据点

在介绍 PromQL 之前,我们先来澄清一些基础概念。

在监控领域,我们经常会听到指标(Metric)这个词,指标是对系统或服务的某个特定方面进行量化的测量值。比如,服务器的 CPU 使用率、内存使用率、数据库的连接数、某个 API 的 99 分位延迟等都是常见的指标。指标可以反应系统的运行状态,帮助我们了解系统的性能、稳定性、可用性等。

指标数据一般是周期性采集,每次采集到的数据称为一个数据点,举例如下:

{
    "metric": "cpu_usage_idle",
    "labels": {
        "host": "10.1.2.3",
        "region": "us-west"
    },
    "value": 82.3,
    "timestamp": 1632892800
}

上例是用 JSON 格式表示的一个数据点,包含了以下几个字段:

  • metric:指标名称,比如 cpu_usage_idle 表示 CPU 空闲率
  • labels:标签,用于标识数据点的维度,比如 host 表示主机名,region 表示地区
  • value:数据点的值,比如 82.3 表示 CPU 空闲率为 82.3%
  • timestamp:数据点的时间戳,比如 1632892800 表示 Unix 时间戳

metriclabelsvaluetimestamp 四个字段组成了数据点的唯一标识,metriclabels 两个字段组成了指标的唯一标识。

就上例而言,假设 10.1.2.3 这个机器每 15 秒采集一个数据点,一分钟就会有 4 个数据点,一小时就会有 240 个数据点,一天就会有 5760 个数据点。如果我们有 10000 台机器,一天就会有 57600000 个数据点。而这,仅仅是机器的一个指标,实际上,我们还会采集很多其他指标,另外除了机器,还要采集数据库、中间件、应用程序等的指标,数据量是非常庞大的。很多大公司每秒甚至会采集数千万、上亿的数据点。

这么大的数据量,存在哪里?

时序数据库 TSDB(Time Series Database)

这类数据有个典型特点,每个数据点都带有一个时间戳属性,查询的时候一定会指定时间范围,比如是查询最近 15 分钟,还是查询最近 1 小时,这类数据就是典型的时序数据。

时序数据量大、查询时要根据标签灵活过滤、做聚合计算,传统的关系型数据库无法胜任,因此,时序数据库应运而生。第一个比较知名的开源时序库是 InfluxDB,Prometheus 也内置了一个时序数据库,以本地文件的特定格式存储这些数据。

关系型数据库的查询语言是 SQL,Prometheus 也为时序数据查询设计了一种全新的语言 PromQL。PromQL 非常流行,很多时序库都会兼容 PromQL,比如 VictoriaMetrics、GrepTimeDB 等。

使用 PromQL 查询时序数据的样例

既然 PromQL 是用来查询时序数据的,那我们就来做一个 Hello world 级别的查询测试,用 PromQL 查询一次数据给你看看,先有个直观的认识。(下面我是使用夜莺监控的即时查询来做演示,你也可以直接使用 Prometheus 自带的 UI 来做查询测试)

使用PromQL查询CPU使用率

上例我是查询的 cpu_usage_active 这个指标,查到了 7 条数据。这个例子非常简单,想查什么指标,就输入对应的指标名就可以查询。因为我没有加过滤条件,就把时序库中存储的所有该指标的数据都查出来了。下面我加点过滤条件,只查询特定的一些数据。

使用PromQL查询CPU使用率-增加过滤条件

上例中,我在指标名字后面加了一对大括号,里面写了两个过滤条件:env="plus", region="center", 这样就只查询了 envplusregioncenter 的数据。

打开 Chrome 开发者工具,可以看到这个查询是请求的 /api/v1/query 接口,请求参数是 time 和 query:

time=1727592927&query=cpu_usage_active%7Benv%3D%22plus%22%2C%20region%3D%22center%22%7D

当然,上面的内容是 url 编码后的,解码后就是:

time=1727592927&query=cpu_usage_active{env="plus", region="center"}

返回的内容如下:

{
    "status": "success",
    "data": {
        "resultType": "vector",
        "result": [
            {
                "metric": {
                    "__name__": "cpu_usage_active",
                    "cpu": "cpu-total",
                    "env": "plus",
                    "ident": "plus01",
                    "region": "center"
                },
                "value": [
                    1727592927,
                    "10.795742524837411"
                ]
            },
            {
                "metric": {
                    "__name__": "cpu_usage_active",
                    "cpu": "cpu-total",
                    "env": "plus",
                    "ident": "plus02",
                    "region": "center"
                },
                "value": [
                    1727592927,
                    "10.944097282143026"
                ]
            },
            {
                "metric": {
                    "__name__": "cpu_usage_active",
                    "cpu": "cpu-total",
                    "env": "plus",
                    "ident": "plus03",
                    "region": "center"
                },
                "value": [
                    1727592927,
                    "10.882402563360198"
                ]
            }
        ]
    }
}

返回的内容,包含如下字段:

  • resultTypevector,后面我们做其他查询的时候,注意对比 resultType
  • result 是个数组,包含多个 vector,每个 vector 包含两部分:metricvalue
  • metric 是个对象,包含了指标的标签信息,指标名也放到标签里的,用了一个很特殊的标签名 __name__
  • value 是个数组,第一个元素是时间戳,第二个元素是值

注意,上例中 value 的时间戳是 1727592927,和查询参数里的时间戳一样,我们直观理解的话,会认为 TSDB 中的时序数据恰好有值是在 1727592927 时刻采集的,所以查询时就返回了。但是,我的指标数据是 15s 采集一次,哪有这么巧,正好在我发起查询的那一刻采集到数据?!

如果你连续点击几次查询按钮并分析发起的多次查询,会发现:值不变,时间戳一直在变,返回的时间戳和查询参数里的时间戳一直保持一致。

这是因为 Prometheus 的查询引擎在查询时,会根据查询参数里的时间戳,去找最近的数据点,而不是严格按照时间戳去查找。那也不能无限制一直往前找,最多可以往前找多久,是由 Prometheus 的启动参数:--query.lookback-delta 决定的,默认是 5m,也就是说,你发起查询时,最多只会往前找 5 分钟的数据。(这个知识点至关重要)

所以,上例中这类查询,返回的时间戳并非是 TSDB 里存储的真实时间戳。那如果我就想知道真实的时间戳,应该怎么办?

Prometheus range vector vs instant vector

上面的 PromQL 稍微变换一下,在后面加一个时间范围,比如 [1m] 表示一分钟,点击查询看到如下结果:

matrix

大括号里的过滤条件称为 selector,metric_name 其实也是 selector 的一部分,cpu_usage_active{env="plus", region="center"} 就相当于 {__name__="cpu_usage_active", env="plus", region="center"},metric_name 写到大括号前面姑且可以看做是一种语法糖。只有 selector 的查询,其查询结果称为 instant vector,加上时间范围(上例中是 [1m]),其查询结果称为 range vector。后面我会贴出 response,你可以看到返回的数据结构。

因为数据是每 15s 采集一次,对于每个指标,一分钟就会有 4 个点,表格里第一列展示的指标标签,后面的部分就是那 4 个点的信息,夜莺监控为了易用性考虑,会展示值以及对应的时间戳,并且把时间戳转换成了人类可读的时间格式,并且会用下面的时间戳减去上面的时间戳,展示出时间间隔。(这对于排查问题非常有用)

查看 Chrome 开发者工具,可以看到这次查询的接口仍然是 /api/v1/query,请求参数是 time、query,其中 time 是 1727593833,返回的 response 中的时间戳已经不是固定的 1727593833 了,此时返回的那多个 时间戳+数值,才是时序库里真实存储的数据。

下面贴出 response,给你参考:

{
    "status": "success",
    "data": {
        "resultType": "matrix",
        "result": [
            {
                "metric": {
                    "__name__": "cpu_usage_active",
                    "cpu": "cpu-total",
                    "env": "plus",
                    "ident": "plus01",
                    "region": "center"
                },
                "values": [
                    [
                        1727593784.977,
                        "11.755802217046362"
                    ],
                    [
                        1727593799.977,
                        "11.05898123024424"
                    ],
                    [
                        1727593814.977,
                        "12.462210281214825"
                    ],
                    [
                        1727593829.978,
                        "11.226541553767426"
                    ]
                ]
            },
            {
                "metric": {
                    "__name__": "cpu_usage_active",
                    "cpu": "cpu-total",
                    "env": "plus",
                    "ident": "plus02",
                    "region": "center"
                },
                "values": [
                    [
                        1727593785.199,
                        "11.74987392595977"
                    ],
                    [
                        1727593800.200,
                        "11.127869952064797"
                    ],
                    [
                        1727593815.201,
                        "12.443324937122252"
                    ],
                    [
                        1727593830.200,
                        "11.282481141014513"
                    ]
                ]
            },
            {
                "metric": {
                    "__name__": "cpu_usage_active",
                    "cpu": "cpu-total",
                    "env": "plus",
                    "ident": "plus03",
                    "region": "center"
                },
                "values": [
                    [
                        1727593776.433,
                        "11.152291106290598"
                    ],
                    [
                        1727593791.433,
                        "11.42281202857196"
                    ],
                    [
                        1727593806.434,
                        "12.038900067201045"
                    ],
                    [
                        1727593821.435,
                        "11.453966121635645"
                    ]
                ]
            }
        ]
    }
}

这次 resultType 变成了 matrix,值的部分变成了 values,values 是个数组,每个元素是一个 value,value 的第一个元素仍然是时间戳,第二个元素仍然是值。

上例的两次查询都是调用的 /api/v1/query 接口,这个接口的时间参数仅仅是一个 time 时间戳,但是对于时序数据,还有另一个非常重要的查询场景,就是查询一段时间范围内的数据,这个时候就需要调用 /api/v1/query_range 接口了。

Prometheus query_range

还是刚才的例子,我现在切到 Graph 视图,就会调用 /api/v1/query_range 接口。

Prometheus query_range

Graph 视图,不再展示为 Table 了,而是折线图。查到几个指标,就画几条线。

查看 Chrome 开发者工具,可以看到请求变成了 /api/v1/query_range,请求参数是 start、end、step、query,其中 start 是查询的开始时间,end 是查询的结束时间,step 是查询的时间间隔,query 是查询的 PromQL 语句。

这里最不好理解的是 step 参数,这个参数是用来控制返回的数据点的时间间隔的,比如 step=15,就是每 15s 返回一个数据点,step=60,就是每分钟返回一个数据点。这个参数的值,会影响到返回的数据点的数量,也会影响到返回的数据点的时间戳。

如果你查询的时间范围很大,step 很小,就会返回非常多的数据,导致浏览器崩溃。用户默认没有指定 step 的值,夜莺会自动计算一个合适的值,保证返回的数据点数量不会太多。

如果想手工指定 step 的值,也是可以的,就是 “最近一小时” 后面那个输入框,placeholder 是 Res.(s) 的那个输入框,在里边手工填写指定 step 的值即可。

我这里把时间范围设置的小一点,设置为最近一分钟,step 设置为 1,点击查询,看到如下结果:

Prometheus query_range with small step

鼠标放到图上,来回移动,可以看到数据点的间隔变成了 1s。但是,我的原始数据是 15s 才采集一次,多了很多数据点,说明 Prometheus 存在自动补点渲染的逻辑。它会根据你传入的 start、end 确定时间范围,把这个时间范围内的数据点都查出来,然后根据 step 参数确定各个数据点的间隔,把这些数据点挨个补全,最终返回。

小结

本文是 PromQL 系列教程的第一篇,讲解一些基础知识,这些基础知识至关重要,是后续学习 PromQL 的基础。笔者尽量用通俗易懂的语言讲解,希望能帮助到你。

快猫星云 联系方式 快猫星云 联系方式
快猫星云 联系方式
快猫星云 联系方式
快猫星云 联系方式
快猫星云
OpenSource
开源版
Flashcat
Flashcat
FlashDuty
Flashduty