2023-11-19T14:24:18.406421902-suvh.jpeg

准备工作

Pom.xml依赖

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
	<groupId>io.micrometer</groupId>
	<artifactId>micrometer-registry-prometheus</artifactId>
	<scope>runtime</scope>
</dependency>

springboot配置文件

management.server.port=8081
management.metrics.tags.application=${spring.application.name}
management.metrics.export.prometheus.enabled=true
management.endpoints.web.exposure.include=prometheus

启动程序,访问:http://127.0.0.1:8081/actuator/prometheus 可以看到指标都是按照Prometheus的格式输出的,默认的监控指标有很多jvm、http、tomcat、logback等;以上就是准备工作,如果有问题,请从头排查是否配置错误。

自定义Prometheus监控指标

利用拦截器实现所有HTTP接口的监控

利用HTTP的拦截器添加Prometheus的监控指标,首先创建一个拦截器CustomInterceptor 实现HandlerInterceptor接口,然后重写里面的 前置处理、后置处理;

import io.micrometer.core.instrument.*;
import org.jetbrains.annotations.NotNull;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.*;
import java.util.concurrent.TimeUnit;

public class CustomInterceptor implements HandlerInterceptor {

    private static final String CUSTOM_KPI_NAME_TIMER = "custom.kpi.timer"; //耗时
    private static final String CUSTOM_KPI_NAME_COUNTER = "custom.kpi.counter"; //api调用次数。
    private static final String CUSTOM_KPI_NAME_SUMMARY = "custom.kpi.summary"; //汇总率
    private static MeterRegistry registry;
    private long startTime;
    private final GaugeNumber gaugeNumber = new GaugeNumber();

    void getRegistry(){
        if(registry == null){
            //这里使用的时SpringUtil获取Bean,没有用@Autowired注解,Autowired会因为加载时机问题导致拿不到;
            registry = SpringUtil.getBean(MeterRegistry.class);
        }
    }

    @Override
    public boolean preHandle(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull Object handler) throws Exception {
        getRegistry();
        //记录接口开始调用的时间
        startTime = System.currentTimeMillis();
        return HandlerInterceptor.super.preHandle(request, response, handler);
    }

    @Override
    public void postHandle(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull Object handler, ModelAndView modelAndView) throws Exception {
        HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, @NotNull Object handler, Exception ex) throws Exception {
        //统计调用次数
        registry.counter(CUSTOM_KPI_NAME_COUNTER,"uri", request.getRequestURI(), "method", request.getMethod(),
                "status", String.valueOf(response.getStatus()), "exception", ex == null ? "" : ex.getMessage(), "outcome", response.getStatus() == 200 ? "SUCCESS" : "CLIENT_ERROR").increment();
        //统计单次耗时
        registry.timer(CUSTOM_KPI_NAME_TIMER,"uri", request.getRequestURI(), "method", request.getMethod(),
                "status", String.valueOf(response.getStatus()), "exception", ex == null ? "" : ex.getMessage(), "outcome", response.getStatus() == 200 ? "SUCCESS" : "CLIENT_ERROR").record(System.currentTimeMillis() - startTime, TimeUnit.MILLISECONDS);
        //统计调用成功率,根据过滤Counter对象,获取计数
        Collection meters = registry.get(CUSTOM_KPI_NAME_COUNTER).tag("uri", request.getRequestURI()).tag("method", request.getMethod()).meters();
        double total = 0;
        double success = 0;
        for (Meter meter : meters) {
            Counter counter = (Counter) meter;
            total += counter.count();
            String status = meter.getId().getTag("status");
            if (status != null && status.equals("200")) {
                success += counter.count();
            }
        }
        //保存对应的成功率到Map中
        String key = request.getMethod() + request.getRequestURI();
        gaugeNumber.setPercent(key, success / total * 100L);
        registry.gauge(CUSTOM_KPI_NAME_SUMMARY, Tags.of("uri", request.getRequestURI(), "method", request.getMethod()), gaugeNumber, value -> value.getPercent(key));
        HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
    }
    // gauge监控某个对象,所以用内部类替代,然后根据tag标签区分对应的成功率;key 为 method + uri
    static class GaugeNumber {
        Map map = new HashMap<>();

        public Double getPercent(String key) {
            return map.get(key);
        }

        public void setPercent(String key, Double percent) {
            map.put(key, percent);
        }
    }
}

注册自定义拦截器给Spring

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class CustomWebConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new CustomInterceptor()).addPathPatterns("/**");
    }
}

SpringUtil.getBean


import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

@Component
public class SpringUtil implements ApplicationContextAware {

    private static ApplicationContext applicationContext;
    @Override
    public void setApplicationContext(ApplicationContext applicationContextParam) throws BeansException {
        applicationContext=applicationContextParam;
    }
    public static Object getObject(String id) {
        Object object = null;
        object = applicationContext.getBean(id);
        return object;
    }
    public static  T getObject(Class tClass) {
        return applicationContext.getBean(tClass);
    }

    public static Object getBean(String tClass) {
        return applicationContext.getBean(tClass);
    }

    public static  T getBean(String str,Class tClass) {
        return (T)applicationContext.getBean(str);
    }

    public static  T getBean(Class tClass) {
        return applicationContext.getBean(tClass);
    }
}