草庐IT

Prometheus的一些基础知识

zhixin9001 2023-03-28 原文

核心组件

Prometheus是一个开源的监控告警系统,它支持按多个维度存储监控数据,配套的PromQL可以对数据进行灵活的查询。
下图为其整体的架构:

主要包含四部分:

  • Prometheus Server,Prometheus Server用于从Exporters拉取数据,将采集到的监控数据按照时间序列的方式存储在本地磁盘当中(Prometheus Server本身也是一个时序数据库);并支持通过PromQL和通过API Client对数据进行查询; 它还负责通过服务发现或者静态配置的方式来识别监控目标。
  • Exporters,用于从监控目标采集数据,并先Prometheus Server提供收集数据的端口。是一个广义的概念,只要可以支持Server获取监控数据,就可以称为Exporter。具体分为两类:直接采集:此类Exporter直接内置了对Prometheus监控的支持,如cAdvisor, Kubernetes, Etcd等;间接采集:被监控目标不支持直接采集,需要集成专门的类库,比如Mysql Exporter, Consule Exporter, ASP.NET Core Exporter等。
  • PushGateway,Prometheus采用Pull模式采集数据,Server会定期调用Exporter提供的端口;但对于定期运行的Job类应用来说,并不是总能采集到数据,此外也可能受网络的限制,Server无法访问到Exporter,这些情况下,可以使用PushGateway进行数据的中转,由Exporter采用Push模式主动将数据发送到PushGateway,再由Server从PushGateway拉取数据。
  • AlertManager,Prometheus Server支持基于PromQL创建告警规则,如果规则满足,会产生一条告警,告警的后续处理流程由AlertManager来处理,它内置支持邮件、Slack等方式,也可以通过WebHook支持更多的自定义方式。

通过Docker容器启动

docker run -p 9090:9090 -v ${pwd}\prometheus.yml:/etc/prometheus/prometheus.yml prom/prometheus

启动完成后,可以通过http://localhost:9090/graph 访问Prometheus的UI界面,或者通过http://localhost:9090/metrics查看原始数据。

任务和实例

prometheus.yml的配置示例:

global:
  scrape_interval:     15s
scrape_configs:
  - job_name: 'prometheus'
    static_configs:
      - targets: ['localhost:9090']
  - job_name: 'node'
    static_configs:
      - targets: ['localhost:9100']

这段基本的prometheus.yml进行了对任务(job_name)、实例(targets)、抓取间隔(scrape_interval)的配置。
实例:暴露监控样本数据的HTTP服务,也就是Exporter的HTTP端口地址;
任务: 一组相同采集目的的实例,或者同一个采集进程的多个副本则通过任务来管理。

时间序列

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

  ^
  │   . . . . . . . . . . . . . . . . .   . .   node_cpu{cpu="cpu0",mode="idle"}
  │     . . . . . . . . . . . . . . . . . . .   node_cpu{cpu="cpu0",mode="system"}
  │     . . . . . . . . . .   . . . . . . . .   node_load1{}
  │     . . . . . . . . . . . . . . . .   . .  
  v
    <------------------ time ---------------->

Sample

矩阵的每一个点称为一个样本(sample),样本由以下三部分组成:

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

Metric

Metric的格式:

<metric name>{<label name>=<label value>, ...}

其中,一个metric可以包含多个标签(label),用来从多个维度反映当前样本的特征,通过这些维度,Prometheus可以对样本数据进行过滤、聚合等计算。

Metric的类型:

在Prometheus的存储实现上所有的监控样本都是以time-series的形式保存在内存TSDB(时序数据库)中的,而time-series又归属于不同的metric,所以从存储上来讲所有的metric都是相同的,但是在不同的场景下这些metric又有区别。具体分为:

  • Counter(计数器)
  • Gauge(仪表盘)
  • Histogram(直方图)
  • Summary(摘要)
Counter

Counter计数器的值只增不减(除非系统发生重置),这种metric用途非常广泛,比如可以在应用程序中记录某些事件发生的次数,然后通过使用PromQL内置的一系列函数对数据做进一步的分析,比如计算该事件产生速率随时间的变化。

通过rate()计算5m内的平均增长率:

rate(process_cpu_seconds_total[5m])

通过topk查询端口访问量前10:

topk(10,prometheus_http_requests_total)
Gauge

与Counter不同,Gauge类型的指标侧重于反应系统的当前状态。因此这类指标的样本数据可增可减。常见指标如node_exporter提供的node_memory_MemFree(主机当前空闲的内容大小)、node_memory_MemAvailable(可用内存大小)等。

直接查看系统的当前状态:

go_goroutines

通过delta()可以获取样本在一段时间返回内的增减情况:

