Prometheus

Prometheus是一个开源监控解决方案,用于收集和聚合指标作为时间序列数据。是继Kubernetes之后第二个CNCF托管项目

特点

普罗米修斯的主要特点是:

  • 多维数据模型,其中时间序列数据由指标名称和键/值对标识
  • PromQL,一种灵活的查询语言
  • 不依赖分布式存储;单个服务器节点是自治的
  • 时间序列收集通过 HTTP 上的 Pull 模型进行
  • 通过中间网关支持推送时间序列
  • 通过服务发现或静态配置发现Target
  • 多种图形和仪表板支持

什么是指标?

指标是外行术语中的数值测量。术语时间序列是指随时间变化的记录。用户想要测量的内容因应用程序而异。对于 Web 服务器来说,它可能是请求时间;对于数据库,它可以是活动连接或活动查询的数量等。

指标在理解应用程序为何以某种方式运行方面发挥着重要作用。假设您正在运行一个 Web 应用程序并发现它很慢。要了解您的应用程序发生了什么,您将需要一些信息。例如,当请求数量较多时,应用程序可能会变慢。如果您有请求计数指标,则可以确定原因并增加处理负载的服务器数量。

更多信息

下面将会介绍常见开发语言如果集成到Prometheus监控体系中。

Java

在Java世界里,所有应用事实上可以简单的划分为两类:Spring应用和非Spring应用。Spring已经成为了事实上的Jakarta EE(Java EE)标准,所以本节会分别介绍Spring应用和非Spring应用如何集成Prometheus指标。

Micrometer

首先需要说明一下就是Micrometer,Micrometer是一个在JVM-Based应用中的一个供应商中立的应用可观测性门面(facade),类似于slf4j。Micrometer支持常见的可观测性系统,包括Prometheus。

基本概念

Registry

每个应用都有一个MeterRegistry对象。是所有指标的注册表。

CompositeMeterRegistry

通过CompositeMeterRegistry,可以同时添加多个注册表,允许发布到多个监控系统

globalRegistry

一个静态全局注册表

Meter

监控中不同类型的指标,包括TimerCounterGaugeDistributionSummaryLongTaskTimerFunctionCounterFunctionTimerTimeGauge称作Meter。

命名约定

由于不同监控系统有不同的指标名约定,Micrometer建议使用小数点分割,然后会转换为目标监控系统推荐的命名约定,请看下面的例子:

1
registry.timer("http.server.requests");

在不同的监控系统中,会转换为推荐的名称:

  1. Prometheus - http_server_requests_duration_seconds
  2. Atlas - httpServerRequests
  3. Graphite - http.server.requests
  4. InfluxDB - http_server_requests

SpringBoot & Micrometer

首先创建一个SpringBoot项目,然后添加依赖

1
2
3
4
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'io.micrometer:micrometer-registry-prometheus'
}

接下来编辑application.yaml,添加下面的配置:

1
2
3
4
5
6
7
8
9
10
11
12
spring:
application:
name: JavaMetricsDemo

management:
endpoints:
web:
exposure:
include: prometheus
metrics:
tags:
application: ${spring.application.name}

首先,定义了应用的名称为JavaMetricsDemo,然后在management中开启prometheus端点暴露,接下来通过mertris配置给所有指标添加了一个自定义tagapplication=JavaMetricsDemo,方便区分

然后直接启动应用,访问http://localhost:8080/actuator/prometheus,即可看到默认情况下会暴露一些常见指标,jvm,tomcat,logback等。

暴露自定义指标

在实际开发中,自带的指标肯定是不够用的,需要手动添加一些业务、接口相关指标,参考下面的代码:

1
2
3
4
5
6
7
8
9
10
@RestController
public class DemoController {

@Timed(value = "foo.time", description = "Time taken to return foo", histogram = true)
@RequestMapping
public String foo() {
return "foo";
}

}

我们创建了一个DemoController,然后定义了一个返回字符串的接口,注意我们在方法上面添加了**@Timed**注解,然后重新启动应用,观察指标变化

1
2
3
4
5
6
7
8
9
10
# HELP http_server_requests_seconds  
# TYPE http_server_requests_seconds summary
http_server_requests_seconds_count{application="JavaMetricsDemo",error="RuntimeException",exception="RuntimeException",method="GET",outcome="SERVER_ERROR",status="500",uri="/",} 2.0
http_server_requests_seconds_sum{application="JavaMetricsDemo",error="RuntimeException",exception="RuntimeException",method="GET",outcome="SERVER_ERROR",status="500",uri="/",} 0.0217194
http_server_requests_seconds_count{application="JavaMetricsDemo",error="none",exception="none",method="GET",outcome="SUCCESS",status="200",uri="/",} 3.0
http_server_requests_seconds_sum{application="JavaMetricsDemo",error="none",exception="none",method="GET",outcome="SUCCESS",status="200",uri="/",} 0.0175536
# HELP http_server_requests_seconds_max
# TYPE http_server_requests_seconds_max gauge
http_server_requests_seconds_max{application="JavaMetricsDemo",error="RuntimeException",exception="RuntimeException",method="GET",outcome="SERVER_ERROR",status="500",uri="/",} 0.0203117
http_server_requests_seconds_max{application="JavaMetricsDemo",error="none",exception="none",method="GET",outcome="SUCCESS",status="200",uri="/",} 0.0114682

