原文链接:https://www.hezebin.com/article/66b3b1fb4379b36dec11a1a1

前言

在现代分布式系统和云原生环境中,为了确保复杂的分布式系统和服务的高可用性、可靠性和性能,通常采用实时可视化监控和分析,实现故障快速响应、资源优化和安全保障,从而提升用户满意度和运营效率。

在目前主流的解决方案中,PrometheusGrafana 是两个使用 Go 开发的强大开源工具,通常一起使用来实现全面的监控和可视化系统。其中Prometheus负责采集和存储监控对象的时序数据。

架构

Prometheus是一个开源系统监控和警报工具包,最初由SoundCloud构建。自2012年成立以来,许多公司和组织都采用了Prometheus,该项目拥有非常活跃的开发人员和用户社区。https://prometheus.io/docs/introduction/overview 。

Prometheus的主要特点是:

  • 采用一种多维数据模型,包含由metric name(指标名称)和 k/v (键值对)标识的时间序列数据

    比如我们在对河水水质的抽样检测中,【大肠杆菌检测】和【有害化学物质检测】样本就可以类比成是两个metric指标名称;为了了解【大肠杆菌检测】的水质结果,我们还需要一些具体的指标数据来描述这个样本,如:检测时间(time=‘2024-01-02 03:04:05’)、检测位置(position=‘河流上游’)、大肠杆菌含量(proportion=‘0.02’)这几组数据可以类比成是几组 k/v 键值对。

  • 内置了一个灵活、强大的数据查询语言 PromQL

    查询关系型数据库数据用 SQL 语言,那查询指标时序数据用的就是专属的 PromQL 语言,其具备和 SQL 一样的范围查询、分类和聚合等功能。

  • 不依赖分布式存储,单个服务器节点是自治的

    Prometheus 核心部分只有一个单独的二进制文件,不存在任何的第三方依赖(数据库,缓存等等)。唯一需要的就是本地磁盘,因此不会有潜在级联故障的风险。但 Prometheus 也有远程读写功能,能将数据同时存储到其他中间件。

  • 通过HTTP上的拉模型进行时序数据收集

    Prometheus 基于 Pull 模型的架构方式定时轮询的拉取收集时序数据,这意味着可以从任何位置、任何类型实现了该数据协议的系统或程序中通过 HTTP 采集到指标数据。

  • 通过中间网关支持推送时序数据

    对于短作业类型程序,程序可以在退出时主动 Push 推送指标数据到 Prometheus 实现监控

  • 通过服务发现或静态配置发现目标
  • 多种图形和仪表板模式支持

Prometheus 生态系统由多个组件组成,其中许多组件是可选的:

  • prometheus server:Prometheus 主服务器用于抓取和存储时间序列数据
  • client libraries:客户端库是某种语言(例如Go,Java,Python,Ruby)的库,可以通过编写自定义收集器以从其他系统中轻松提取指标并将指标公开给Prometheus
  • push gateway:一个能够支持短时任务的推送网关
  • exporters:为各类服务提供的特殊数据导出器,比如 HAProxy、StatsD、Graphite 等
  • alertmanager:专门用来处理报警
  • 以及支持其他各式工具

下图展示了Prometheus的架构及其生态系统的一些组件:

对于监控系统而言,大量的监控任务必然导致有大量的数据产生。而 Prometheus 可以高效地处理这些数据,对于单一 Prometheus Server 实例而言它就可以处理数以百万的监控指标和每秒处理数十万的数据点。

Prometheus 自身提供了一定的可视化能力,且可以和很多流行的可视化工具进行整合如:Grafana。

Prometheus Server 安装

下载适用于您的平台的 Prometheus的最新版本:https://prometheus.io/download ,然后将其解压缩:

tar xvfz prometheus-*.tar.gz
cd prometheus-*

Prometheus服务器是一个名为 prometheus(或Microsoft Windows上的 prometheus.exe)的二进制文件。且下载的目录中附带了一个名为 prometheus.yml 的文件中的示例配置:

global:
  scrape_interval: 15s # Set the scrape interval to every 15 seconds. Default is every 1 minute.
  evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute.
  # scrape_timeout is set to the global default (10s).

# Alertmanager configuration
alerting:
  alertmanagers:
    - static_configs:
        - targets:
          # - alertmanager:9093

# Load rules once and periodically evaluate them according to the global 'evaluation_interval'.
rule_files:
  # - "first_rules.yml"
  # - "second_rules.yml"

# A scrape configuration containing exactly one endpoint to scrape:
# Here it's Prometheus itself.
scrape_configs:
  # The job name is added as a label `job=<job_name>` to any timeseries scraped from this config.
  - job_name: "prometheus"

    # metrics_path defaults to '/metrics'
    # scheme defaults to 'http'.

    static_configs:
      - targets: ["localhost:9090"]

示例配置文件中有三个配置块:globalrule_filesscrape_configs。global块控制Prometheus服务器的全局配置;rule_files 块指定了 Prometheus 的一些特殊规则配置文件路径;最后一个块 scrape_configs 控制 Prometheus 监视的资源。