delta(go_goroutines[2h])
Summary和Histogram

这两类指标主要用于统计和分析样本的分布情况。对于一些量化的指标,一般情况下都会计算其平均值,比如API平均响应时间,但这些统计方式会受长尾问题的影响,比如假设大多数API响应都在500ms之间,只有少部分响应时间需要5s,那么统计平均值后就无法识别这少部分响应特别慢的请求。
为了区分是平均的慢还是长尾的慢,可以按照请求响应的时间范围进行分组,Summary和Histogram都可以用于这类统计。但Histogram会按值所在的范围,统计各范围区间的数量;Summary则会按照中位数来统计。
比如Summary类型的go_gc_duration_seconds:

# HELP go_gc_duration_seconds A summary of the pause duration of garbage collection cycles.
# TYPE go_gc_duration_seconds summary
go_gc_duration_seconds{quantile="0"} 3.8753e-05
go_gc_duration_seconds{quantile="0.25"} 7.731e-05
go_gc_duration_seconds{quantile="0.5"} 0.000111513
go_gc_duration_seconds{quantile="0.75"} 0.000220177
go_gc_duration_seconds{quantile="1"} 0.00450966
go_gc_duration_seconds_sum 0.164080505
go_gc_duration_seconds_count 1003

从上面的样本可以得知go_gc的总次数为1003次,总耗时0.164080505s,中位数耗时0.000111513s。

PromQL

PromQL是Prometheus内置的数据查询语言,其提供对时间序列数据丰富的查询、聚合以及逻辑运算能力的支持,常用于数据查询、可视化、告警处理等场景。

查询时间序列

Prometheus通过指标名称(metrics name)以及对应的一组标签(labelset)唯一定义一条时间序列。
当直接使用监控指标名称查询时,可以查询该指标名称下的所有时间序列:
promhttp_metric_handler_requests_total

promhttp_metric_handler_requests_total{code="200", instance="host.docker.internal:9090", job="prometheus"} 893
promhttp_metric_handler_requests_total{code="500", instance="host.docker.internal:9090", job="prometheus"} 0
promhttp_metric_handler_requests_total{code="503", instance="host.docker.internal:9090", job="prometheus"} 0
标签匹配模式
  • 完全匹配,=和!=
promhttp_metric_handler_requests_total{code="200"}
promhttp_metric_handler_requests_total{code!="200"}
  • 正则匹配,=~和!~
promhttp_metric_handler_requests_total{code=~"200|500"}
promhttp_metric_handler_requests_total{code!~"200|500"}

范围查询

直接使用监控指标名称查询时,返回值中只包含该时间序列中最新的一个样本值,这样的返回结果也叫瞬时向量,对应的表达式称为瞬时向量表达式
如果想查询过去一段时间范围内的样本数据时,则需要使用区间向量表达式。通过[]来定义

promhttp_metric_handler_requests_total[5m]

支持的时间单位有s m h d w y

promhttp_metric_handler_requests_total{code="200", instance="host.docker.internal:9090", job="prometheus"}[15s]
1280 @1653186543.159
1281 @1653186548.16
1282 @1653186553.16

时间位移操作

瞬时向量表达式或者区间向量表达式都是以当前时间为基准,而通过时间位移操作,可以改变时间基准,如位移到2天前:

promhttp_metric_handler_requests_total[15s] offset 2d

聚合操作

通过PromQL查询时,如果指标和标签不能唯一确定一条时间序列,则会返回多条满足这些特征维度的结果。而通过聚合操作可以对这些时间序列进行处理,现成一条新的时间序列。

sum (求和)
min (最小值)
max (最大值)
avg (平均值)
stddev (标准差)
stdvar (标准方差)
count (计数)
count_values (对value进行计数)
bottomk (后n条时序)
topk (前n条时序)
quantile (分位数)

sum(promhttp_metric_handler_requests_total)
{} 1387
avg(promhttp_metric_handler_requests_total) by (code)

{code="200"} 1395
{code="500"} 0
{code="503"} 0

标量和字符串

除了向量类型,PromQL还支持使用标量(Scalar)和字符串(String), Scalar是浮点类型的数字值,直接使用字符串作为PromQL的表达式,则会直接返回字符串。

PromQL 操作符

数学运算

瞬时向量与标量直接可以进行加减乘除、取余、幂运算等数学运算,数学运算符会依次作用于瞬时向量的每个样本值,从而得到一组新的时间序列。

而如果是瞬时向量与瞬时向量之间进行数学运算时,过程会相对复杂一点。 例如,如果我们想根据node_disk_bytes_written和node_disk_bytes_read获取主机磁盘IO的总量,可以使用如下表达式:

node_disk_bytes_written + node_disk_bytes_read

