WebMvcMetricsFilter
关于Prometheus
一个开源的监控项目,集成服务发现(Consul)、数据收集(Metrics)、存储(TSDB)及展示(通常是接入Grafana),外加一系列的周边支持(比如Springboot集成等等)
换而言之: 简单、好用
具体的搭建及打点类型(Counter、Gauge、Timer),建议百度按需搜索,也可参考如下文章:
《基于Prometheus搭建SpringCloud全方位立体监控体系》.
WebMvcMetricsFilter
《Prometheus+Springboot2.x实用实战——Timer(一)之@Timed初探》.
在上一篇文章中,简单介绍了Prometheus框架中@Timed的自带应用实例WebMvcMetricsFilter的简单用法,即:只需要在通过 @Controller 注解对外暴露的接口方法上添加 @Timed 注解,即可自动实现对该接口调用Timer记录
问题来了:如何实现的?
追踪调用路径
我们定位到如下方法中
org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.record
private void record(TimingContext timingContext, HttpServletResponse response, HttpServletRequest request,
Throwable exception) {
Object handlerObject = request.getAttribute(HandlerMapping.BEST_MATCHING_HANDLER_ATTRIBUTE);
Set<Timed> annotations = getTimedAnnotations(handlerObject);
Timer.Sample timerSample = timingContext.getTimerSample();
Supplier<Iterable<Tag>> tags = () -> this.tagsProvider.getTags(request, response, handlerObject, exception);
if (annotations.isEmpty()) {
if (this.autoTimeRequests) {
stop(timerSample, tags, Timer.builder(this.metricName));
}
}
else {
for (Timed annotation : annotations) {
stop(timerSample, tags, Timer.builder(annotation, this.metricName));
}
}
}
能看到这里的Recode方法中实际使用了Timer.Sample构建了一个Timer计时器,并进行记录。那么问题来了:Spring又是如何调用它的呢?
调用堆栈
为了弄清楚这一点,我们来调试一下,看一下堆栈:
我们在方法第一行打上调试:
调用一个接口,成功调试到这一行
在其中,我发现了红圈中的方法被调用了多次。说明这里很有可能使用了递归调用
OncePerRequestFilter
定位到类org.springframework.web.filter.OncePerRequestFilter
我们可以看到,之前用到Timed的这个过滤器WebMvcMetricsFilter继承了OncePerRequestFilter。
在进行接下来的一节之前,如果不熟悉OncePerRequestFilter和FilterChain的小伙伴,可以参考下面2篇文章
《SpringBoot中filter的使用详解及原理》
《SpringBoot2 | SpingBoot FilterRegistrationBean 注册组件 | FilterChain 责任链源码分析(九)》
原理简析
WebMvcMetricsFilter实际实现了Spring中的接口,并通过FilterChain的责任链设计模式,在接收到外部请求后,该Filter会通过Timer.Sample包装接下来的Filter调用,通过递归调用的调用时长计算接口时长,并记录相关维度
内部逻辑具体如下:
在107-133的filterAndRecordMetrics方法中,完成如下步骤
- 生成TimingContext的实体,实体中包含了Timer.Sample(相当于记录了开始时间)
- 通过FilterChain递归下一个Filter(这一步返回会完成接口方法的调用)
- 通过recode方法,通过第一步生成的Timer.Sample记录埋点数据
可以参看下方源码的注释
private void filterAndRecordMetrics(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws IOException, ServletException {
//在这里生成TimingContext实体
TimingContext timingContext = TimingContext.get(request);
if (timingContext == null) {
//向TimingContext中注入Timer.Sample,用于记录开始时间
timingContext = startAndAttachTimingContext(request);
}
try {
//记录调用责任链中的下一个Filter,直至运行接口方法
filterChain.doFilter(request, response);
//从这里开始,根据不同的情况(分支)通过recode方法最终记录埋点
if (!request.isAsyncStarted()) {
// Only record when async processing has finished or never been started.
// If async was started by something further down the chain we wait
// until the second filter invocation (but we'll be using the
// TimingContext that was attached to the first)
Throwable exception = (Throwable) request.getAttribute(DispatcherServlet.EXCEPTION_ATTRIBUTE);
record(timingContext, response, request, exception);
}
}
catch (NestedServletException ex) {
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
record(timingContext, response, request, ex.getCause());
throw ex;
}
catch (ServletException | IOException | RuntimeException ex) {
record(timingContext, response, request, ex);
throw ex;
}
}
同时,光有一个Filter类是不够的,WebMvcMetricsFilter在org.springframework.boot.actuate.autoconfigure.metrics.web.servlet.WebMvcMetricsAutoConfiguration中进行了实例化,使得它在启动时被构建,并进入filterChain的责任链中
/**
* {@link EnableAutoConfiguration Auto-configuration} for instrumentation of Spring Web
* MVC servlet-based request mappings.
*
* @author Jon Schneider
* @author Dmytro Nosan
* @since 2.0.0
*/
@Configuration
@AutoConfigureAfter({ MetricsAutoConfiguration.class, SimpleMetricsExportAutoConfiguration.class })
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@ConditionalOnClass(DispatcherServlet.class)
@ConditionalOnBean(MeterRegistry.class)
@EnableConfigurationProperties(MetricsProperties.class)
public class WebMvcMetricsAutoConfiguration {
……
@Bean
public MetricsWebMvcConfigurer metricsWebMvcConfigurer(MeterRegistry meterRegistry,
WebMvcTagsProvider tagsProvider) {
return new MetricsWebMvcConfigurer(meterRegistry, tagsProvider);
}
……
}
总结
WebMvcMetricsFilter实际实现了Spring的Filter接口,启动时默认构建,成为FilterChain的责任链一环。通过包装下一个Filter的调用,计算耗时并默认从response实体中获取对应的埋点纬度,最终完成埋点记录。