前言
最近在做一些业务上的监控,了解到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能够做到定位到具体的时间点和很长一段时间(应用存活时间)内的数据情况。