PromQL会根据这个表达式依次找到与左边向量元素匹配(标签完全一致)的右边向量元素进行运算,如果没找到匹配元素,则直接丢弃。同时新的时间序列将不会包含指标名称。

布尔运算

布尔运算可以根据时间序列中样本的值,对其进行过滤,PrmoQL支持的布尔运算符有:

== (相等) 
!= (不相等) 
> (大于)
< (小于)
>= (大于等于)
<= (小于等于)

瞬时向量与标量进行布尔运算时,会依次对向量中所有时间序列样本的值进行比较,如果结果为true则保留,否则会丢弃。

使用bool修饰符改变布尔运算符的行为

布尔运算符的默认行为是对时序数据进行过滤。而在其它的情况下我们可能需要的是真正的布尔结果。
比如判断promhttp_metric_handler_requests_total的值是否大于1800,是则返回1,否则返回0,这时可以使用bool修饰符:

promhttp_metric_handler_requests_total > bool 1800

PromQL内置函数

通过内置函数可以对时序数据进行丰富的处理。

Increase: 计算Counter指标增长率

Counter类型的监控指标其特点是只增不减,在没有发生重置(如服务器重启,应用重启)的情况下其样本值应该是不断增大的。为了能够更直观的表示样本数据的变化剧烈情况,需要计算样本的增长速率。
increase(v range-vector),参数v是一个区间向量,increase函数获取区间向量中的第一个和最后一个样本并返回其增长量。因此promhttp_metric_handler_requests_total的增长率可以这样计算:

increase(promhttp_metric_handler_requests_total[1m]) / 60
rate/irate

除了上述方法,使用rate函数也可以直接计算增长率:

rate(promhttp_metric_handler_requests_total[1m])

需要注意的是使用rate或者increase函数去计算样本的平均增长速率,容易陷入“长尾问题”当中,其无法反应在时间窗口内样本数据的突发变化。 例如,对于主机而言在2分钟的时间窗口内,可能在某一个由于访问量或者其它问题导致CPU占用100%的情况,但是通过计算在时间窗口内的平均增长率却无法反应出该问题。
为了解决该问题,PromQL提供了另外一个灵敏度更高的函数irate(v range-vector)。irate同样用于计算区间向量的计算率,但是其反应出的是瞬时增长率。

irate(promhttp_metric_handler_requests_total[1m])

irate函数是通过区间向量中最后两个样本数据来计算区间向量的增长速率。这种方式可以避免在时间窗口范围内的“长尾问题”,并且体现出更好的灵敏度,通过irate函数绘制的图标能够更好的反应样本数据的瞬时变化状态。

参考资料
https://yunlzheng.gitbook.io/prometheus-book/

