前言
最近在做一些业务上的监控,了解到Prometheus
的Histogram
指标监控,还蛮符合我在业务的监控需求。
之前用Prometheus
的Counter
和Guage
比较多,对于Histogram
的使用还是头一遭,在网上也没找到好的中文文档。
于是想着把自己这次使用Prometheus
的经历输出成一篇博客文章,一方面记录自己是如何使用的,另一方面希望能对其他人有所帮助。
先简单介绍一下我的需求,了解一下为什么Histogram
是我想要的指标类型。
我业务中需要调用一个外部的数据接口,而这个接口是有每秒钟限定调用次数的限制的,当某次调用达到限流限制后,希望能够过一段时间后进行重试,如果再次失败就再延长间隔时间等待重试,最多重试「MaxRetryTime
」次。
我需要监控的内容是,记录「达到多少次后才重试成功;失败多少次
」。想要达到的效果如下图所示。
这里设置的「
MaxRetryTime
」= 5。
在本篇文章中我们会用到Grafana
的两个面板类型:Bar guage
和Heatmap
。
Add Histogram in Spring
因为后端服务是基于Java
的工程,所以在Spring
中来编写Histogram
数据的收集代码。
引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
开启配置文件
# 指标监控
management:
server:
port: ${server.port}
endpoint:
health:
show-details: always
endpoints:
web:
exposure:
include: health,info,prometheus,metrics
# 默认就是 /actuator
base-path: /actuator
metrics:
tags:
application: ${spring.application.name}
environment: ${spring.profiles.active}
# 启用 prometheus 的自动装载
export:
prometheus:
enabled: true
这是一个标准的配置。
收集指标示例
@Service
public class MetricCollectDemo {
@Resource
private PrometheusMeterRegistry prometheusMeterRegistry;
public void retry(int curRetryTime) {
String retryStatus = "";
// 查询数据
try {
// do rpc call
} catch (RpcCallLimitException e) {
retryStatus = "fail";
// retry ...
return;
} finally {
// 记录这是第几次成功或失败
DistributionSummary summary = DistributionSummary.builder("my_histogram")
.serviceLevelObjectives(1, 2, 3, 4, 5)
.tag("status", retryStatus)
.register(prometheusMeterRegistry);
summary.record(curRetryTime);
}
}
}
}
关键的代码就在于声明一个DistributionSummary
,配置其serviceLevelObjectives
,并注册到prometheusMeterRegistry
,然后调用「record
」方法记录监测值。
DistributionSummary
就是Histogram
,而serviceLevelObjectives
对应Histogram
中的「桶 bucket
」概念。
这里声明了5个「桶 bucket
」,分别是小于等于1的桶、小于等于2...小于等于5的桶,实际上还会创建有一个小于正无穷+Inf桶
。
如果此时summary.record(3)
,那么小于等于3的桶
的计数就会+1。
这里不用担心会重复「
注册
」,如果看底层的代码就知道prometheusMeterRegistry
是如何避免重复的指标的registry
了。
查看指标
通过暴露的http://{host}/actuator/prometheus
接口就能看到所有的prometheus
指标了,比如:
# TYPE my_histogram histogram
my_histogram_bucket{status="success",le="1.0",} 19.0
my_histogram_bucket{status="success",le="2.0",} 23.0
my_histogram_bucket{status="success",le="3.0",} 24.0
my_histogram_bucket{status="success",le="4.0",} 24.0
my_histogram_bucket{status="success",le="5.0",} 24.0
my_histogram_bucket{status="success",le="+Inf",} 24.0
my_histogram_count{status="success",} 24.0
my_histogram_sum{status="success",} 30.0
my_histogram_bucket{status="fail",le="1.0",} 5.0
my_histogram_bucket{status="fail",le="2.0",} 6.0
my_histogram_bucket{status="fail",le="3.0",} 6.0
my_histogram_bucket{status="fail",le="4.0",} 6.0
my_histogram_bucket{status="fail",le="5.0",} 6.0
my_histogram_bucket{status="fail",le="+Inf",} 6.0
my_histogram_count{status="fail",} 6.0
my_histogram_sum{status="fail",} 7.0
可以发现生成了对应的桶,在my_histogram
后面加了_bucket
并携带le
标签,le
代表的就是less or equal
的意思。
所以如果想要计算(3, 4]区间
的数量,应该用le="4.0"
的桶的数值 - le="3.0"
的桶的数值。
关于prometheus server
端的指标拉取和Grafana
导入数据源这里就不再展开了。
Grafana中展示每个桶的值
如果我们要在Grafana
中展示每个桶对应的值(但不看+Inf
桶的),首先先创建bar gauge
的Panel
,对应的PromQL
语句是:sum by(le) my_histogram_bucket{status="success", le!="+Inf"}
前面我们介绍过桶逐渐增大的,le
越大,对应的值越大。每一桶都包含前一桶的值。所以得到的图像是这样的:
但这不是我们想要的结果,我们想看的是每个桶区间的值。
展示每个桶区间的值
Grafana
支持直方图
的展示,只需要将Prometheus
数据格式从Time series
更改为Heatmap
。
我们会得到下面的结果,达到了我们所希望的结果:
但是你会发现这个图中是不包含任何时间序列的,也就是说,他只展示当前bucket
中的数值,随着应用重启,数值都会清空。接下来我们使用热点图来查看不同时间点的热点情况。
热力图
Grafana
为此提供了一个热力图面板,观测数据在时间线上的变化。
像github
上的contributions
展示就是一个热力图:
很容易就能发现在哪个时间点最能Coding
。
接下来就来看看如何设置这个热力图。基于前文的设置,首先将bar gague
的Panel
样式换成Heatmap
。
计算每个桶区间在某段时间内,如「[$__interval]
」时间间隔内的增加量,值越大的在热点图中就会越明显(颜色很深),PromQL
:sum(increase(my_histogram_bucket[$__interval])) by (le)
.
同样的,使用rate
函数也能达到相同的目的。最后,「将查询选项 Query options > 最大数据点 Max data points
」设置为 25
,避免因分辨率过高导致的浏览器性能下降。
最后得到效果如下:
根据热力图我们就能直接观察到,究竟是哪个时间点重试次数很多仍然失败,从而根据日志或别的方式去进一步观察问题。
可惜
可惜,因为Prometheus
是TiDB 时序型数据库
,每次采集的都是某个时间点的Bucket瞬时状态
,无法做到计算出某个时间段的具体累计数据,并通过直方图展示出来。
不过bar gauge
配合Heatmap
能够做到定位到具体的时间点和很长一段时间(应用存活时间)内的数据情况。