安装
包管理安装
#arch系
pacman -S prometheus
部分启动参数
#通过添加启动参数选择配置文件位置
--config.file=配置文件地址
持久化
-
Prometheus 的本地时间序列数据库以自定义、高效的格式将数据存储在本地存储上
-
Prometheus的存储数据库默认只保留15天的数据,可以通过启动参数来配置存储的规则
#配置本地存储的路径
--storage.tsdb.path=data/
#配置删除旧数据的时间,默认15d代表删除15天以前的数据
--storage.tsdb.retention.time=15d
Prometheus还支持使用远程数据库存储,这里不展开
配置
配置Target
简介
可以通过配置文件来定义要抓取的job和instance,所有抓取到的服务可以在Target页面查看到。
通常,一个instance代表单个进程,而相同instance组成一个job。
同样的,Prometheus也有自己的metrics页面,所以可以开启多个Prometheus来交叉监控,提高可用性
eureka
Prometheus配置
#可以直接在配置文件底部添加
- job_name: 'Spring Cloud'
#配置metrics路径
metrics_path: '/actuator/prometheus'
eureka_sd_configs:
#可以支持设置多个注册中心
- server: 'http://127.0.0.1:7001/eureka'
- server: 'http://127.0.0.1:7002/eureka'
maven项目开启
要导入的maven依赖
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
项目配置文件
management.endpoints.web.exposure.include:*
management.endpoints.enabled-by-default=true
management.endpoint.metrics.enabled=true
management.endpoint.prometheus.enabled=true
management.metrics.export.prometheus.enabled=true
mysql
Prometheus配置
- job_name: 'mysql'
static_configs:
#mysqld_exporter的ip及端口,多个exporter可以用","隔开
- targets: ['127.0.0.1:9104']
部署exporter
想要配置一个数据源,一般都是要配置一个外置的导出器(exporter)。exporter
会在目标服务上监控对应中间件等其他服务的指标并暴露一个用来展示metrics
的接口,而Prometheus会从这个接口拉取数据。
以mysql为例,想要配置一个mysql的数据源,要在目标机器上启动一个mysqld_exporter
来导出mysql
的metrics
。
mysqld_exporter项目地址:mysqld_exporter
配置
通过以下方法可以快速配置并启动mysqld_exporter
-
编辑配置文件:
.my.cnf
[client] user=root password=haoran8060
-
通过参数启动
mysqld_exporter
mysqld_exporter --config.my-cnf:配置文件地址
更多详细启动参数可以从mysqld_exporter中查看
更多exporter
prometheus还提供很多其他中间件的exporter
,可以在GitHub上找到
更多exporter:prometheus
配置自定义Label
默认情况下,当Prometheus加载Target实例完成后,这些Target时候都会包含一些默认的标签:
上面这些标签将会告诉Prometheus如何从该Target实例中获取监控数据。一般来说,Target以
__
作为前置的标签是在系统内部使用的,因此这些标签不会被写入到样本数据中。不过这里有一些例外,例如,我们会发现所有通过Prometheus采集的样本数据中都会包含一个名为instance的标签,该标签的内容对应到Target实例的__
address__
。 这里实际上是发生了一次标签的重写处理。这种发生在采集样本数据之前,对Target实例的标签进行重写的机制在Prometheus被称为Relabeling。
# The source labels select values from existing labels. Their content is concatenated
# using the configured separator and matched against the configured regular expression
# for the replace, keep, and drop actions.
[ source_labels: '[' <labelname> [, ...] ']' ]
# Separator placed between concatenated source label values.
[ separator: <string> | default = ; ]
# Label to which the resulting value is written in a replace action.
# It is mandatory for replace actions. Regex capture groups are available.
[ target_label: <labelname> ]
# Regular expression against which the extracted value is matched.
[ regex: <regex> | default = (.*) ]
# Modulus to take of the hash of the source label values.
[ modulus: <int> ]
# Replacement value against which a regex replace is performed if the
# regular expression matches. Regex capture groups are available.
[ replacement: <string> | default = $1 ]
# Action to perform based on regex matching.
[ action: <relabel_action> | default = replace ]
实例
在本次demo中,添加了一个app_name
的标签
可以看出以eureka作为Target,有很多独有的变量
- job_name: 'Spring Cloud'
scheme: http
metrics_path: '/actuator/prometheus'
eureka_sd_configs:
- server: 'http://127.0.0.1:7001/eureka'
- server: 'http://127.0.0.1:7002/eureka'
relabel_configs:
#数据来源,这里使用了eureka中的application.name
- source_labels: [__meta_eureka_app_name]
#映射的Label名
target_label: app_name
更多详细内容查看relabel_config
更多配置
官方文档地址:CONFIGURATION
PromQL
Prometheus提供了一种名为(PromQL)的查询语句,可以用户实时选择和聚合时间序列数据。
官方文档:QUERYING PROMETHEUS
简介
数据类型
PromQL数据类型分为四类
-
Instant vector(瞬时向量):一组时间序列,有多个时序序列,但是每一个时序序列只有一个时刻的值
-
Range vector(区间向量):一组时间序列,每一个时序有个一个时间区间的数据
-
Scalar(标量数据):一个简单的数字浮点值
-
String(字符串):一个字符串,目前尚未使用
时间序列选择器
瞬时向量选择器
对于向量来说,瞬时向量选择器支持通过在花括号{}
内的标签匹配器来过滤时间序列如:
http_requests_total{job="prometheus",group="canary"}
可以过滤出job
为prometheus
且group
为canary
的时间序列
匹配标签支持始终匹配运算符
=
:匹配完全相同的标签!=
:匹配相同的标签=~
:通过正则表达式匹配标签!~
:与正则表达式不匹配的标签
范围向量选择器
通过在末尾添加[]
来指定时间区间
http_requests_total{job="prometheus"}[5m]
时间区间
时间区间为一个数字加上一个单位
ms
:毫秒s
:秒m
:分钟h
:小时d
:天w
:周y
:年
时间可以通过串联来组合,如:
5h30m
运算符
PromQL支持算数二元运算符(+
,-
,*
,/
,%
,^ 幂
)、比较二元运算符(==
,!=
,>
,<
,>=
,<=
)、逻辑二维运算符(and
,or
,unless 补充
)等运算符。
因为Prometheus拥有向量和标量,所以运算符的运算要区分出向量与标量的运算
算数二元运算符
- 标量与标量:结果为一个标量
- 向量与标量:运算符作用于向量中的每个数字,结果为向量。例如,如果对向量乘2,那么结果为一个向量,且其中的每个值都乘二。
- 向量与向量:运算符作用于左侧向量中的每个条目以及右侧向量中的匹配的元素。
比较二元运算符
- 标量与标量:必须使用
bool
修饰,结果产生一个标量,值为0(false)或1(true)如
1< bool 2
- 向量与标量:运算符运用于向量中的每个值。如果不使用
bool
修饰,结果为false
的向量会直接被删除,如果使用bool
修饰,每个向量都会被保存,而向量的值被修改为0(false)或1(true)
#不匹配的向量被去除,结果保留向量原有的值
http_requests_total< 100
#所有向量会被保留,每个向量的值为0或1
http_requests_total< bool 100
- 向量与向量:运算符默认充当过滤器,应用于匹配向量。表达式不为真或在表达式的另一侧找不到匹配项的向量元素则被删除,如果用
bool
则会被保留,而向量的值被修改为0(false)或1(true)
#对照查询
process_cpu_usage
#结果和上面的对照组相同
process_cpu_usage== process_cpu_usage
#结果为空
process_cpu_usage< process_cpu_usage
#数量与对照组相同,但是值都为0
process_cpu_usage< bool process_cpu_usage
向量匹配
一对一向量匹配
在默认情况下,如果两个向量的标签和对应的值相同,则认为他们匹配,可以使用关键字来修改匹配的规则
on(<label list>)
:表示只需要列出的标签匹配就认为匹配ignoring(<label list>)
:表示在原有的基础上,即使列出的标签不匹配,也可以认为向量匹配
<vector expr> <bin-op> ignoring(<label list>) <vector expr>
<vector expr> <bin-op> on(<label list>) <vector expr>
使用示例查看官方文档
多对一与一对多向量匹配
多对一和一对多匹配是指左端或右端的一个向量元素可以与另一端的多个元素匹配的情况。这必须使用group_left
或group_right
修饰符明确请求,其中左/右确定哪个向量的匹配。
<vector expr> <bin-op> ignoring(<label list>) group_left(<label list>) <vector expr>
<vector expr> <bin-op> ignoring(<label list>) group_right(<label list>) <vector expr>
<vector expr> <bin-op> on(<label list>) group_left(<label list>) <vector expr>
<vector expr> <bin-op> on(<label list>) group_right(<label list>) <vector expr>
使用示例查看官方文档
使用的语句
下面列出本次demo中各个面板使用的语句
其中展示结果是使用Grafana的图表
QPS(1分钟平均)
- 作用:
- 展示一分钟内平均Ops/s
- PromQL语句:
sum by(instance)(rate(http_server_requests_seconds_count{ job="Spring Cloud"}[1m]))
- 结果展示
响应时间
- 作用:
- 展示各个实例的响应时间
- PromQL语句:
sum by(instance)(rate(http_server_requests_seconds_sum{job="Spring Cloud", status!~"5.."}[1m]))/sum by(instance)(rate(http_server_requests_seconds_count{job="Spring Cloud", status!~"5.."}[1m]))
- 结果展示:
堆内存使用率
- 作用:
- 展示jvm堆内存使用率
- PromQL语句:
sum by(instance)(jvm_memory_used_bytes{job="Spring Cloud", area="heap"})*100/sum by(instance)(jvm_memory_max_bytes{job="Spring Cloud", area="heap"})
- 结果展示:
GC暂停时间
- 作用:
- 展示jvm GC暂停时间
- PromQL语句:
rate(jvm_gc_pause_seconds_sum{job="Spring Cloud"}[1m])/rate(jvm_gc_pause_seconds_count{job="Spring Cloud"}[1m])
- 结果展示:
非堆内存使用率
- 作用:
- 展示非堆内存使用率
- PromQL语句:
sum by(instance)(jvm_memory_used_bytes{job="Spring Cloud", area="nonheap"})*100/sum by(instance)(jvm_memory_max_bytes{job="Spring Cloud", area="nonheap"})
- 结果展示:
CPU使用率
- 作用:
- 展示实例的CPU使用率
- PromQL语句:
process_cpu_usage{job="Spring Cloud"}
- 结果展示:
网关请求量
-
作用:
- 记录从Zuul网关总的请求量
- 记录每个
client
被分摊到的请求量
-
PromQL语句:
sum(increase(http_server_requests_seconds_count{instance=~".*zuul.*"}[30s]))
sum by(instance)(increase(http_server_requests_seconds_count{instance=~".*client.*"}[30s]))
-
结果展示:
请求错误率
-
作用:
-
展示每台实例接收到的请求数
-
展示每台实例接收到的请求后出现的错误数
-
展示每台实例的错误率
-
-
PromQL语句:
sum by(instance)(increase(http_request_total[30s]))
increase(http_request_error_total[30s])
sum by(instance)(increase(http_server_requests_seconds_count[30s]))/(increase(http_request_total[30s])+(increase(http_request_total[30s]) == bool 0 ))*(increase(http_request_total[30s])!=bool 0)*100
-
备注:
http_request_total
与http_request_error_total
为自定义的指标,具体查看[Spring Boot自定义指标](#Spring Boot自定义指标)- 第三条语句是为了避免除零,所以才这么复杂。因为错误率是(frac{请求错误数}{请求数}),如果没有请求,就会出现除零,导致图表连线不连续,具体查看除零问题
-
结果展示:
在线微服务
- 作用:
- 展示微服务的在线情况
- PromQL语句:
up{job="Spring Cloud"}
- 结果展示:
在线服务器数量
-
Spring Boot自定义指标作用:
1.展示微服务在线数
-
PromQL语句:
sum(up{job="Spring Cloud"})
-
结果展示:
Spring Boot自定义指标
概述
https://www.cnblogs.com/yunlongn/p/11343848.html
https://www.cnblogs.com/cjsblog/p/11556029.html
Micrometer
几个类
public Counter counter(String name, String... tags) {
return counter(name, Tags.of(tags));
}
public static Tags of(@Nullable String... keyValues) {
...
if (keyValues.length % 2 == 1) {
throw new IllegalArgumentException("size must be even, it is a set of key=value pairs");
}
...
}
Meter
Micrometer
提供一系列原生的Meter
,包括Timer
,Counter
,Gauge
,DistributionSummary
,LongTaskTimer
,FunctionCounter
,FunctionTimer
,TimeGauge
。不同的meter类型导致有不同的时间序列指标值。例如,单个指标值用Gauge表示,计时事件的次数和总时间用Timer表示
Meter
由MeterRegistry
生成
Tag
Tag
(标签)是Micrometer
的一个重要的功能,严格来说,一个度量框架只有实现了标签的功能,才能真正地多维度进行度量数据收集。Tag的命名一般需要是有意义的,所谓有意义就是可以根据Tag的命名可以推断出它指向的数据到底代表什么维度或者什么类型的度量指标。
如这样声明一个Counter
registry.counter("test", "test_tag", "tag_info");
最后显示出的指标就是
test_tag_total{tag_name="tag_info"}
MeterRegistry
-
MeterRegistry
- 是一个抽象类,定义了获取一系列预制的
Meter
的方法
- 是一个抽象类,定义了获取一系列预制的
-
PrometheusMeterRegistry
- 实现了
MeterRegistry
抽象类
- 实现了
-
CompositeMeterRegistry
- 使用了组合模式,实现了
MeterRegistry
抽象类 - 内置了一个
Set<MeterRegistry> registries
,可以向里头添加删除MeterRegistry
- 使用了组合模式,实现了
Metrics
是全局的MeterRegistry
,使用方式更加简单便捷,因为一切只需要操作工厂类Metrics的静态方法,如
Metrics.addRegistry(new SimpleMeterRegistry());
Counter counter = Metrics.counter("counter", "tag-1", "tag-2");
counter.increment();
上面就是一个最简单的使用例,代码创建了一个Counter
,如果使用了Prometheus
的包,那么就会自动显示到metrics
页面,并且在Prometheus
可以直接查看到
内部有一个静态的CompositeMeterRegistry
,如果导入的是Prometheus
的包,那么Spring会自动注入一个PrometheusRegistry
的实例
实例
通过自定义指标来展示每个模块的最大实例数(Eureka为注册中心)
package com.example.server2.service;
import ...
@Service
public class CountService {
//定时查询最大微服务数
@Scheduled(cron = "*/10 * * * * *")
public void getAllService(){
//获取已经注册过的所有微服务信息
PeerAwareInstanceRegistry registry = EurekaServerContextHolder.getInstance().getServerContext().getRegistry();
List<Pair<Long, String>> instancesList = registry.getLastNRegisteredInstances();
//将微服务通过名称来分别计数
HashMap<String, Integer> map = new HashMap<>();
for (Pair<Long, String> longStringPair : instancesList) {
System.out.println(longStringPair.first()+": "+longStringPair.second());
//从默认传回的字符串中切割出模块名
String name = getServerName(longStringPair.second());
map.put(name, map.getOrDefault(name, 0)+1);
}
//将结果展示为指标,其中指标名为"server_total",给指标添加了"server_name"的tag来区分不同的模块。
for (Map.Entry<String, Integer> entry : map.entrySet()) {
Metrics.gauge("server_total", Collections.singleton(Tag.of("server_name", entry.getKey())),entry.getValue());
}
}
//获取字符串中的模块名
private String getServerName(String name){
int index = name.indexOf('(');
String s = new String(name.substring(0,index));
System.out.println(s);
return s;
}
}
PushGateway
Prometheus实际上只支持pull
的方式获取指标数据,如果想要采用push
的方法推送数据,则要先将数据push
到PushGateway,之后Prometheus再从PushGateway中拉取数据
官方文档的建议是尽量少使用这种方式来推送数据,因为有以下缺点
- 当通过单个 Pushgateway 监控多个实例时,Pushgateway可能会发生故障并且潜在瓶颈。
- PushGateway中的
up
状态是在作业推送数据时自动生成,无法监控作业的状态 - PushGateway 可以持久化推送给它的所有监控数据,即使你的监控已经下线,Prometheus 还会拉取到旧的监控数据,除非通过api删除
官方推荐仅在运行批处理作业(不会连续运行,抓取困难的作业)的情况下使用。而如果是运行时间超过几分钟的批处理作业,官方还是推荐使用基于拉取的方式来监控
批处理作业的关键指标有最后一次成功的时间、作业的每个主要阶段所花费的时间、整体运行时间和上次完成作业的时间(成功或失败)
如果是因为防火墙或者NAT导致无法直接获取Target的指标,官方推荐将Prometheus部署在防火墙后,或者使用PushProx进行内网穿透
PushGateway部署
从GitHub下载Release后直接运行,也支持从docker部署
运行后可以访问到PushGateway的页面(默认地址为127.0.0.1:9091):
之后在Prometheus中添加PushGateway为Target
...
- job_name: 'pushgateway'
static_configs:
- targets: ['localhost:9091']
...
在Prometheus中可以查看到PushGateway:
Java使用PushGateway类
这里实现了一个模拟耗时的操作。一个线程累加一个数到100,每次都延迟1s。而另一个线程则定时push数的当前值,时间间隔为1s
import ...
public class Pushgateway {
public static void main(String[] args) throws IOException {
//一个计数器,模拟一个耗时操作,当数字到达100就停止
Count count = new Count();
//用来定时获取count中的数字,并push到PushGateway上
Push push = new Push(count);
//启动线程
Thread t1 = new Thread(count);
Thread t2 = new Thread(push);
t1.start();
t2.start();
}
static class Count implements Runnable{
//初始为0
Integer value=0;
//获取当前的值
public Integer getValue() {
return value;
}
//将当前的值加一
public synchronized void addValue() {
value = value+1;
}
@Override
public void run() {
//延迟1秒来模拟耗时操作,当数值达到100时停止
while(getValue()<100){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
addValue();
}
}
}
static class Push implements Runnable{
//一个内置Gauge类型的指标,与Counter不同,他的值可以上下浮动
Gauge duration;
//上文的Count类
Count count;
//PushGateway类,通过ip端口来构造
PushGateway pg = new PushGateway("127.0.0.1:9091");
//创建一个注册器
CollectorRegistry registry = new CollectorRegistry();
//构造函数,传入一个Count类
public Push(Count count) throws IOException {
this.count=count;
//创建指标,并指定指标的名字和说明信息,并注册到registery上
duration = Gauge.build()
.name("count_now_number").help("一个说明信息,会展示在Prometheus上").register(registry);
}
@Override
public void run() {
//每秒push一次数据,当数值达到100时停止
while(count.getValue()<100){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//更新数据并将新数据push到PushGateway上
Integer value = count.getValue();
duration.set(value);
try {
pg.pushAdd(registry, "test");
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}