有关配置选项的完整规范,请参阅配置文档:https://prometheus.io/docs/operating/configuration

Prometheus 服务器是允许使用--web.enable-lifecycle 参数在运行时动态加载配置文件变更的,最后使用配置文件来启动Prometheus服务器:

# By default, Prometheus stores its database in ./data (flag --storage.tsdb.path).
./prometheus --config.file=prometheus.yml --web.enable-lifecycle

Prometheus实例可以使用SIGHUP信号重新加载其配置,而无需重新启动进程。如果你运行在Linux上,这可以通过使用kill -s SIGHUP <PID>来执行,用你的Prometheus进程ID替换<PID>

由于 Prometheus 还将自身的数据作为HTTP端点公开,因此它可以抓取和监控自己的健康状况。在默认配置中,有一个名为prometheusjob,用于抓取Prometheus服务器公开的时间序列数据。该job包含一个静态配置的目标,即localhost上的9090端口。Prometheus通过目标上的/metrics路径拉取数据。因此,此默认作业是通过URL进行抓取:http://localhost:9090/metrics


在服务器启动后,你可以通过http://localhost:9090访问 Prometheus 自带的 Web UI 管理后台:

在该页面下,可以看到已经正常监控到配置文件中的 Prometheus 服务自身的各项运行指标数据。通过http://localhost:9090/metrics 查看采集到的 Prometheus服务器原始指标数据:

概念和术语

上述虽然已经成功启动了 Prometheus 服务器实现对数据的采集,但 Prometheus 本质是一个数模模型和运算的工具,要想顺利理解 Prometheus,进一步看懂和使用上图的Prometheus管理工具系统或能根据自己的需求场景查询、读懂监控数据,还需要先了解 Prometheus 的一些基本术语和概念。

术语表

术语解释
Exporter导出器是一个二进制文件,与您想要从中获取指标的应用程序一起运行。导出器公开Prometheus指标,通常通过将以非Prometheus格式公开的指标转换为Prometheus支持的格式。
Instance实例是唯一标识作业中的目标的标签。 即配置文件中 Job 下的 Targets 数组元素项
Target目标是要抓取的对象的定义。例如,要应用的标签、连接所需的任何身份验证或定义刮取将如何发生的其他信息。
Job具有相同目的的目标的集合,例如监视一组复制的相似进程的可伸缩性或可靠性,称为作业。
Alert警报是Prometheus中处于激活中警报规则的结果。警报从Prometheus发送到警报管理器。
AlertmanagerAlertmanager接收警报,将其聚合到组中,消除重复,应用静默,节流,然后向电子邮件,Pagerduty,Slack等发送通知。
Notification通知表示一组一个或多个警报,并由Alertmanager发送到电子邮件,Pagerduty,Slack等。
PromQLPromQL是Prometheus查询语言。它允许广泛的操作,包括聚合,切片和切割,预测和连接。
PushgatewayPushgateway保持来自批处理作业的最新推送指标。这允许Prometheus在他们终止后刮取他们的指标。
Recording Rules记录规则预先计算经常需要的或计算量大的表达式,并将其结果保存为一组新的时间序列。
Remote Read远程读取是Prometheus的一项功能,允许从其他系统(如长期存储)透明阅读时间序列作为查询的一部分。
Remote Read Adapter并非所有系统都直接支持远程读取。远程读取适配器位于Prometheus和另一个系统之间,在它们之间转换时间序列请求和响应。
Remote Write远程写入是Prometheus的一项功能,允许将摄取的样本即时发送到其他系统,例如长期存储。(本地磁盘也存储)
Remote Write AdapterRemote Write Adapter并非所有系统都直接支持远程写入。远程写适配器位于Prometheus和另一个系统之间,将远程写中的样本转换为另一个系统可以理解的格式。
Sample样本是时间序列中某个时间点的单个值。在Prometheus中,每个样本都由一个float64值和一个毫秒精度的时间戳组成。
SilenceAlertmanager中的静默会阻止通知中包含标签与静默匹配的警报。
Time SeriesPrometheus时间序列是属于相同度量和相同标记维度集合的时间戳值的流。Prometheus将所有数据存储为时间序列。

数据模型

Prometheus从根本上将所有数据存储为时间序列:属于相同度量和相同标记维度集的时间戳值流。除了存储的时间序列外,Prometheus还可以生成临时的派生时间序列作为查询的结果。

派生时间序列即可以利用数学公式或Prometheus内置函数生成相关的其他结果数据

Metric names and labels

每个时间序列都由其指标名称和称为标签的可选键值对唯一标识,即:metric nameoptional key-value

Metric names

  • 指定被测量的系统的一般特性(例如http_requests_total - 接收的HTTP请求总数)。
  • 指标名称可以包含ASCII字母、数字、下划线和冒号。它必须与regex [a-zA-Z_:][a-zA-Z0-9_:]*匹配。

