一、client_java

client_java是Prometheus针对JVM类开发语言的client library库,我们可以直接基于client  _java用户可以快速实现独立运行的Exporter程序,也可以在我们的项目源码中集成client_java以支持Prometheus。注意:Prometheus 提供的client_java 项目可以很方便的将 JVM 和自定义的指标暴露出来,具体可以监控的指标参考

二、配置文件

  1. pom文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.6.3</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.code</groupId>
	<artifactId>prometheus_test</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>prometheus_test</name>
	<description>prometheus_test</description>
	<properties>
		<java.version>1.8</java.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<!--这个依赖用于健康检查,审计,指标收集-->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-actuator</artifactId>
		</dependency>

		<!--这个依赖用于把数据转换为prometheus格式使用 r-->
		<dependency>
			<groupId>io.micrometer</groupId>
			<artifactId>micrometer-registry-prometheus</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>

	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>
</project>
  1. yaml文件
spring:
  application:
    name: springboot-prometheus

management:
  metrics:
    #允许对应的数据指标被导出
    export:
      prometheus:
        enabled: true
  #激活 prometheus 并转换为对应的 prometheus 数据
  endpoint:
    prometheus:
      enabled: true
    health:
      show-details: always
    metrics:
      enabled: true
  #对外暴露哪些指标
  endpoints:
    web:
      exposure:
        include: "*"
      # 自定义监控地址的路径来避免被别人轻易的就访问到,更安全的做法是将其端口同服务的端口区分开,并禁止外网访问 原始基路径/actuator
      base-path: /customize-actuator
    # actuator暴露接口使用的端口,为了和api接口使用的端口进行分离,用于禁止外网访问
  server:
    port: 8085

三、自定义Collector

在client_java的simpleclient模块中提供了自定义监控指标的核心接口。simpleclient模块由micrometer-registry-prometheus依赖导入。当无法直接修改监控目标时,可以通过自定义Collector的方式,实现对监控样本收集,该收集器需要实现collect()方法并返回一组监控样本,如下所示:

public class CustomExporter extends Collector {
    @Override
    public List<MetricFamilySamples> collect() {
         List<MetricFamilySamples> mfs = new ArrayList<>();

        //创建metrics指标
        GaugeMetricFamily labeledGauge =
                new GaugeMetricFamily("io_namespace_custom_metrics", "custom metrics", Collections.singletonList("labelname"));

        //设置指标的label以及value
        labeledGauge.addMetric(Collections.singletonList("labelvalue"), 1);

        mfs.add(labeledGauge);
        return mfs;
    }
}

这里定义了一个名为my_guage的监控指标,该监控指标的所有样本数据均转换为一个MetricFamilySamples.Sample实例,该实例中包含了该样本的指标名称、标签名数组、标签值数组以及样本数据的值。监控指标my_guage的所有样本值,需要持久化到一个MetricFamilySamples实例中,MetricFamilySamples指定了当前监控指标的名称、类型、注释信息等。需要注意的是MetricFamilySamples中所有样本的名称必须保持一致,否则生成的数据将无法符合Prometheus的规范。

@Component
public class PromeConfig {
    @Resource
    private CollectorRegistry collectorRegistry;
    @Bean
    @Primary
    public CustomCollector customCollector() {
        return new CustomCollector().register(collectorRegistry);
    }
}

访问:http://localhost:8085/customize-actuator/prometheus

Prometheus java客户端组装暴露数据 prometheus client library_spring


可以看到自定义的collector被Prometheus所统计监控

四、使用内置的Collector

通过client_java中定义的标准接口,用户可以快速实现自己的监控数据收集器,并通过HTTPServer将样本数据输出给Prometheus。除了提供接口规范以外,client_java还提供了多个内置的Collector模块,以simpleclient_hotspot为例,该模块中内置了对JVM虚拟机运行状态(GC,内存池,JMX,类加载,线程池等)数据的Collector实现。

<!-- https://mvnrepository.com/artifact/io.prometheus/simpleclient_hotspot -->
<dependency>
    <groupId>io.prometheus</groupId>
    <artifactId>simpleclient_hotspot</artifactId>
    <version>0.16.0</version>
</dependency>

通过调用io.prometheus.client.hotspot.DefaultExport的initialize方法注册该模块中所有的Collector实例:

DefaultExports.initialize();

