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