Metric labels

  • 使Prometheus的维度数据模型能够识别相同指标名称的任何给定标签组合。它标识该度量的特定维度实例化(例如:使用方法POST到/api/tracks处理程序的所有HTTP请求)。查询语言允许基于这些维度进行筛选和聚合。
  • 任何标签值的更改,包括添加或删除标签,都会创建一个新的时间序列。
  • 标签可以包含ASCII字母、数字以及下划线。它们必须匹配正则表达式[a-zA-Z_][a-zA-Z0-9_]*
  • __(两个“_”)开头的标签名称保留供内部使用。
  • 标签值可以包含任何Unicode字符。
  • 标签值为空的标签被视为等同于不存在的标签。

Samples

样本形成实际的时间序列数据。每个样品包括:

  • float64值
  • 毫秒精度的时间戳

Notation

给定一个指标名称和一组标签,时间序列通常使用以下表示法来识别:

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

例如,一个指标名为api_http_requests_total、标签为method="POST"handler="/messages"的时间序列可以这样写:

api_http_requests_total{method="POST", handler="/messages"}

这与OpenTSDB使用的符号相同。

指标类型

Prometheus客户端库(client libraries)提供四种核心指标类型。目前,它们仅在客户端库(以启用针对特定类型的使用量身定制的API)和有线协议中有所区别。Prometheus服务器还没有使用类型信息,而是将所有数据转换为非类型化的时间序列(但将来可能支持)。

Counter

是一个单调递增的计数器,用于记录某个事件发生的次数或某个值的累计量。它只能增加,不能减少(除了在重启或重置时重新开始)。

适用于需要记录累计次数或量的场景,如请求数量、任务完成数量、错误次数等。可以用于监控增长趋势和总量统计。不要使用Counter来公开可以减少的值。例如,不要使用Counter来表示当前正在运行的进程的数量,而应使用Gauge。

Counter 的 Go 客户端库使用文档:http://godoc.org/github.com/prometheus/client_golang/prometheus#Counter

示例:

httpRequestsTotal := prometheus.NewCounter(prometheus.CounterOpts{
    Name: "http_requests_total",
    Help: "Total number of HTTP requests.",
})
prometheus.MustRegister(httpRequestsTotal)

// 在处理 HTTP 请求时增加计数
httpRequestsTotal.Inc()

假设我们有以下 HTTP 请求计数:

http_requests_total 1234

这表示从应用程序启动以来,HTTP 请求的总数是 1234 次。

Gauge

Gauge 是一个可以上下浮动的计量器,用于记录当前值。它可以增加或减少,用于记录瞬时值,如温度、内存使用量、并发请求数等。

适用于需要记录当前状态或瞬时值的场景,如当前内存使用量、当前连接数等。可以用于监控系统状态的变化和趋势。

Go 客户端文档:http://godoc.org/github.com/prometheus/client_golang/prometheus#Gauge

示例:

currentMemoryUsage := prometheus.NewGauge(prometheus.GaugeOpts{
    Name: "current_memory_usage_bytes",
    Help: "Current memory usage in bytes.",
})
prometheus.MustRegister(currentMemoryUsage)

// 在内存使用变化时更新值
currentMemoryUsage.Set(float64(runtime.MemStats.Alloc))

假设我们有以下内存使用数据:

current_memory_usage_bytes 524288000

这表示当前内存使用量是 524,288,000 字节(约 500 MB)。

Histogram

Histogram 用于对样本值进行分桶,并统计每个桶内的样本数量。它还会维护一个累加器,记录所有观察值的总和和样本数量。每个桶的定义是基于一个边界值的数组(buckets),这些边界值将样本值划分为多个区间。

适用于需要了解某个值的分布情况的场景,例如请求延迟、响应时间等。可以方便地计算某个阈值以下的样本数量,以及各个阈值区间内的样本分布。

比如描述接口响应时间,如果我们只看响应时间变化的曲线图,那用 Gauge 指标类型即可,但这样的指标实际意义并不大,顶多只能看出响应时间的稳定性,在求平均响应时间时,对于一些出现次数较少的慢请求(如特殊大客户海量数据或者有 bug)如 1s 以上的,甚至会被中位数 50ms 给抹平,没法直观的洞察到这类情况。

而 Histogram 就是用来描述样本分布情况的,它可以用来呈现的数据形式为如 0~50ms 的响应时间次数为 2w 次,50ms~200ms 的次数为 1k 次 … 1s以上的为 200 次(实际可能需要做一些逻辑运算),那么这样的三条曲线我们除了能很清晰的知道常规响应时间外,还能知道异常慢查询集中出现的时间和出现的次数占比等

基本指标名称为<basename>的 Histogram 会在一次抓取中暴露多个时间序列:

  • 观察桶(observation buckets)的累积计数器,显示为<basename>_bucket{le="<包含作用域的上限>"}
  • 所有观察值的总和,显示为 <basename>_sum
  • 观察到的事件计数,显示为 <basename>_count (与上面的 <basename>_bucket{le="+Inf"} 相同)

使用 histogram_quantile() 函数可以根据直方图甚至是直方图的聚合来计算分位数,histogram 也适用于计算 Apdex score。当在 buckets 上操作时,记住 histogram 也是累积的。有关 histograms 和 summaries 的详细信息,可以参考 histograms and summaries

直方图的Go客户端库使用文档:http://godoc.org/github.com/prometheus/client_golang/prometheus#Histogram

示例:

httpDuration := prometheus.NewHistogram(prometheus.HistogramOpts{
    Name:    "http_request_duration_seconds",
    Help:    "A histogram of the HTTP request durations.",
    Buckets: prometheus.DefBuckets,
})
prometheus.MustRegister(httpDuration)

// 在处理 HTTP 请求时记录延迟
httpDuration.Observe(time.Since(start).Seconds())

假设我们有以下响应时间的观测数据(以秒为单位):0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0。通过 Prometheus 查询结果:

http_request_duration_seconds_bucket{le="0.05"} 0
http_request_duration_seconds_bucket{le="0.1"} 1
http_request_duration_seconds_bucket{le="0.2"} 2
http_request_duration_seconds_bucket{le="0.5"} 5
http_request_duration_seconds_bucket{le="1"} 10
http_request_duration_seconds_bucket{le="+Inf"} 10
http_request_duration_seconds_sum 5.5
http_request_duration_seconds_count 10

该结果表示 0~0.05 的数据量为 0;0~0.1 的数量为 1;0~0.2 的为 2 … 全都在 1.0 以下,所以为 10。

Summary

类似于 Histogram,Summary 记录的是数据的直接观测值,并可以计算特定百分位数(quantile)的值。
会计算总和和样本数量,同时允许指定不同的百分位数来进行计算。

用于需要精确计算某些百分位数值的场景,如请求延迟的 95% 和 99% 分位数。比 Histogram 提供了更精确的百分位数计算,但代价是内存使用更多,特别是在高流量场景中。

基本指标名称为 <basename> 的 Summary 会在一次抓取指标期间显示多个时间序列:

  • 观察到的事件分位数 quantile -φ (0≤φ≤1),显示为 <basename> {quantile="<φ>"}
  • 所有观察值的总和,显示为 <basename> _sum
  • 已观察到的事件计数,显示为 <basename> _count

有关 φ 分位数的详细说明,摘要用法以及与直方图的差异,请参见 histograms and summaries

Summaries 客户端库使用文档: http://godoc.org/github.com/prometheus/client_golang/prometheus#Summary

示例:

httpDuration := prometheus.NewSummary(prometheus.SummaryOpts{
    Name:       "http_request_duration_seconds",
    Help:       "A summary of the HTTP request durations.",
    Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001},
})
prometheus.MustRegister(httpDuration)

// 在处理 HTTP 请求时记录延迟
httpDuration.Observe(time.Since(start).Seconds())

假设我们有以下响应时间的观测数据(以秒为单位):0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0。通过 Prometheus 查询结果:

http_request_duration_seconds{quantile="0.5"} 0.55
http_request_duration_seconds{quantile="0.9"} 0.95
http_request_duration_seconds{quantile="0.99"} 1
http_request_duration_seconds_sum 5.5
http_request_duration_seconds_count 10

指标结果表示 HTTP 请求持续时间的 50% 分位数(即中位数)是 0.55 秒。换句话说,有 50% 的请求持续时间小于或等于 0.55 秒;90% 分位数是 0.95 秒,有 90% 的请求持续时间小于或等于 0.95 秒; 99% 分位数是 1 秒,有 99% 的请求持续时间小于或等于 1 秒。

Jobs and Instances

在 Prometheus 术语中,一个您可以抓取的 endpoint 被称为一个 instance,通常对应到一个单独的进程。一组同样目的的 instance 的集合,进程因扩展性或可靠性而被复制的叫做一个 Job。

例如,具有四个实例的 API server job:

  • job: api-server
    • instance 1: 1.2.3.4:5670
    • instance 2: 1.2.3.4:5671
    • instance 3: 1.2.3.3:5677
    • instance 3: 1.2.3.3:5678

自动生成的标签和时间序列

当Prometheus抓取一个目标时,它会自动将一些标签附加到抓取的时间序列上,这些标签用于识别抓取的目标:

  • job:目标所属的配置作业名称。
  • instance:目标URL中被抓取的<host>:<port>部分。

如果这些标签中的任何一个已经存在于抓取的数据中,则行为取决于honor_labels配置选项。有关详细信息,请参阅 scrape配置文档


对于每一个实例抓取,Prometheus 会自动存储以下时序样本(即为内置 labels):

  • up{job="<job-name>", instance="<instance-id>"}。如果实例健康,则值为 1,也就是可访问,如果抓取失败则值为 0
  • scrape_duration_seconds{job="<job-name>", instance="<instance-id>"}:抓取持续时间。
  • scrape_samples_post_metric_relabeling{job="<job-name>", instance="<instance-id>"}: metric relabel 生效后剩余的样本数。
  • scrape_samples_scraped{job="<job-name>", instance="<instance-id>"}: target 暴露的样本数量。
  • scrape_series_added{job="<job-name>", instance="<instance-id>"}: 在一次抓取中新时序的大约数量。v2.10 新增。

