Prometheus+Springboot2.x实用实战——Timer(二)之WebMvcMetricsFilter(最少配置的Timer记录)

13 篇文章 0 订阅
4 篇文章 0 订阅

关于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。
在进行接下来的一节之前,如果不熟悉OncePerRequestFilterFilterChain的小伙伴,可以参考下面2篇文章
《SpringBoot中filter的使用详解及原理》
《SpringBoot2 | SpingBoot FilterRegistrationBean 注册组件 | FilterChain 责任链源码分析(九)》

原理简析

WebMvcMetricsFilter实际实现了Spring中的接口,并通过FilterChain责任链设计模式,在接收到外部请求后,该Filter会通过Timer.Sample包装接下来的Filter调用,通过递归调用的调用时长计算接口时长,并记录相关维度
内部逻辑具体如下:
在107-133的filterAndRecordMetrics方法中,完成如下步骤

  1. 生成TimingContext的实体,实体中包含了Timer.Sample(相当于记录了开始时间)
  2. 通过FilterChain递归下一个Filter(这一步返回会完成接口方法的调用)
  3. 通过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类是不够的,WebMvcMetricsFilterorg.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实体中获取对应的埋点纬度,最终完成埋点记录。

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

技术流奶爸奶爸

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值