Prometheus+Springboot2.x實用實戰——Timer(二)之WebMvcMetricsFilter(最少配置的Timer記錄)

關於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實體中獲取對應的埋點緯度,最終完成埋點記錄。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章