up 时间序列对于实例可用性监控非常有用。

PromQL

Prometheus 提供了一组称为 PromQL(Prometheus Query Language) 的函数式查询语言,它能够让用户实时地查询和聚合时间序列数据。表达式的结果既能以一个图表展示,也能在 Prometheus 的 expression browser 中以列表的形式查看,它也能通过 HTTP API 被外部系统消费。

PromQL 数据类型

在 Prometheus 的表达式语言中,一个表达式或子表达式可以计算为以下四种类型:

  • 瞬时向量(Instant vector)- 一组时间序列,每个时间序列包含一个样本,所有样本共享相同的时间戳;即每个时序只有一个点
  • 区间向量(Range vector)- 一组时间序列,其中包含每个时间序列随时间的一系列数据点;即每个时序有多个点
  • 标量(Scalar)- 一个简单的数字浮点值,没有时序
  • 字符串(String)- 一个简单的字符串值, 目前未使用

示例,瞬时向量:查询 label:promhttp_metric_handler_requests_total

区间向量:同样查询该 label,加上时间范围限制,最近 1 分钟的数据(默认配置是 15s 收集拉取一次指标数据,1m 就是 4 次)

标量:上问题到 Prometheus 本身就是一个数学模型,所以可以直接写数学计算表达式

PromQL 语法

瞬时向量选择器

PromQL 选择器通过在 label 标签名称后加花括号 {} 和指定 k/v 的方式来筛选过滤字段,如以 promhttp_metric_handler_requests_total 为例只筛选 code200 的请求属指标,则语法为:

promhttp_metric_handler_requests_total{code="200"}

查询结果只返回 200 状态码的:

若花括号中什么k\v 都不指定则表示查询这个 label 的所有时序数据,且 promhttp_metric_handler_requests_total{} 等同于 promhttp_metric_handler_requests_total

另外,向量选择器也并非必须指定 label 标签名称,但是向量选择器必须指定一个名称或至少一个与空字符串不匹配的标签匹配器。以下表达式是非法的:

{job=~".*"} # Bad!

相反,这些表达式是有效的,因为它们都有一个不匹配空标签值的选择器。

{job=~".+"}              # Good!
{job=~".*",method="get"} # Good!

示例:如{job="prometheus"}会返回所有带有job字段且值为prometheus的 label 时间序列:

选择器还可以通使用内置的__name__ 键来匹配指标名称。例如,表达式 http_requests_total 等价于{__name__="http_requests_total"}。以下表达式选择名称以job:开头的所有指标:

{__name__=~"job:.*"}

指标名称不能是关键字boolonignoringgroup_leftgroup_right 之一。以下表达式是非法的:

on{} # Bad!

解决此限制的方法是使用__name__标签:

{__name__="on"} # Good!

匹配运算符

PromQL 存在以下标签匹配运算符:

  • =:选择与提供的字符串完全相等的标签。
  • !=:选择与提供的字符串不相等的标签。
  • =~:选择与提供的字符串regex匹配的标签。
  • !~:选择与提供的字符串不正则匹配的标签。

正则表达式匹配完全锚定,如 env=~"foo"的匹配被视为env=~"^foo$"。且选择器指定了多标签键值对时,返回的时序数据同时匹配多个条件

示例:http_requests_total{environment=~"staging|testing|development",method!="GET"},选择所有非 GET 类型的 staging、testing、development 环境请求。

匹配空标签值(v)的选择器会匹配所有没有设置该标签键(k)的时间序列,如 http_requests_total{environment=""} 会返回不带 environment 标签键的时序数据:

http_requests_total
http_requests_total{replica="rep-a"}
http_requests_total{replica="rep-b"}

并将排除:

http_requests_total{environment="development"}

区间向量选择器

上述标签名配合键值对的方式的选择器返回的数据类型都是瞬时向量,对于区间向量,它需要指定一个时间范围。语法上在瞬时向量选择器的末尾,加上方括号[],表示以当前时刻截止的时间范围内的时序数据。

如选择在过去5分钟内为所有时间序列记录的所有值,这些时间序列的指标名称为http_requests_total,标签为job的值设置为prometheus

http_requests_total{job="prometheus"}[5m]

持续时间指定为数字,后面紧跟以下单位之一:

  • ms -毫秒
  • s -秒
  • m -分钟
  • h -小时
  • d - days -假设一天总是有24小时
  • w - weeks -假设一周总是有7 d
  • y - years -假设一年总是有365 d1

    1:对于一年中的每一天,闰日被忽略,反之,对于一分钟,闰秒被忽略。

以下是有效持续时间的一些示例:

5h
1h30m
5m
10s

时间偏移符