重新运行CustomExporter,并获取样本数据

$ curl http://127.0.0.1:1234/metrics
# HELP jvm_buffer_pool_used_bytes Used bytes of a given JVM buffer pool.
# TYPE jvm_buffer_pool_used_bytes gauge
jvm_buffer_pool_used_bytes{pool="direct",} 8192.0
jvm_buffer_pool_used_bytes{pool="mapped",} 0.0

除了之前自定义的监控指标以外,在响应内容中还会得到当前JVM的运行状态数据。在client_java项目中除了使用内置了对JVM监控的Collector以外,还实现了对Hibernate,Guava Cache,Jetty,Log4j、Logback等监控数据收集的支持。用户只需要添加相应的依赖,就可以直接进行使用。

Prometheus java客户端组装暴露数据 prometheus client library_spring boot_02

五、在业务代码中进行监控埋点

  1. Prometheus中4种监控类型
  • Counter,只增不减的计数器
  • Gauge,可增可减的仪表盘
  • Histogram,自带buckets区间用于统计分布统计图
  • Summary, 客户端定义的数据分布统计图

基于这些实现,开发人员可以非常方便的在应用程序的业务流程中进行监控埋点

  1. Counter:计数器是表示单个单调递增计数器的累积量,其值只能增加或在重启时重置为零。
  • 服务的请求数量
  • 已完成的任务数
  • 出现的错误总数

一般而言,Counter类型的metrics指标在命名中我们使用_total结束。

public class PrometheusMetricsInterceptor implements HandlerInterceptor {
    @Autowired
    private MeterRegistry meterRegistry;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
         // 请求计数+1,标签分别为 请求路径,请求方法,response http code
        meterRegistry.counter("http_requests_total", Tags.of("url", request.getRequestURI(), "method", request.getMethod(),"status",String.valueOf(response.getStatus()))).increment();
}

Prometheus java客户端组装暴露数据 prometheus client library_开发语言_03

不要使用计数器来监控可能减少的值。 例如,不要使用计数器来处理当前正在运行的进程数,而应该用Gauge。

  1. Gauge:Gauge可以用来存放一个可以任意变大变小的数值,通常用于测量值Gauge可以任意加减
  • 温度
  • 内存
  • cpu
  • 线程数
public class PrometheusMetricsInterceptor implements HandlerInterceptor {
    @Autowired
    private MeterRegistry meterRegistry;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 处理中计数 +1
        meterRegistry.gauge("http_process_req", Tags.of("url", request.getRequestURI(), "method", request.getMethod()), 1);
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        // 请求完毕,正在请求计数器-1
        meterRegistry.gauge("http_process_req", Tags.of("url", request.getRequestURI(), "method", request.getMethod()), -1);
    }
}

Prometheus java客户端组装暴露数据 prometheus client library_开发语言_04

  1. Histogram:直方图,内置分析样本的分布情况
  • 请求持续时间
  • 响应大小
  • 区间数据分组统计
    Histogram:自带buckets区间用于统计分布统计图
    主要用于在指定分布范围内(Buckets)记录大小(如http request bytes)或者事件发生的次数。

以请求响应时间requests_latency_seconds为例,假如我们需要记录http请求响应时间符合在分布范围{.005, .01, .025, .05, .075, .1, .25, .5, .75, 1, 2.5, 5, 7.5, 10}中的次数时。

public class PrometheusMetricsInterceptor implements HandlerInterceptor {

 @Autowired
    private MeterRegistry meterRegistry;
    private ThreadLocal<Timer.Sample> threadLocal = new ThreadLocal<>();

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        Timer.Sample sample = Timer.start();
        threadLocal.set(sample);
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

        Timer histogramTimer = Timer.builder("http_req_histogram")
                //publishPercentiles-用于发布你的应用中计算出的百分比值。这些值在跨维度上都是不可聚合
                .publishPercentiles(0.5,0.95)
                //publishPercentileHistogram-用于发布直方图,可用于计算跨纬度的百分比近似值聚
                .publishPercentileHistogram()
                //控制publishPercentileHistogram桶的数量,同时控制基础HdrHistogram结构的准确性和内存占用量。
                .minimumExpectedValue(Duration.ofMillis(1))
                .maximumExpectedValue(Duration.ofMinutes(3))
                .sla(Duration.ofMillis(10), Duration.ofMillis(50), Duration.ofMillis(100), Duration.ofMillis(300), Duration.ofMillis(1000))
                .tags(Tags.of("url", request.getRequestURI(), "method", request.getMethod(), "code", String.valueOf(response.getStatus())))
                .register(meterRegistry);

