实践干货 - 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的指标采集等等,还有非常多的中间件指标采集待大家去尝试。

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

相关推荐
tan180°3 小时前
MySQL表的操作(3)
linux·数据库·c++·vscode·后端·mysql
优创学社24 小时前
基于springboot的社区生鲜团购系统
java·spring boot·后端
why技术4 小时前
Stack Overflow,轰然倒下!
前端·人工智能·后端
幽络源小助理4 小时前
SpringBoot基于Mysql的商业辅助决策系统设计与实现
java·vue.js·spring boot·后端·mysql·spring
ai小鬼头5 小时前
AIStarter如何助力用户与创作者?Stable Diffusion一键管理教程!
后端·架构·github
简佐义的博客5 小时前
破解非模式物种GO/KEGG注释难题
开发语言·数据库·后端·oracle·golang
Code blocks6 小时前
使用Jenkins完成springboot项目快速更新
java·运维·spring boot·后端·jenkins
追逐时光者6 小时前
一款开源免费、通用的 WPF 主题控件包
后端·.net