offset 修饰符允许更改查询中各个瞬时和范围向量的时间偏移。

例如,以下表达式返回http_requests_total 相对于当前查询时刻过去5m的所有值:

http_requests_total offset 5m

请注意,offset修饰符总是需要紧跟在选择符之后,即sum(http_requests_total{method="GET"} offset 5m)是正确的;而sum(http_requests_total{method="GET"}) offset 5m是不正确的。

这个语法同样适用于区间向量。这将返回 http_requests_total 一周前的 5分钟范围内各值的比例:

rate(http_requests_total[5m] offset 1w)

时刻修饰符

@修饰符允许更改查询中各个瞬时向量和区间向量的求值时间。提供给@修饰符的时间是一个float类型的unix时间戳。

offset的区别在于@指定具体时刻,示例:

rate(http_requests_total[5m] @ 1609746000)

将返回 http_requests_total2021-01-04T07:40:00+00:00 时前 5 分钟各值的出现比例。

另外 @ 也可以和 offset 结合使用:

# offset after @
http_requests_total @ 1609746000 offset 5m
# offset before @
http_requests_total offset 5m @ 1609746000

这两个查询将产生相同的结果!

算数运算符

Prometheus的查询语言支持基本的逻辑和算术运算符。Prometheus中支持这些二进制算术运算符:+加j、-减j、*乘c、/除c、%取模q、^幂。


二进制算术运算符主要在标量/标量向量/标量向量/向量值对之间进行运算。

示例:prometheus_target_metadata_cache_bytes/1024/1024,表示计算Prometheus 自身 target 元数据缓存 MB 大小。还有如缓存大小label 除以总内存大小的 label 这种就属于 向量/向量

关系运算符

Prometheus中存在这些二元关系运算符:==!=><>=<=

默认情况下,这些运算符会过滤数据,但可以通过在运算符后添加 bool 修饰符来修改它们的行为,使其返回布尔值 01 而不是过滤。

http_requests_total{job="api"} > http_requests_total{job="web"}  # 过滤掉比较结果为 false 的样本

up > 0.5  # 过滤掉值 <= 0.5 的样本
up > bool 0.5  # 将值 <= 0.5 的样本值变为 0,将值 > 0.5 的样本值变为 1,删除度量名称

up 是一个内置的指标,它表示 Prometheus 监控的每个目标的可用性状态。

在两个标量之间,必须提供bool修饰符,这些运算符会导致另一个标量,根据比较结果,该标量是0(false)或1(true)。

5 > 3  # 错误,必须使用 bool 修饰符
5 > bool 3  # 结果是 1(true)

集合运算符

在 Prometheus 表达式语言(PromQL)中,逻辑/集合二元运算符用于处理时间序列数据。它们可以用来进行集合操作,或者用于执行逻辑操作:

  • and: 逻辑与运算符,返回两个时间序列的交集,即在两个表达式中都存在的时间序列。

    up and http_requests_total
    

    这个表达式返回同时存在于 up 和 http_requests_total 中的时间序列。

  • or :逻辑或运算符,返回两个时间序列的并集,即在至少一个表达式中存在的时间序列。

    up or http_requests_total
    

    这个表达式返回 up 和 http_requests_total 中的所有时间序列。

  • unless : 逻辑差运算符,返回在第一个时间序列中存在但在第二个时间序列中不存在的时间序列。

    up unless http_requests_total
    

    这个表达式返回存在于 up 中但不存在于 http_requests_total 中的时间序列。

向量匹配关键字

一对一匹配用于在两个向量中找到具有完全相同标签集的时间序列。默认情况下,所有标签必须匹配。如果使用了 ignoring 关键字,则指定的标签将被忽略,而其他标签必须匹配。如果使用了 on 关键字,则只有指定的标签需要匹配,其他标签会被忽略。

示例数据:

# http_requests_total 数据
http_requests_total{method="get", instance="a"}  100
http_requests_total{method="post", instance="a"} 50
http_requests_total{method="get", instance="b"}  80

# errors_total 数据
errors_total{method="get", instance="a"} 10
errors_total{method="post", instance="a"} 5
errors_total{method="get", instance="b"} 8

查询示例,默认情况下,完全匹配所有标签:

# 语法:<vector1> <operator> <vector2>
http_requests_total == errors_total

结果:

{method="get", instance="a"} true  # 100 == 10 返回 true
{method="post", instance="a"} true  # 50 == 5 返回 true
{method="get", instance="b"} true  # 80 == 8 返回 true

使用 ignoring 关键字忽略 instance 标签:

# 语法:<vector1> <operator> ignoring(<label list>) <vector2>
http_requests_total == ignoring(instance) errors_total

结果:

{method="get"} true  # 匹配到所有 `method="get"` 的时间序列
{method="post"} true  # 匹配到所有 `method="post"` 的时间序列

使用 on 关键字仅匹配 method 标签:

#语法:<vector1> <operator> on(<label list>) <vector2>
http_requests_total == on(method) errors_total

结果:

