1. 前言
工作几个月了,但是工作的前3个月几乎没有工作内容,从国庆节后终于步入工作节奏,开始接一些需求了,老早就想督促自己去学习去记录,但是自制力太差了,希望自己可以慢慢的写博客、读书,取得一些进步,在半年后取得一些成果。
之前看了半天的spring security ,本想写一篇博客,结果因为太懒,一直没动笔,都忘光了。。
言归正传,前阵子leader给了我一个监控的需求,需要使用prometheus,因此我去调研了一下,然后在项目中使用。
2. prometheus简介
关于prometheus的介绍,网上有很多,我就不在此班门弄斧了,里面主要有counter、gauge、timer、summary和histogram五种度量方式,前三种比较好理解,后两种我理解的是对某一个值分布的统计,即横坐标不是时间轴。
3. gauge的使用
现在需求是这样的,将时间轴等距离均分,如间隔是一分钟,然后去监控每一分钟内发送到kafka中的消息数量。首先counter是不能使用的,因为counter的设定是只增不减,然后summary和histogram在我的理解中是对分布的统计和监控,所以也不能使用,因此我只好投机取巧的使用了gauge(投机取巧说的是:gauge的意义更在于是统计一个实时的值,反应一种动态变化)。
因为项目使用的是springboot2.x,为了方便,就结合actuator使用了,引入如下依赖(项目使用的是gradle,目前感觉没有maven好用)
compile group: 'org.springframework.boot', name: 'spring-boot-starter-actuator', version: '2.2.10.RELEASE'
compile group: 'io.micrometer', name: 'micrometer-registry-prometheus', version: '1.1.3'
3.1 定义gauge变量
要使用gauge,首先需要引入register,并在register中注册gauge,gauge的定义包括名字以及一个Number(当然还有其他的注册方式,如标签等,需要可以自行google),因为此处要统计某段时间内某个值的积累量,因此我做了如下定义
gaugeMap.put(gaugeName,registry.gauge(gaugeName,new AtomicLong(0));
放入map中的目的是为了之后容易操作。
然后我们可以使用该gauge对需要统计的量进行增量,如以下操作
send(){
kafkaTemplate.send(topic, data);
gaugeMap.get(gaugeName).incrementAndGet();
}
当然,监控操作最好是使用aop来进行,这样可以和业务代码分离。
3.2 统计该gauge变量在某一段时间内的积累值
首先我们要明白,prometheus收集监控信息时,不支持push操作,需要对配置的各个监控目标进行pull操作,因为我们要监控一分钟内的积累量,就需要将prometheus server端的拉取间隔配置为60s,接下来就遇到了另一个问题,在prometheus server拉取的时候,如何正好将这一刻的积累值给它,并进行清零操作呢?
我的思路是这样的,prometheus server端拉取信息也是通过springboot项目暴露的http端点,因此可以通过拦截器,当拉取结束后就对gauge的积累值进行清零。结果发现拦截器并不起作用,后来想起来暴露的端口都可以通过配置替换,所以可能是springboot项目中,prometheus client自己起了一个springboot环境,因此导致拦截器无效。灵机一动,想到过滤器是在servlet层面进行过滤的,应该会生效吧,然后尝试了一下果然可以拦截到 ip:port/actuator/prometheus 端点,所以就选择使用过滤器进行这个操作。
这里还有一个坑,在filter中自动注入为null。这是因为在spring项目中,在项目启动时,监听器listener最先初始化,然后是过滤器filter,最后是servlet。 Spring监听器在启动时会读取spring配置文件,进行spring容器的初始化。springMVC的dispatcherServlet初始化时会读取springMVC的配置文件,进行springMVC容器的初始化。Spring容器初始化时会实例化各个bean。过滤器是servlet规范中定义的,并不归spring容器管理,也无法直接注入spring中的bean,所以在filter中无法注入spring的实例。
因此使用如下方法注入即可
public void init(FilterConfig config) throws ServletException {
ServletContext sc = config.getServletContext();
ApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(sc);
if (ctx != null && ctx.getBean("prometheusMonitor") != null) {
monitor = (PrometheusCustomMonitor) ctx.getBean("prometheusMonitor");
log.info("prometheusMonitor 注入");
}
}
之后就可以愉快的使用它了,然后我们配置好filter的拦截路径后,进行如下操作
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain){
filterChain.doFilter(servletRequest, servletResponse);
gaugeMap.get(gaugeName).set(0L);
}
这样大体的使用就讲完了,希望之后可以每周或者每两周写一篇博客,加油。