实践干货 - Prometheus+Grafana实现SpringBoot服务监控

前言

在之前的 文章 中,我们有提到过如何基于Prometheus+Grafana的搭建去实现Redis,MySQL这类的监控。那么本文我们将会讲解如何实现基于SpringBoot这类Java服务的监控。

SpringBoot端配置

首先你需要下载好Grafana和Prometheus,并且安装相关环境。具体的安装步骤在本文就不做重复提及了。

接着我们需要构建一套SpringBoot的服务,相关依赖内容如下所示:

xml 复制代码
<properties>
        <springboot.web.version>2.4.9</springboot.web.version>
        <prometheus.version>1.10.9</prometheus.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
            <version>${springboot.web.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>${springboot.web.version}</version>
        </dependency>
        <dependency>
            <groupId>io.micrometer</groupId>
            <artifactId>micrometer-registry-prometheus</artifactId>
            <version>${prometheus.version}</version>
        </dependency>
    </dependencies>

接着我们可以写一个简单的SpringBoot接口服务:

java 复制代码
@RestController
@RequestMapping("/test")
public class TestController {


    @GetMapping("/success")
    public String success() {
        System.out.println("success");
        return "success";
    }

    @GetMapping("/echo1")
    public String echo1() {
        return "echo";
    }

    @GetMapping("/echo2")

    public String echo2() {
        return "echo";
    }

    @GetMapping("/echo3")
    public String echo3() {
        int k = 1/0;
        return "echo";
    }

    @GetMapping("/echo4")
    public String echo() {
        return "echo";
    }
}

到这里,我们所做的东西都是非常简单的步骤,然后就需要我们配置好application.yaml配置文件,并且启动SpringBoot应用:

yml 复制代码
spring:
  jmx:
    enabled: true
  application:
    name: qiyu-live-monitor-springboot-web
management:
  endpoints:
    web:
      exposure:
        include:
          - health
          - prometheus

  metrics:
    tags:
      application: ${spring.application.name}

注意,这里我只开放了health检测和prometheus的端口,用于给监控平台读取,其他端口尽量不要选择开放,注意接口安全性。

启动了之后,可以尝试请求:http://localhost:8080/actuator/ 接口,你会看到当前SpringBoot所有对外暴露的actuator类型接口地址。

接着我们点开如下地址:http://localhost:8080/actuator/prometheus。

你会看到有一串之前没有遇见过的数据内容:

这些数据其实就是我们今天要讲的指标数据,具体如何使用,我们后边会提及。

好了,到这里,SpringBoot服务的配置基本就可以结束了,下边来看Prometheus和Grafana这边的配置。

Prometheus和Grafana配置

首先,我们启动Prometheus和Grafana的时候,大多数情况都是需要依赖一份yaml的规则文件的,而基于它们的监控特点,也是要修改这份文件的。

这里需要注意的点是,Prometheus主要负责通过Http接口去拉取我们业务服务的指标数据。也就是定期拉取上边我们SpringBoot依赖中的spring-boot-starter-actuator的接口。

也就是http://localhost:8080/actuator/prometheus接口中所返回的内容。

所以梳理下来,你会发现Prometheus的执行原理,是采用了拉模型的思路去实现的,各个业务服务只需要暴露一个地址,返回内部服务的指标信息(例如api执行信息,jvm信息等),然后Prometheus这边会定时去拉最新的数据,并且存储在自己的时许数据库中。如下图所示:

这一点的设计,和传统的其他监控平台设计思路不太一样,例如SkyWalking,Pinpoint这类监控平台,都是得引入一些agent包去做上报的动作,所以需要将监控拆解为client端和server端。

而Prometheus这套更多是希望业务服务不需要关心这部分的动作,只需开放接口等着它去采集即可。

好了,现在我们继续进入到实战当中,这里启动Promethus的时候,需要新增配置项:

perl 复制代码
global:  scrape_interval: 15s
scrape_configs:  - job_name: "prometheus"    static_configs:    - targets: ["localhost:9090"]
  - job_name: "node_exporter"    static_configs:    - targets: ["localhost:9100"]      labels:        instance: node_exporter   //这里的job_name可以自定义配置   - job_name: 'qiyu-live-monitor-springboot-web'    scrape_interval: 5s    metrics_path: '/actuator/prometheus'    static_configs:    //这里我们就简单配置一个静态ip,如果是集群环境,建议配置注册中心地址    - targets: ['127.0.0.1:8080']

接着启动prometheus:

ini 复制代码
prometheus --config.file=./prometheus.yml

启动之后,我们可以尝试访问以下地址:

bash 复制代码
http://localhost:3000/ Grafana地址http://localhost:9090/targets prometheus地址http://localhost:9100/metrics 采集点地址

如果一切正常的话,你应该可以看到以下几个页面:

http://localhost:3000/: 这个是Grafana的登录页面,默认是admin,admin账号。

http://localhost:9100/metrics: 这个地址你可以理解为是各个业务方的指标汇总展示地址,不过数据很杂,展示不友好。

http://localhost:9090/targets: 这个地址可以理解为是Promethus将采集回来的数据统统整理好了,方便我们使用可视化界面来进行管理。这里你仔细观察,还能看到SpringBoot服务采集回来的数据集合。

好了,到这里基本的数据已经整理好了,就剩下画图了。

Grafana绘图步骤

现在,我们有了指标数据后,可以在Grafana上去给它进行绘制了,先来给大家看看我的绘制效果:

这里我只是简单的实现了如何将指标汇集的效果,其实都是使用PromSQL去实现的,下边将SQL内容展示给大家看:

请求总数统计

累计请求总数,这块比较简单,用了一个sum函数,将默认的指标做了下累加:

java 复制代码
sum(http_server_requests_seconds_count{application=~"$application"})

异常总数统计

这块也是简单的做了个sum计算,只不过对于异常的判断,因为用的是默认的指标,不好做标准化,所以就用了exception字段去进行字符串匹配方式进行判断:

bash 复制代码
sum(http_server_requests_seconds_count{application=~"$application",exception!~"None"})

每秒HTTP接口并发统计

每秒接口并发量统计,其实是用了increase函数,它的底层就是求某个时间段内的增量数据,底层用的是一个可以重置的计数器去实现的。具体函数如下所示:

java 复制代码
increase(http_server_requests_seconds_count{application=~"$application"}[60s])

这里可以注意到,我在图中有圈起来一个叫做Legend的东西,这个东西比较容易忽略,它是用于标识统计图中每条折线的标签所用的,这里可以选择用自定义变量去渲染,不过自定义变量要使用双重花括号去表示。

每秒HTTP异常接口并发统计

这块其实和每秒并发统计时类似的,所以我就不做过多解释了,直接贴PromSQL:

java 复制代码
increase(http_server_requests_seconds_count{application=~"$application",exception!~"None"}[60s])

好了到这里基本上基于Prometheus的指标采集和绘图渲染功能就告一段落了,希望看到这里,你对整个步骤的实践有一定的了解。

自定义指标采集

其实前边的内容你会发现,大部分都是默认的指标采集,也就是说actuator接口默认返回了什么,Prometheus就只能采集什么。而且说实话,默认actuator返回的这些指标,实在是不够用,例如环境类型(uat,pre,grey,prod)缺失,响应状态码缺失等等,所以大多数时候,大家更愿意使用自定义指标采集的方案。

因此官方的jar包中也可以提供了相关的指标API去实现这类自定义的需求,下边时一份我的实践代码,可供大家学习参考:

基于SpringBoot的拦截器去统计各个uri接口的请求次数:

指标采集组件封装:

java 复制代码
package com.qiyu.monitor.web.config;

import io.prometheus.client.CollectorRegistry;
import io.prometheus.client.Counter;
import io.prometheus.client.Gauge;
import io.prometheus.client.Histogram;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

/**
 * @Author idea
 * @Date: Created in 09:48 2023/11/12
 * @Description
 */
@Component
public class PrometheusComponent implements ApplicationContextAware {

    /**
     * 请求总数
     */
    private Counter requestCounter;

    /**
     * 正在处理中的请求数
     */
    private Gauge duringRequestGauge;

    /**
     * 请求直方图
     */
    private Histogram requestHistogram;

    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        CollectorRegistry collectorRegistry = applicationContext.getBean(CollectorRegistry.class);
        requestCounter = Counter.build().name("demo_req_total").labelNames("uri", "method", "code")
                .help("请求总数").register(collectorRegistry);
    }

    public Counter counter() {
        return requestCounter;
    }
}

在拦截器中注入采集点:

java 复制代码
package com.qiyu.monitor.web.config;

import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * @Author idea
 * @Date: Created in 09:53 2023/11/12
 * @Description
 */
public class PrometheusInterceptor extends HandlerInterceptorAdapter {

    private PrometheusComponent prometheusComponent;

    public PrometheusInterceptor(PrometheusComponent prometheusComponent) {
        this.prometheusComponent = prometheusComponent;
    }

    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        return super.preHandle(request, response, handler);
    }

    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        String uri = request.getRequestURI();
        String method = request.getMethod();
        int status = response.getStatus();
        prometheusComponent.counter().labels(uri,method, String.valueOf(status)).inc();
        super.afterCompletion(request, response, handler, ex);
    }

}

最后要记得配置改拦截器:

java 复制代码
package com.qiyu.monitor.web.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * @Author idea
 * @Date: Created in 09:54 2023/11/12
 * @Description
 */
@Configuration
@ConditionalOnBean(PrometheusComponent.class)
public class WebMvcConfig implements WebMvcConfigurer {

    @Autowired
    protected PrometheusComponent prometheusComponent;

    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new PrometheusInterceptor(prometheusComponent)).addPathPatterns("/**");
    }
}

看到这里,你会发现,其实本质都非常简单,就是构建的api需要多谷歌搜索,熟悉下。

其实类似的功能,我们还可以扩展开来,例如:Kafka消费和发送的指标采集,MyBatis的指标采集,Redis的指标采集,Dubbo的指标采集,SpringCloud Feign的指标采集等等,还有非常多的中间件指标采集待大家去尝试。

希望本文能对你有所启发。

相关推荐
用户685453759776938 分钟前
同步成本换并行度:多线程、协程、分片、MapReduce 怎么选才不踩坑
后端
javaTodo1 小时前
Claude Code 记忆机制详解:从 CLAUDE.md 到 Auto Memory,六层体系全拆解
后端
LSTM971 小时前
使用 C# 和 Spire.PDF 从 HTML 模板生成 PDF 的实用指南
后端
JaguarJack1 小时前
为什么 PHP 闭包要加 static?
后端·php·服务端
BingoGo1 小时前
为什么 PHP 闭包要加 static?
后端
是糖糖啊2 小时前
OpenClaw 从零到一实战指南(飞书接入)
前端·人工智能·后端
百度Geek说2 小时前
基于Spark的配置化离线反作弊系统
后端
Java编程爱好者2 小时前
虚拟线程深度解析:轻量并发编程的未来趋势
后端
苏三说技术3 小时前
Spring AI 和 LangChain4j ,哪个更好?
后端