Spring Boot 开发 -- 集成 Prometheus 进行高效监控

引言

随着微服务架构的流行,对服务的监控和管理变得尤为重要。Prometheus作为一个开源的监控和告警工具,以其强大的数据采集、存储和查询能力,受到了众多开发者的青睐。Spring Boot作为Java领域快速构建微服务的框架,与Prometheus的结合可以实现对Spring Boot应用的实时监控。本文将介绍如何使用Prometheus监控Spring Boot应用。

一、 Prometheus 简介

Prometheus 是一个开源的系统监控和警报工具包,它通过采集和存储指标(metrics),提供了强大的数据查询语言,可以帮助我们分析和理解应用程序的行为。Prometheus 的核心组件是 Prometheus Server,它负责采集监控指标并提供查询接口。

  • Prometheus 官网:https://prometheus.io/
  • 项目 github 地址:https://github.com/prometheus/prometheus

二、 Spring Boot Actuator

Spring Boot Actuator 是 Spring Boot 提供的一系列用于监控和管理 Spring Boot 应用的工具。它提供了许多端点(endpoints),例如 /health、/info、/metrics 等,这些端点可以公开应用的内部信息,如健康状态、配置信息和度量指标。

三、 集成 Prometheus 和 Spring Boot

要将 Prometheus 与 Spring Boot 应用集成,我们需要执行以下步骤:

3.1 添加依赖

首先,将 Spring Boot Actuator 和 Micrometer Prometheus Registry 添加到项目的依赖中。

  • Actuator 提供了一系列内置端点,用于显示运行应用的性能信息,如健康状况、指标等。

  • Micrometer Prometheus registry 会将这些指标格式化为 Prometheus 可读格式。

    org.springframework.boot spring-boot-starter-actuator 2.7.15 io.micrometer micrometer-registry-prometheus 1.9.14

3.2 配置 Actuator

接下来,application.yml 文件中配置 Actuator 以暴露 Prometheus 端点:

management:
  endpoints:
    web:
      exposure:
        include: '*'
  metrics:
    export:
      prometheus:
        enabled: true

其他配置属性:

  • management.endpoints.web.exposure.include=* # 暴露所有端点
  • management.metrics.export.prometheus.enabled=true #启用Prometheus导出器
  • management.endpoints.web.base-path=“/status” # 将/actuator/xxx修改为/status/xxx,防止被猜到
  • management.endpoints.server.request.metric-name=“application:request” # 自定义接口指标名
  • management.server.port=10001 #指定端口,默认跟server.port一样,可以防止被猜到

3.3 启动 Prometheus

下载并运行 Prometheus Server。可以从 Prometheus官网 下载适用于您操作系统的版本。

  • docker 方式 拉取安装镜像文件

    docker pull prom/prometheus

  • 创建并运行容器

    docker run --name prometheus -d -p 9090:9090 prom/prometheus

对于需要自定义配置的部署,可以将主机上的自定义 prometheus.yml 文件挂载到容器中:

docker run -d --name prometheus -p 9090:9090 -v D:developsoftdockerDockerDesktopWSLdataprometheusprometheus.yml:/etc/prometheus/prometheus.yml prom/prometheus
  • 浏览器访问 http://localhost:9090
    在这里插入图片描述

3.4 配置 Prometheus

  • 拷贝 prometheus.yml 文件到宿主机 :

    docker cp prometheus:/etc/prometheus/prometheus.yml D:developsoftdockerDockerDesktopWSLdataprometheusprometheus.yml

  • 修改 Prometheus 的配置文件 prometheus.yml,添加 Spring Boot 应用作为目标(target):

    scrape_configs:

    • job_name: ‘spring-boot-application’
      metrics_path: ‘prometheus-demo/actuator/prometheus’
      scrape_interval: 15s
      static_configs:
      • targets: [‘192.168.10.108:8091’]

如上,localhost:8080 应替换为 Spring Boot 应用相应的 宿主机 和端口

  • scrape_interval 指定 Prometheus 从应用中抓取指标的频率。
  • metrics_path 中 prometheus-demo为 springboot 应用的contextPath,/actuator/prometheus 为默认路径

3.5 访问监控数据

启动 Spring Boot 应用后,Prometheus 将定期从 /actuator/prometheus 端点抓取指标数据。

四、 Grafana 可视化指标

虽然 Prometheus 提供了基本的数据查询和展示功能,但通常我们会使用 Grafana 来实现更丰富的数据可视化。Grafana 支持 Prometheus 作为数据源,可以方便地创建仪表板展示监控数据。

4.1 安装 Grafana

  • docker 方式 拉取安装镜像文件

    docker pull grafana/grafana

  • 创建并运行容器

    docker run -d --name=grafana -p 3000:3000 grafana/grafana

  • 浏览器访问 http://localhost:3000

默认用户名/密码:admin/admin

在这里插入图片描述

4.2 配置数据源

在 Grafana 中配置 Prometheus 作为数据源,指向 Prometheus Server 的地址。
在这里插入图片描述

4.3 创建仪表板

创建新的仪表板,并添加面板来展示关心的监控指标。
在这里插入图片描述

  1. 点击左侧边栏的图标,选择 “Dashboard”,创建一个新的仪表盘。
  2. 在仪表盘中添加一个全新的面板。在这里,选择要显示的指标,决定可视化类型(图表、仪表、表格等),并自定义面板的外观。
  3. 选择 Prometheus 记录源,并使用 Prometheus 查询语言 (PromQL) 选择希望可视化的指标。例如,要显示 HTTP 请求的消耗,可以使用 price(http_requests_total[5m]) 这样的查询。
  4. 保存面板和仪表盘。可以创建尽可能多的面板,以可视化 Spring Boot 应用中的特殊指标。

五、 自定义监控指标

除了 Spring Boot Actuator 提供的内置指标,我们还可以通过 Micrometer 添加自定义监控指标,以监控特定的业务逻辑或性能瓶颈。

5.1 添加自定义指标

在 Spring Boot 应用中,使用 Micrometer 的 API 添加自定义指标:

import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.MeterRegistry;

@RestController
public class CustomMetricsController {
    private final Counter ordersCounter;

    public CustomMetricsController(MeterRegistry registry) {
        this.ordersCounter = Counter.builder("orders_count")
                                    .description("The total number of orders")
                                    .register(registry);
    }

    @GetMapping("/order")
    public String createOrder() {
        ordersCounter.increment();
        return "Order created";
    }
}

5.2 在 Grafana 中展示自定义指标

在 Grafana 中,可以像展示其他 Prometheus 指标一样展示自定义指标。

参考文献:

  1. https://spring4all.com/forum-post/6888.html
  2. prometheus 官方文档: https://prometheus.io/docs/prometheus/latest/getting_started/
  3. https://www.jianshu.com/p/9480634bbfeb
  4. https://springdoc.cn/spring-boot-prometheus/
  5. https://springdoc.cn/spring-boot-actuators/

Springboot整合Prometheus-自定义指标_gauge.builder-CSDN博客

依赖

// actuator依赖提供指标
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-actuator</artifactId>
  <version>2.6.3</version>
</dependency>

// 将指标转换成prometheus可使用的格式
<dependency>
  <groupId>io.micrometer</groupId>
  <artifactId>micrometer-registry-prometheus</artifactId>
  <version>1.8.2</version>
</dependency>
ps:
1、以下指标名称都为prometheus接收的指标名称
2、http://ip:port/actuator/prometheus 此为prometheus默认开放接口,输出prometheus格式
3、http://localhost:11130/actuator/metrics 此为actuator的指标接口,输出当前开放的指标

SpringBoot自定义指标

注入依赖

要注册自定义指标,需要注入MeterRegistry到你的组件

import io.micrometer.core.instrument.MeterRegistry;

@Autowired
private MeterRegistry meterRegistry;

Meters(指标)

Counter(计数器)

Counter是一种比较简单的Meter,它是一种单值的度量类型,或者说是一个单值计数器。Counter接口允许使用者使用一个固定值(必须为正数)进行计数。准确来说:Counter就是一个增量为正数的单值计数器。这里举个很简单的使用例子:
1、使用meterRegistry创建指标

Counter counter = meterRegistry.counter("http.request", "createOrder", "/order/create");
counter.increment();

2、使用Counter内部建造器类Counter.Builder去实例化Counter

Counter counter = Counter.builder("name")  //名称
                .baseUnit("unit") //基础单位
                .description("desc") //描述
                .tag("tagKey", "tagValue")  //标签
                .register(meterRegistry);//绑定的MeterRegistry
        counter.increment();

3、FunctionCounterCounter的特化类型,它把计数器数值增加的动作抽象成接口类型ToDoubleFunction,这个接口JDK1.8中对于Function的特化类型接口。FunctionCounter的使用场景和Counter是一致的。FunctionCounter使用的一个明显的好处是,我们不需要感知FunctionCounter实例的存在,实际上我们只需要操作作为FunctionCounter实例构建元素的AtomicInteger实例即可,这种接口的设计方式在很多框架里面都可以看到。

AtomicInteger n = new AtomicInteger(0);
        //这里ToDoubleFunction匿名实现其实可以使用Lambda表达式简化为AtomicInteger::get
        FunctionCounter.builder("functionCounter", n, AtomicInteger::get)
                .baseUnit("unit") //基础单位
                .description("desc") //描述
                .tag("tagKey", "tagValue")  //标签
                .register(meterRegistry);
        n.incrementAndGet(); // 仅对自增api有效
使用场景:

Counter的作用是记录XXX的总量或者计数值,适用于一些增长类型的统计,例如下单、支付次数、Http请求总量记录等等,通过Tag可以区分不同的场景,对于下单,可以使用不同的Tag标记不同的业务来源或者是按日期划分,对于Http请求总量记录,可以使用Tag区分不同的URL。

gauge(量规)

Gauge是获取当前度量记录值的句柄,也就是它表示一个可以任意上下浮动的单数值度量Meter。Gauge通常用于变动的测量值,测量值用ToDoubleFunction参数的返回值设置,如当前的内存使用情况,同时也可以测量上下移动的”计数”,比如队列中的消息数量。Gauge的典型使用场景是用于测量集合或映射的大小或运行状态中的线程数。Gauge一般用于监测有自然上界的事件或者任务,而Counter一般使用于无自然上界的事件或者任务的监测,所以像Http请求总量计数应该使用Counter而非GaugeMeterRegistry中提供了一些便于构建用于观察数值、函数、集合和映射的Gauge相关的方法:
1、使用meterRegistry创建指标

List<String> list = registry.gauge("listGauge", Collections.emptyList(), new ArrayList<>(), List::size); 
List<String> list2 = registry.gaugeCollectionSize("listSize2", Tags.empty(), new ArrayList<>()); 
Map<String, Integer> map = registry.gaugeMapSize("mapGauge", Tags.empty(), new HashMap<>());

上面的三个方法通过MeterRegistry构建Gauge并且返回了集合或者映射实例,使用这些集合或者映射实例就能在其size变化过程中记录这个变更值。更重要的优点是,我们不需要感知Gauge接口的存在,只需要像平时一样使用集合或者映射实例就可以了。此外,Gauge还支持java.lang.Number的子类,java.util.concurrent.atomic包中的AtomicInteger和AtomicLong,还有Guava提供的AtomicDouble:

AtomicInteger n = registry.gauge("numberGauge", new AtomicInteger(0));
n.set(1);
n.set(2);

2、建造器流式创建:

List<Integer> list = new ArrayList<>();
//一般我们不需要操作Gauge实例
Gauge gauge = Gauge.builder("gauge", list, List::size)
         .baseUnit("unit") //基础单位
         .description("desc") //描述
         .tag("tagKey", "tagValue")  //标签
         .register(meterRegistry);
使用场景:

1、有自然(物理)上界的浮动值的监测,例如物理内存、集合、映射、数值等。
2、有逻辑上界的浮动值的监测,例如积压的消息、(线程池中)积压的任务等,其实本质也是集合或者映射的监测。

timers(计时器)

Time适用于记录耗时比较短的事件的执行时间,通过时间分布展示事件的序列和发生频率。所有的Timer的实现至少记录了发生事件的数量和这些事件的总耗时,从而生成一个时间序列。Timer的基本单位基于服务端的指标而定,但是实际上我们不需要过于关注Timer的基本单位,因为Micrometer在存储生成的时间序列的时候会自动选择适当的基本单位。Timer接口提供的常用方法如下:
1、使用meterRegistry创建指标

		Timer print1000 = meterRegistry.timer("print1000");
		print1000.record(()->{ 
			// 传入任务,并自行执行
		     for (int i = 0; i < 1000; i++) {
		         System.out.println(i);
		     }
		 });
        Timer print1000 = meterRegistry.timer("print1000");
		// 只定义了指标以及任务,不自行执行
        Runnable wrap = print1000.wrap(() -> {
            for (int i = 0; i < 1000; i++) {
                System.out.println(i);
            }
        });
        // 执行后指标才有值
        wrap.run();

2、建造器流式创建:

 Timer print1000 = Timer.builder("print1000")
                .description("desc") //描述
                .tag("tagKey", "tagValue")  //标签
                .register(meterRegistry);

        Runnable wrap = print1000.wrap(() -> {
            for (int i = 0; i < 1000; i++) {
                System.out.println(i);
            }
        });
        wrap.run();

3、Timer的使用还可以基于它的内部类Timer.Sample,通过startstop两个方法记录两者之间的逻辑的执行耗时。

        Timer.Sample sample = Timer.start(meterRegistry);
        for (int i = 0; i < 1000; i++) {
            System.out.println(i);
        }
        sample.stop(meterRegistry.timer("print1000"));

4、FunctionTimerTimer的特化类型,它主要提供两个单调递增的函数(其实并不是单调递增,只是在使用中一般需要随着时间最少保持不变或者说不减少),一个用于计数的函数和一个用于记录总调用耗时的函数。

官方文档中的例子如下:

IMap<?, ?> cache = ...; // 假设使用了Hazelcast缓存
registry.more().timer("cache.gets.latency", Tags.of("name", cache.getName()), cache,
    c -> c.getLocalMapStats().getGetOperationCount(),  //实际上就是cache的一个方法,记录缓存生命周期初始化的增量(个数)
    c -> c.getLocalMapStats().getTotalGetLatency(),  // Get操作的延迟时间总量,可以理解为耗时
    TimeUnit.NANOSECONDS
);

countFunction用于统计事件个数,totalTimeFunction用于记录执行总时间,实际上两个函数都只是Function函数的变体,还有一个比较重要的是总时间的单位totalTimeFunctionUnit。简单的使用方式如下:

 		//这个是为了满足参数,暂时不需要理会
        Object holder = new Object();
        AtomicLong totalTimeNanos = new AtomicLong(0);
        AtomicLong totalCount = new AtomicLong(0);
        FunctionTimer.builder("functionTimer",
                        holder,
                        p -> totalCount.get(), // 统计事件个数
                        p -> totalTimeNanos.get(), // 记录执行总时间
                        TimeUnit.NANOSECONDS) // 总时间的单位
                .description("desc") //描述
                .tag("tagKey", "tagValue")  //标签
                .register(meterRegistry);
        totalTimeNanos.addAndGet(10000000);
        totalCount.incrementAndGet();
使用场景:

1、记录指定方法的执行时间用于展示。
2、记录一些任务的执行时间,从而确定某些数据来源的速率,例如消息队列消息的消费速率等。

long task timer(长任务定时器)

LongTaskTimer也是一种Timer的特化类型,主要用于记录长时间执行的任务的持续时间,在任务完成之前,被监测的事件或者任务仍然处于运行状态,任务完成的时候,任务执行的总耗时才会被记录下来,而无返回的Timer记录的是任务启动间隔时间。在Spring应用中,可以简单地使用@Scheduled@Timed注解,基于spring-aop完成定时调度任务的总耗时记录:
1、@Timed注解使用

@Timed(value = "aws.scrape", longTask = true)
@Scheduled(fixedDelay = 360000)
void scrapeResources() {
    //这里做相对耗时的业务逻辑
}

2、使用meterRegistry创建指标

 LongTaskTimer print1000 = meterRegistry.more().longTaskTimer("print1000");
        print1000.record(()->{
            // 传入任务,并自行执行
            for (int i = 0; i < 1000; i++) {
                System.out.println(i);
            }
        });

3、建造器流式创建:

        LongTaskTimer print1000 = LongTaskTimer.builder("print1000")
                .description("desc") //描述
                .tag("tagKey", "tagValue")  //标签
                .register(meterRegistry);
        print1000.record(()->{
            // 传入任务,并自行执行
            for (int i = 0; i < 1000; i++) {
                System.out.println(i);
            }
        });
使用场景:

LongTaskTimer适合用于长时间持续运行的事件耗时的记录,例如相对耗时的定时任务。

distribution summary(分布式摘要)

Summary主要用于跟踪事件的分布,在Micrometer中,对应的类是Distribution Summary(分发摘要)。它的使用方式和Timer十分相似,但是它的记录值并不依赖于时间单位。常见的使用场景:使用Distribution Summary测量命中服务器的请求的有效负载大小。
1、使用meterRegistry创建指标

DistributionSummary summer = meterRegistry.summary("summer");
summer.record(0.25);

2、建造器流式创建:

DistributionSummary register = DistributionSummary.builder("summer")
           .description("desc") //描述
           .tag("tagKey", "tagValue")  //标签
           .register(meterRegistry);
   register.record(0.25);
使用场景

不依赖于时间单位的记录值的测量,例如服务器有效负载值,缓存的命中率等。

常用标签

常用标签可以对所有指标进行添加通用标签

management.metrics.tags.application=${spring.application.name}
management.metrics.tags.tagKey=tagValue

禁用指标

下面以http.server.requests示例禁用

management.metrics.enable.http.server.requests=false
posted @ 2025-01-09 10:53  CharyGao  阅读(2370)  评论(0)    收藏  举报