{method="get"} true  # 匹配到所有 `method="get"` 的时间序列
{method="post"} true  # 匹配到所有 `method="post"` 的时间序列

一对多匹配用于在两个向量中找到一个向量的每个时间序列与另一个向量中多个时间序列匹配的情况。必须使用 group_leftgroup_right 关键字明确指定哪一侧有较高的基数。

一对多匹配常用于需要对比一个向量中的单个时间序列与另一个向量中的多个时间序列的情况,例如计算每个 HTTP 方法的错误率。示例数据:

# http_requests_total 数据
http_requests_total{method="get", code="200"} 100
http_requests_total{method="get", code="500"} 50
http_requests_total{method="post", code="200"} 80

# errors_total 数据
errors_total{method="get"} 10
errors_total{method="post"} 5

使用 group_left 进行多对一匹配:

# 语法:<vector1> <operator> ignoring(<label list>) group_left(<label list>) <vector2>
errors_total / ignoring(code) group_left(method) http_requests_total

结果:

{method="get", code="200"} 0.1  # 10 / 100
{method="get", code="500"} 0.2  # 10 / 50
{method="post", code="200"} 0.0625  # 5 / 80

使用 group_right 进行一对多匹配:

#语法:<vector1> <operator> ignoring(<label list>) group_right(<label list>) <vector2>
http_requests_total / ignoring(code) group_right(method) errors_total

结果:

{method="get", code="200"} 10  # 100 / 10
{method="get", code="500"} 5  # 50 / 10
{method="post", code="200"} 16  # 80 / 5

聚合操作

Prometheus支持以下内置聚合运算符,可用于聚合单个即时向量的元素,从而生成具有聚合值的更少元素的新向量:

  • sum(计算尺寸总和)
  • min(选择最小尺寸)
  • max(选择最大尺寸)
  • avg(计算各维度的平均值)
  • group(结果向量中的所有值均为1)
  • stddev(计算各维度的总体标准差)
  • stdvar(计算各维度的总体标准方差)
  • count(计算向量中元素的数量)
  • count_values(计数具有相同值的元素的数量)
  • bottomk(样本值的最小k个元素)
  • topk(样本值的最大k个元素)
  • quantile(计算 φ 量子(0 ≤ φ ≤ 1)超过维度)

这些运算符既可以用于聚合所有标签维,也可以通过包含withoutby子句来保留不同的维。这些子句可以用在表达式之前或之后。语法:

<aggr-op> [without|by (<label list>)] ([parameter,] <vector expression>)
# 或
<aggr-op>([parameter,] <vector expression>) [without|by (<label list>)]

label list是未加引号的标签列表,可能包含尾随逗号,即(label1, label2)(label1, label2,)都是有效语法。

without从结果向量中删除列出的标签,而所有其他标签都保留在输出中。by则相反,它会丢弃未在by子句中列出的标签,即使它们的标签值在向量的所有元素之间都是相同的。

只有 count_valuesquantiletopkbottomk 需要 parameter

示例,如果指标http_requests_total具有按application、instance和group标签展开的时间序列,则我们可以通过以下方式计算每个应用程序和组在所有实例上看到的HTTP请求的总数::

sum without (instance) (http_requests_total)

另外上面的聚合操作加上后缀: <aggregation>_over_time() 允许随时间聚合给定范围向量的每个系列,并返回包含每个系列聚合结果的即时向量

运算符优先级

下面的列表显示了Prometheus中二元运算符的优先级,从最高到最低:

  1. ^
  2. *, /, %, atan2
  3. +, -
  4. ==, !=, <=, <, >=, >
  5. and, unless
  6. or

内置函数

PromQL 内置了一些数学函数和时间函数等函数,详情根据需求参考官方文档:https://prometheus.io/docs/prometheus/latest/querying/functions

Alert Manager 告警

已完全可被 Granfana 的告警替代!见后文 Granfana 的监控配置。

Exporter

上文提到 Prometheus 有一个 Exporter 组件,Exporter 可以是一个相对开放的概念,其可以是一个独立运行的程序独立于监控目标以外,也可以是直接内置在监控目标中。只要能够向 Prometheus 提供标准格式的监控样本数据即可。

Prometheus 官方维护了一些常用的 Exporter,另外还有大量优秀的第三方 Exporter 可供选择。该链接下可查看和下载官方收集的各类场景下的 Exporter:https://prometheus.io/docs/instrumenting/exporters

在使用 Export 前先灌输一些思想,即 Export 和应用一起运行内嵌在应用中延迟更低、明确自己需要采集的指标,不要一股脑全部采集,频率越高、指标越多占用磁盘则越多,所以已有的 Exporter 不一定实用和最佳,根据情况考虑自行实现一个 Exporter(一个/metrics 接口)。

监控 Linux主机

采集服务器主机上的运行指标如 CPU, 内存,磁盘等信息,可以使用 node_exporter。下载地址:https://prometheus.io/download/#node_exporter

解压后直接运行即可:

tar xzvf  node_exporter-1.2.2.linux-amd64.tar.gz
./node_exporter