由此我们可以知道,通过添加**@Timed**,可以获取接口响应时间相关指标,包含最大值,请求次数等。通过上述指标,可能会对服务健康提供一定帮助,也可以通过指标的tag精确到每个不同的接口请求。

接下来继续改造该类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@RestController
public class DemoController {

private final MeterRegistry meterRegistry;

public JavaMetricsApplication(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
}

@PostConstruct
public void init() {
Counter.builder("foo.errors").description("Number of errors").register(meterRegistry);
}

@RequestMapping
public String foo() {
if (Math.random() > 0.5) {
Counter counter = meterRegistry.counter("foo.errors");
counter.increment();
throw new RuntimeException("oops");
}
return "foo";
}

}

通过IOC容器注入一个MeterRegistry,这就是上文说的每个应用的Registry对象,我们一般不需要手动创建,直接注入使用即可。

然后我们在init段中声明一个Counter类型的指标,这个过程建议单独放到一起,在应用启动时候注册到全局的Registry即可,然后我们获取一个叫foo.errors的Counter,用来统计错误次数,每当程序发生异常,就将次数加一。

再次启动应用,手动请求几次接口,重新查看指标页面

1
2
3
# HELP foo_errors_total Number of errors
# TYPE foo_errors_total counter
foo_errors_total{application="JavaMetricsDemo",} 3.0

可以看到我们新增的指标已经添加上来了

指标名和代码中的不同?

这里按照micrometer约定,因为micrometer是一个厂商中立的api,所以为了保持统一性,代码中所有指标统一用点分隔,在具体的provider中会自动转换成目标推荐的格式,这里就按照Prometheus的格式转换成了下划线,又因为是counter类型,所以最后添加了_total后缀。

总之,在代码中统一使用点分隔即可

到这里,演示了如何使用micrometer在一个Java应用中暴露指标接口和自定义指标,其他类型参考Counter用法即可。

Prometheus client_java

偶然发现Prometheus官方提供的clinet库从之前的simple_client换成了client_java,并且释出了1.0版本,这里也简单使用一下

快速开始

详情参考 quickstart

首先引入依赖

1
2
3
implementation 'io.prometheus:prometheus-metrics-core:1.0.0'
implementation 'io.prometheus:prometheus-metrics-instrumentation-jvm:1.0.0'
implementation 'io.prometheus:prometheus-metrics-exporter-httpserver:1.0.0'
  • prometheus-metrics-core是指标库。
  • prometheus-metrics-instrumentation-jvm提供开箱即用的 JVM 指标。
  • prometheus-metrics-exporter-httpserver是一个用于公开 Prometheus 指标的独立 HTTP 服务器。

如果项目已经使用Servlet等,可以使用prometheus-exporter-servlet-jakarta

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import io.prometheus.metrics.core.metrics.Counter;
import io.prometheus.metrics.exporter.httpserver.HTTPServer;
import io.prometheus.metrics.instrumentation.jvm.JvmMetrics;
import java.io.IOException;

public class App {
public static void main(String[] args) throws InterruptedException, IOException {
JvmMetrics.builder().register(); // initialize the out-of-the-box JVM metrics
Counter counter = Counter.builder()
.name("my_count_total")
.help("example counter")
.labelNames("status")
.register();
counter.labelValues("ok").inc();
counter.labelValues("ok").inc();
counter.labelValues("error").inc();
HTTPServer server = HTTPServer.builder()
.port(9400)
.buildAndStart();
System.out.println("HTTPServer listening on port http://localhost:" + server.getPort() + "/metrics");
Thread.currentThread().join(); // sleep forever
}
}

这是首先初始化了prometheus-metrics-instrumentation-jvm提供的自带的JVM指标,可以看到这里并没有Registry声明,因为通过这里可以看到client_java默认隐式使用了一个全局的Registry,并且也推荐只使用默认实现。

然后通过CounterBuilder创建了一个Counter指标,并且设置了几个样本值,最后在9400端口启动了一个Prometheus指标端点。

分析源码可以发现prometheus-metrics-exporter-httpserver默认使用了jdk HttpServer实现

然后需要解释一下这里的labelNameslabelValues是怎么使用的,这里给出一个下面例子,相信看完你就懂了:

1
2
3
4
5
6
7
8
Counter counter = Counter.builder()
.name("my_count_total")
.help("example counter")
.labelNames("status", "path")
.register();
counter.labelValues("ok", "/foo").inc();
counter.labelValues("ok", "/bar").inc();
counter.labelValues("error", "/error").inc();

更多概念一文也无法讲清楚,若有需要推荐查看官网原文

Spring

目前client_java在2023年9月27日才发布1.0版本,所以目前官方还是推荐使用Micrometer方案。

非Spring项目中可考虑使用client_java和Micrometer皆可

其他语言

Prometheus提供了多种语言的SDK用来对应用可观测性指标收集,目前,官方支持的有:

非官方支持: