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

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

相关推荐
阿祖zu31 分钟前
芝士就是力量!开源私有化部署与 GitHub 双向同步的个人知识笔记 App
前端·后端·ios
不才不才不不才41 分钟前
Spring 源码系列(16): doDispatch 全流程——一次请求的主干链路
java·后端·spring
IT_陈寒1 小时前
SpringBoot自动配置失效?这个隐藏配置坑了我一整晚
前端·人工智能·后端
搜狐技术产品小编20231 小时前
告别“黑盒”与误判:如何用“多智能体对抗辩论”重构内容安全审核系统
后端·多agent
DantyWei1 小时前
controller注册及调用时机
后端
edwarddamon2 小时前
Spring Cloud 配置热更新与 Bean 代理机制梳理
java·后端
码事漫谈2 小时前
我用 Seed Evolving 做了个 AI 小说写作工具
后端
Raas1002 小时前
MAIGateway,魔芋企业级AI网关的FinAPI成本竞争力设计
java·后端·网关·api网关·魔芋ai·finapi·mai gateway
计算机魔术师3 小时前
传统基础架构 - 微服务
大数据·后端·微服务·架构·开发工具·编程语言
DantyWei3 小时前
kube-controller-manager的leader选举流程和策略
后端