为了开机即启动 Export 可创建一个 Service。在 /etc/systemd/system/ 目录下创建一个名为 node_exporter.service 的服务文件:

vi /etc/systemd/system/node_exporter.service

在文件中添加以下内容:

[Unit]
Description=Example Service
Wants=network-online.target
After=network-online.target

[Service]
Type=simple
ExecStart=/usr/local/bin/node_exporter

[Install]
WantedBy=multi-user.target

启动并启用服务:

# 重新加载 systemd 配置
systemctl daemon-reload

# 启动 node_exporter 服务
systemctl start node_exporter

# 设置 node_exporter 服务开机自启动
systemctl enable node_exporter

# 查看服务状态
systemctl status node_exporter

默认情况下,node_exporter 会在端口 9100 上监听。你可以通过访问以下 URL 来获取 node_exporter 所有监控数据,来检查是否在运行:

http://<your_server_ip>:9100/metrics

最后修改 Prometheus 服务器的配置文件 prometheus.yml,增加对Linux主机的监控job:

# 在 scrape_configs 配置项下添加配置:

scrape_configs:
  - job_name: "prometheus"
    static_configs:
      - targets: ["localhost:9090"]
  # 添加Node Exporter监控配置
  - job_name: "node_exporter"
    static_configs:
      - targets: ["localhost:9100"]

然后重启 Prometheus 服务器,或者开启了热加载,可以通过请求curl -X POST http://localhost:9090/-/reload或直接通过命令 kill -s SIGHUP <PID> 来动态更新配置。此时 Prometheus 管理后台的 Targets 列表下也会显示新加入的监控目标:

监控 MongoDB

如法炮制,该包通过第三方渠道下载:https://github.com/percona/mongodb_exporter/releases ,启动命令:

./mongodb_exporter --mongodb.uri mongodb://username:password@node1:27017,node2:27017,node3:27017/?replicaSet=myReplicaSet --collect-all

更多参数参考:https://github.com/percona/mongodb_exporter/blob/main/REFERENCE.md

添加 Prometheus 配置:

scrape_configs:
  - job_name: 'mongodb_exporter'
    static_configs:
      - targets: ['localhost:9216']

监控 Golang 服务

参考 Go DDD 项目:https://github.com/ihezebin/go-template-ddd,安装 Prometheus Go 包:

go get 

按照上文提到的方式注册一些指标,并在对应的业务中填充指标数据:

latencyCounter = prometheus.NewCounterVec(prometheus.CounterOpts{
		Name: "http_requests_total",
		Help: "Total number of HTTP requests.",
	}, []string{metricLabelHost, metricLabelPath, metricLabelMethod, metricLabelHTTPCode, metricLabelCode})

prometheus.MustRegister(httpRequestsTotal)


// 在具体业务中
labels := prometheus.Labels{
			// metricLabelPath:      u.Host + metricPath, // exclude param from url
			metricLabelHost:     u.Host,
			metricLabelPath:     metricPath,
			metricLabelMethod:   r.Request.Method,
			metricLabelHTTPCode: strconv.Itoa(r.StatusCode()),
			metricLabelCode:     "",
		}

latencyCounter.With(labels).Inc()

然后在 Gin HTTP 服务注册一个 /metrics 路由:

serverHandler.GET("/metrics", gin.WrapH(promhttp.Handler()))

添加 Prometheus 配置:

scrape_configs:
  - job_name: 'go-template-ddd_exporter'
    static_configs:
      - targets: ['localhost:8080']

Pushgateway

Pushgateway是一种中间服务,这种自定义的采集方式非常灵活,除了上文提到的短时应用外;长作业任务也可以不采用 Exporter,而选择只推送想采集的指标数据到 Pushgateway。

但 Pushgateway 也存在一定的短板缺陷:

  • 当通过单个Pushgateway监视多个实例时,Pushgateway 存在单点隐患和瓶颈
  • Pushgateway 永远不会忘记推送到它的系列,并且将永远将它们暴露给 Prometheus,除非这些系列通过Pushgateway 的API手动删除

当一个作业的多个实例通过instance标签或类似标签在Pushgateway中区分它们的指标时,后一点尤其相关。即使源实例被重命名或删除,实例的配置文件也将保留在Pushgateway中。这是因为作为指标缓存的Pushgateway的生命周期与向其推送指标的进程的生命周期基本上是分开的。与Prometheus通常的拉式监控相比:当实例消失时(有意或无意),它的指标将自动沿着消失。当使用Pushgateway时,情况并非如此,现在,您必须手动删除任何过时的指标,或者自己自动执行此生命周期同步。

翻译自官网:https://prometheus.io/docs/practices/pushing/#when-to-use-the-pushgateway

Grafana

Prometheus 并非不能将指标图形化,但是其呈现能力有限:

对比如下 Grafana 的页面显得也比较丑陋:

所以常使用专业做图形渲染的工具 Grafana,详见:Grafana 可视化监控和告警

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