前言
最近有个老项目要接入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后出现下面截图,那么恭喜你,配置成功
部署生产环境后,将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进行配置,这类文章一堆,比较简单了,我就不写了。