有关Prometheus的一些基础知识的更多相关文章

  1. ruby-on-rails - 如何生成传递一些自定义参数的 `link_to` URL? - 2

    我正在使用RubyonRails3.0.9,我想生成一个传递一些自定义参数的link_toURL。也就是说,有一个articles_path(www.my_web_site_name.com/articles)我想生成如下内容:link_to'Samplelinktitle',...#HereIshouldimplementthecode#=>'http://www.my_web_site_name.com/articles?param1=value1¶m2=value2&...我如何编写link_to语句“alàRubyonRailsWay”以实现该目的?如果我想通过传递一些

  2. postman接口测试工具-基础使用教程 - 2

    1.postman介绍Postman一款非常流行的API调试工具。其实,开发人员用的更多。因为测试人员做接口测试会有更多选择,例如Jmeter、soapUI等。不过,对于开发过程中去调试接口,Postman确实足够的简单方便,而且功能强大。2.下载安装官网地址:https://www.postman.com/下载完成后双击安装吧,安装过程极其简单,无需任何操作3.使用教程这里以百度为例,工具使用简单,填写URL地址即可发送请求,在下方查看响应结果和响应状态码常用方法都有支持请求方法:getpostputdeleteGet、Post、Put与Delete的作用get:请求方法一般是用于数据查询,

  3. 软件测试基础 - 2

    Ⅰ软件测试基础一、软件测试基础理论1、软件测试的必要性所有的产品或者服务上线都需要测试2、测试的发展过程3、什么是软件测试找bug,发现缺陷4、测试的定义使用人工或自动的手段来运行或者测试某个系统的过程。目的在于检测它是否满足规定的需求。弄清预期结果和实际结果的差别。5、测试的目的以最小的人力、物力和时间找出软件中潜在的错误和缺陷6、测试的原则28原则:20%的主要功能要重点测(eg:支付宝的支付功能,其他功能都是次要的)80%的错误存在于20%的代码中7、测试标准8、测试的基本要求功能测试性能测试安全性测试兼容性测试易用性测试外观界面测试可靠性测试二、质量模型衡量一个优秀软件的维度①功能性功

  4. ES基础入门 - 2

    ES一、简介1、ElasticStackES技术栈:ElasticSearch:存数据+搜索;QL;Kibana:Web可视化平台,分析。LogStash:日志收集,Log4j:产生日志;log.info(xxx)。。。。使用场景:metrics:指标监控…2、基本概念Index(索引)动词:保存(插入)名词:类似MySQL数据库,给数据Type(类型)已废弃,以前类似MySQL的表现在用索引对数据分类Document(文档)真正要保存的一个JSON数据{name:"tcx"}二、入门实战{"name":"DESKTOP-1TSVGKG","cluster_name":"elasticsear

  5. ruby - 找一些句子 - 2

    我想找到在某些文本中找到一些(让它是两个)句子的好方法。什么会更好-使用正则表达式或拆分方法?你的想法?应JeremyStein的要求-有一些例子示例:输入:ThefirstthingtodoistocreatetheCommentmodel.We’llcreatethisinthenormalway,butwithonesmalldifference.IfwewerejustcreatingcommentsforanArticlewe’dhaveanintegerfieldcalledarticle_idinthemodeltostoretheforeignkey,butinthis

  6. ruby block 并从 block 中返回一些东西 - 2

    我正在使用ruby​​1.8.7。p=lambda{return10;}deflab(block)puts'before'putsblock.callputs'after'endlabp以上代码输出为before10after我将相同的代码重构到这里deflab(&block)puts'before'putsblock.callputs'after'endlab{return10;}现在我收到LocalJumpError:意外返回。对我来说,这两个代码都在做同样的事情。是的,在第一种情况下我传递了一个过程,在第二种情况下我传递了一个block。但是&block将该block转换为pro

  7. ruby - 我怎样才能更好地了解/了解更多关于 Ruby 的知识? - 2

    按照目前的情况,这个问题不适合我们的问答形式。我们希望答案得到事实、引用或专业知识的支持,但这个问题可能会引发辩论、争论、投票或扩展讨论。如果您觉得这个问题可以改进并可能重新打开,visitthehelpcenter指导。关闭9年前。我最近开始学习Ruby,这是我的第一门编程语言。我对语法感到满意,并且我已经完成了许多只教授相同基础知识的教程。我已经写了一些小程序(包括我自己的数组排序方法,在有人告诉我谷歌“冒泡排序”之前我认为它非常聪明),但我觉得我需要尝试更大更难的东西来理解更多关于Ruby.关于如何执行此操作的任何想法?

  8. 【网络】-- 网络基础 - 2

    (本文是网络的宏观的概念铺垫)目录计算机网络背景网络发展认识"协议"网络协议初识协议分层OSI七层模型TCP/IP五层(或四层)模型报头以太网碰撞路由器IP地址和MAC地址IP地址与MAC地址总结IP地址MAC地址计算机网络背景网络发展        是最开始先有的计算机,计算机后来因为多项技术的水平升高,逐渐的计算机变的小型化、高效化。后来因为计算机其本身的计算能力比较的快速:独立模式:计算机之间相互独立。    如:有三个人,每个人做的不同的事物,但是是需要协作的完成。    而这三个人所做的事是需要进行协作的,然而刚开始因为每一台计算机之间都是互相独立的。所以前面的人处理完了就需要将数据

  9. ruby - 如果键存在,向散列值添加一些东西? - 2

    我在Ruby中有一个哈希:hash=Hash.new里面有一些键值对,比如说:hash[1]="One"hash[2]="Two"如果散列包含键2,那么我想将“Bananas”添加到它的值中。如果散列没有键2,我想创建一个新的键值对2=>"Bananas"。我知道我可以通过首先使用has_key?检查散列是否具有key2来做到这一点,然后采取相应的行动。但这需要一个if语句和不止一行。那么是否有一种简单、优雅的单行代码可以实现这一目标? 最佳答案 这个有效:hash[2]=(hash[2]||'')+'Bananas'如果您希望所有

  10. ruby - 使用 gmail gem 跟踪一些电子邮件 - 2

    我正在使用gmailgem发送电子邮件,我需要跟踪这些电子邮件。我该怎么做?我正在尝试搜索带有message_id的电子邮件,但它会从我的收件箱中提取所有电子邮件,而我只想要特定电子邮件的回复。这是我的实际代码:*使用message_id保存电子邮件*mail=gmail.deliver(email)Email.create(:message_id=>mail.message_id,:from=>user.email,:to=>annotation.to,:body=>annotation.content,:title=>annotation.title,:annotation=>an

随机推荐