        //Timer的使用可以基于它的内部类Timer.Sample,通过start和stop两个方法记录两者之间的逻辑的执行耗时
        threadLocal.get().stop(histogramTimer);
        threadLocal.remove();
    }
}

Prometheus java客户端组装暴露数据 prometheus client library_java_05


Histogram会自动创建3个指标,分别为:

  • 事件发生总次数: basename_count
# 实际含义: 当前一共发生了23次http请求
http_req_histogram_seconds_count{application="springboot-prometheus",code="200",method="GET",url="/prometheus/test2",} 23.0
  • 所有事件产生值的大小的总和: basename_sum
# 实际含义: 发生的23次http请求总的响应时间为 0.0174146秒
http_req_histogram_seconds_sum{application="springboot-prometheus",code="200",method="GET",url="/prometheus/test2",} 0.0174146
  • 事件产生的值分布在bucket中的次数: basename_bucket{le=”上包含”}
# 在总共23次请求当中。http请求响应时间 <=0.01 秒 的请求次数为23
http_req_histogram_seconds_bucket{application="springboot-prometheus",code="200",method="GET",url="/prometheus/test2",le="0.01",} 23.0
http_req_histogram_seconds_bucket{application="springboot-prometheus",code="200",method="GET",url="/prometheus/test2",le="0.05",} 23.0
http_req_histogram_seconds_bucket{application="springboot-prometheus",code="200",method="GET",url="/prometheus/test2",le="0.1",} 23.0
http_req_histogram_seconds_bucket{application="springboot-prometheus",code="200",method="GET",url="/prometheus/test2",le="0.3",} 23.0
http_req_histogram_seconds_bucket{application="springboot-prometheus",code="200",method="GET",url="/prometheus/test2",le="1.0",} 23.0
http_req_histogram_seconds_bucket{application="springboot-prometheus",code="200",method="GET",url="/prometheus/test2",le="+Inf",} 23.0
  1. Summary:摘要 客户端定义的数据分布统计图
  • 观察总和
  • 观察计数
  • 排名估计

Summary和Histogram非常类型相似,都可以统计事件发生的次数或者发小,以及其分布情况。Summary和Histogram都提供了对于事件的计数_count以及值的汇总_sum。 因此使用_count,和_sum时间序列可以计算出相同的内容,例如http每秒的平均响应时间。同时Summary和Histogram都可以计算和统计样本的分布情况,比如中位数,9分位数等等。其中 0.0<= 分位数Quantiles <= 1.0。不同在于Histogram可以通过histogram_quantile函数在服务器端计算分位数。 而Sumamry的分位数则是直接在客户端进行定义。因此对于分位数的计算。 Summary在通过PromQL进行查询时有更好的性能表现,而Histogram则会消耗更多的资源。相对的对于客户端而言Histogram消耗的资源更少

public class PrometheusMetricsInterceptor implements HandlerInterceptor {

    @Autowired
    private MeterRegistry meterRegistry;
   
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

      DistributionSummary summary = DistributionSummary
                .builder("response_size")
                .description("response_size") // 可选
                .baseUnit("bytes") // 可选 添加基本单元,可以最大程度的获取可移植性—对于某些监控系统而言,基本单元是命名规则的一部分。如果不设置,也不会有什么不利影响。
                .tags(Tags.of("url", request.getRequestURI(), "method", request.getMethod(), "code", String.valueOf(response.getStatus()))) // 可选
                .scale(100) // 可选 设置比例因子,这样在记录样本的时候都会乘以这个比例
                .register(meterRegistry);
 
      summary.record(response.getBufferSize());

    }
}

Prometheus java客户端组装暴露数据 prometheus client library_自定义_06

# 含义:当前http请求发生总次数为7次
response_size_bytes_count{application="springboot-prometheus",code="200",method="GET",url="/prometheus/test2",} 7.0
# 含义:这7次http请求的总响应体buffer.size为5734400.0
response_size_bytes_sum{application="springboot-prometheus",code="200",method="GET",url="/prometheus/test2",} 5734400.0