前言

最近有个老项目要接入prometheus,通过grafana图形化界面展示监控信息,上网查了接入方式,结果遇到一些问题,有些是springboot版本的原因,一些是公司平台本身的问题,针对这次接入监控,总结一些经验,供大家参考。

正文:

由于第一次接入prometheus,所以上网搜索springboot如何接入,看了几篇文章写的,比价简单,当时觉得这玩意so easy,太没意思了,结果是啪啪的打脸。按照网上配置,发现几个问题,首先是配置参数提示不存在,其次项目启动后访问ip:port:/metrics数据不对。这个时候意识到没有想象的那么容易,可能是版本不兼容。查看了本项目用到的springboot的版本是1.5.8,然后通过springboot的版本关键字再次去搜索,发现真是,springboot 1.5.x与2.x的接入方式不同。再次按照文档接入,访问ip:port:/prometheus,成功访问到上报数据,注意这里的访问路径发生了改变,这也导致与公司平台相冲突,引发需要映射访问路径的问题。下面主要讲springboot 1.5.x与 2.x接入prometheus的方式,以及映射访问路径的方式。

SpringBoot 1.5.x接入方式:

maven依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
    <version>${spring.boot.version}</version>
</dependency>

<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-registry-prometheus</artifactId>
    <version>1.1.3</version>
</dependency>
<dependency>
   <groupId>io.micrometer</groupId>
   <artifactId>micrometer-spring-legacy</artifactId>
   <version>1.1.3</version>
</dependency>

application.yml配置

#暴露端口9999(此端口需不同于rest端口),如端口冲突可更换,也可以不单独配置端口,那么访问的和rest访问同一个端口
management:
  security:
    enabled: false
  port: 9999
#所有端点关闭,只开放prometheus端点
endpoints:
  enabled: false
  prometheus:
    enabled: true

项目启动后,访问127.0.0.1:9999/prometheus后出现下面截图,那么恭喜你,配置成功

springboot prometheus druid指标 springboot整合prometheus_数据

部署生产环境后,将ip:port 以及 访问路径/prometheus配置到prometheus的配置文件,重启后prometheus会定时拉去数据,下面是我在本地安装的prometheus配置的参数,在prometheus.yml文件中配置下面参数,供参考

# my global config

global:

  scrape_interval: 15s # Set the scrape interval to every 15 seconds. Default is every 1 minute.

  evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute.

  # scrape_timeout is set to the global default (10s).


# Alertmanager configuration

alerting:

  alertmanagers:

    - static_configs:

        - targets:

          # - alertmanager:9093


# Load rules once and periodically evaluate them according to the global 'evaluation_interval'.

rule_files:

  # - "first_rules.yml"

  # - "second_rules.yml"



# A scrape configuration containing exactly one endpoint to scrape:

# Here it's Prometheus itself.

scrape_configs:

  # The job name is added as a label `job=<job_name>` to any timeseries scraped from this config.

  - job_name: "prometheus"

    # metrics_path defaults to '/metrics'

    # scheme defaults to 'http'.

    static_configs:

      - targets: ["localhost:9090"]


  - job_name: "springboot-actuator"

    metrics_path: '/prometheus'

    scrape_interval: 5s # 间隔

    static_configs:

      - targets: ['127.0.0.1:8080']

我是新增了一个job_name,起名 springboot-actuator,在targets中填入你生产环境ip和端口,注意如果访问路径相同,targets中可以加多个ip和端口。重启prometheus服务后,就可以在prometheus查看到对应的指标。这里主要说的是配置,代码层面我们统一在下面讲。

可能对于你公司来说,到这里的接入已经完成了,剩下的就是加入监控代码,但是我们公司平台比较特殊,我们的prometheus是公司统一平台,抓取数据的路径是统一的,都是/metrics,而且是写死的,不能更改,这个时候就要修改暴露的访问路径,也就是将/prometheus映射成/metrics,但是springboot1.5.x的版本不支持映射路径,2.x版本通过配置参数是支持的。这个时候我们咋办呢,想到一个办法是在过滤器中,增加判断,如果是/metrics则转发到/prometheus上,这能解决了吗?结果是没问题的,如果各位小伙伴有其他的方法,欢迎在评论区留言,下面是filter的代码,仅供参考:

public class PrometheusFilter extends OncePerRequestFilter {

@Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
    // 尝试获取真实IP
      if ("/metrics".equals(httpServletRequest.getRequestURI())){
        httpServletRequest.getRequestDispatcher("/prometheus").forward(httpServletRequest,httpServletResponse);
    }
}

将该过滤器注册成bean

@Configuration
public class FilterConfig {

@Bean
public FilterRegistrationBean PrometheusFilter() {
    FilterRegistrationBean registration = new FilterRegistrationBean();
    registration.setFilter(new PrometheusFilter());
    registration.addUrlPatterns("/metrics");
    registration.addInitParameter("paramName", "paramValue");
    registration.setName("PrometheusFilter");
    registration.setOrder(Integer.MAX_VALUE);
    return registration;
}

}

这样当我们访问/metrics路径时,会进入该过滤器,访问其他路径不会进入,以后这里路径可以扩展,在过滤器中增加一些逻辑,例如判断访问ip。如果非公司ip则返回错误,也相当于做了一层权限控制。

Springboot2.x的接入方式:

maven依赖

<dependency>

  <groupId>org.springframework.boot</groupId>

  <artifactId>spring-boot-starter-actuator</artifactId>

</dependency>

<dependency>

  <groupId>io.micrometer</groupId>

  <artifactId>micrometer-registry-prometheus</artifactId>

</dependency>

application.yml文件

#暴露端口9999(此端口需不同于rest端口),如端口冲突可更换其他端口

management:

  endpoints:

    web:

      exposure:

        include: health,prometheus

      base-path: /

      path-mapping:

        prometheus: metrics

这里有个配置参数 path-mapping.prometheus 支持自定义暴露的路径,例如将其修改成/metrics,就无需像springboot1.5.x添加过滤器来处理路径映射,特别好。剩下的步骤就和springboot1.5.x没有啥区别了。

到了这里只剩下一件事,在代码里增加监控,我们通过切面添加统一的监控,统计成功、失败的数量和接口相应时间。

在启动类中注入公共tab的指标bean,例如项目的application参数

@Bean
MeterRegistryCustomizer meterRegistryCustomizer(@Value("${spring.application.name}")String applicationName, MeterRegistry meterRegistry) {
   return meterRegistry1 -> {
      meterRegistry.config().commonTags("application", applicationName);
   };

spring.application.name参数是在application.yml文件中配置的项目名称,这里就不沾代码了

注入指标模版,因为监控的是请求数量,是累加的,这里用到Counter

@Component
public class CounterMetrics {
    @Autowired
    private CollectorRegistry collectorRegistry;

    /**
     * <p>
     * http请求总计数
     * </p>
     *
     * @return io.prometheus.client.Counter
     * @author Mr Zan
     * @since 2022/2/16 4:34 下午
     */
    @Bean
    public Counter httpRequestTotalCounterCollector() {
        return Counter.build().name("http_request_total").labelNames("application","url", "method", "code").help("http请求总计数").register(collectorRegistry);
    }

}

从模版里得到一些信息,指标名称(http_request_total)监控项(url、method、code)

通过切面来统一进行监控

@Component
@Aspect
public class GMonitorAspect extends BaseAspect {
    private static final Logger logger = LoggerFactory.getLogger(GMonitorAspect.class);
    @Value("${spring.application.name}")
    private String application;
    @Resource
    @Qualifier("httpRequestTotalCounterCollector")
    private Counter httpRequestTotalCounterCollector;
    @Resource
    private ServletUtil servletUtil;

    @Around("pointcut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        Histogram.Timer timer = null;
        HttpServletRequest request = servletUtil.getRequest();
        String requestURI = request.getRequestURI();
        String method = request.getMethod();
        Object result;
        String status = "S00000";
        try {
            Object[] args = point.getArgs();
            if (args == null || args.length == 0) {
                result = point.proceed();
            } else {
                result = point.proceed(args);
            }
            if (result instanceof ResponseData) {
                status = ((ResponseData) result).getCode();
            }
        } catch (Throwable e) {
            logger.error("MonitorLog record error ", e);
            status = 501 + "";
            result = ResponseData.createFailResult("执行异常");
        } finally {
            httpRequestTotalCounterCollector.labels(application,requestURI, method, status).inc();
        }
        return result;
    }

}

这里有个切面父类BaseAspect ,定义了在哪些类上进行切面

public abstract class BaseAspect {

      @Pointcut("execution(public * com.xxx.web.*.*(..))")
    public void pointcut() {}

}

注意一点,com.xxx.web.* 是你切面生效的路径,

ServletUtil类是获取请求和相应的工具类,供大家参考

@Component
public class ServletUtil {

    /**
     * 获取HttpRequestServlet对象.
     * @return HttpServletRequest
     * */
    public HttpServletRequest getRequest() {
       ServletRequestAttributes servletRequestAttributes =  (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
       return servletRequestAttributes.getRequest();
    }

    /**
     * 获取HttpRequestServlet对象.
     * @return HttpServletResponse
     * */
    public HttpServletResponse getResponse() {
        ServletRequestAttributes servletRequestAttributes =  (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
        return servletRequestAttributes.getResponse();
    }

代码方面几乎就结束了,剩下的工作就是根据上报的指标,通过grafana进行配置,这类文章一堆,比较简单了,我就不写了。