Prometheus+Springboot2.x實用實戰——Timer(一)之@Timed初探
關於Prometheus
一個開源的監控項目,集成服務發現(Consul)、數據收集(Metrics)、存儲(TSDB)及展示(通常是接入Grafana),外加一系列的周邊支持(比如Springboot集成等等)
換而言之: 簡單、好用
具體的搭建及打點類型(Counter、Gauge、Timer),建議百度按需搜索,也可參考如下文章:
《基於Prometheus搭建SpringCloud全方位立體監控體系》.
@Timed
在io.micrometer.core.annotation包下面,我們發現了一個非常有意思的註解 @Timed
/**
* Copyright 2017 Pivotal Software, Inc.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* https://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.micrometer.core.annotation;
import java.lang.annotation.*;
@Target({ElementType.ANNOTATION_TYPE, ElementType.TYPE, ElementType.METHOD})
@Repeatable(TimedSet.class)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface Timed {
/**
* Name of the Timer metric.
*
* @return name of the Timer metric
*/
String value() default "";
/**
* List of key-value pair arguments to supply the Timer as extra tags.
*
* @return key-value pair of tags
* @see io.micrometer.core.instrument.Timer.Builder#tags(String...)
*/
String[] extraTags() default {};
/**
* Flag of whether the Timer should be a {@link io.micrometer.core.instrument.LongTaskTimer}.
*
* @return whether the timer is a LongTaskTimer
*/
boolean longTask() default false;
/**
* List of percentiles to calculate client-side for the {@link io.micrometer.core.instrument.Timer}.
* For example, the 95th percentile should be passed as {@code 0.95}.
*
* @return percentiles to calculate
* @see io.micrometer.core.instrument.Timer.Builder#publishPercentiles(double...)
*/
double[] percentiles() default {};
/**
* Whether to enable recording of a percentile histogram for the {@link io.micrometer.core.instrument.Timer Timer}.
*
* @return whether percentile histogram is enabled
* @see io.micrometer.core.instrument.Timer.Builder#publishPercentileHistogram(Boolean)
*/
boolean histogram() default false;
/**
* Description of the {@link io.micrometer.core.instrument.Timer}.
*
* @return meter description
* @see io.micrometer.core.instrument.Timer.Builder#description(String)
*/
String description() default "";
}
從註釋中,我們大約能推測出該註解的作用: 用於標註在方法上,使得Prometheus框架可以自動記錄執行耗時
Timer
我們先通過一個Demo來回顧一下Timer的一般用法,方便加下來的剖析
public Timer testTimer() {
//我們一般通過建造者模式構建Timer,builder方法的入參用於定義Timer埋點Name
return Timer.builder("test_timer_point_1")
//tags用於定義埋點的標籤,入參爲一個數組。每2個組成一個key-value對
//這裏實際定義了2個tag:disc=test;status=success
//Builder類中還有一個tag()方法,用於定義單個key-value
.tags("disc", "test", "status", "success"))
//用於定義埋點的描述,對統計沒有實際意義
.description("用於Timer埋點測試")
//用於管理所有類型Point的registry實例
.register(registry);
}
主要參數
讓我們一起來看看註解中的幾個主要參數(有幾個參數我也沒搞懂用法,搞懂了再補充到文章中去哈)
value()
對應io.micrometer.core.instrument.Timer中的builder()的如下重載方法中參數name,用於 定義PointName
static Builder builder(String name) {
return new Builder(name);
}
extraTags()
對應io.micrometer.core.instrument.Timer.Builder中的tags()的如下重載方法,用於 承載埋點的tags
/**
* @param tags Tags to add to the eventual timer.
* @return The timer builder with added tags.
*/
public Builder tags(Iterable<Tag> tags) {
this.tags = this.tags.and(tags);
return this;
}
description()
對應io.micrometer.core.instrument.Timer.Builder中的description()的如下重載方法,用於 定義埋點描述
/**
* @param description Description text of the eventual timer.
* @return This builder.
*/
public Builder description(@Nullable String description) {
this.description = description;
return this;
}
@Timed的用法
在代碼這個搜引用之前,我們來大膽猜測一下它的工作原理。
對!SpringAOP的經典用法。我們大可以自己寫一個攔截器,然後用Around去實現它。
bug!軟件作者我們定義了這個註解,一定會給一個套餐。
So,讓我們來搜一搜Timed的引用
Timed的引用
2個核心的拓展實現:
TimeAspect
注意看類的定義。
/**
* AspectJ aspect for intercepting types or methods annotated with {@link Timed @Timed}.
*
* @author David J. M. Karlsen
* @author Jon Schneider
* @author Johnny Lim
* @since 1.0.0
*/
@Aspect
@NonNullApi
@Incubating(since = "1.0.0")
public class TimedAspect {
………此處省略部分源碼………
@Around("execution (@io.micrometer.core.annotation.Timed * *.*(..))")
public Object timedMethod(ProceedingJoinPoint pjp) throws Throwable {
Method method = ((MethodSignature) pjp.getSignature()).getMethod();
Timed timed = method.getAnnotation(Timed.class);
if (timed == null) {
method = pjp.getTarget().getClass().getMethod(method.getName(), method.getParameterTypes());
timed = method.getAnnotation(Timed.class);
}
final String metricName = timed.value().isEmpty() ? DEFAULT_METRIC_NAME : timed.value();
Timer.Sample sample = Timer.start(registry);
String exceptionClass = "none";
try {
return pjp.proceed();
} catch (Exception ex) {
exceptionClass = ex.getClass().getSimpleName();
throw ex;
} finally {
try {
sample.stop(Timer.builder(metricName)
.description(timed.description().isEmpty() ? null : timed.description())
.tags(timed.extraTags())
.tags(EXCEPTION_TAG, exceptionClass)
.tags(tagsBasedOnJoinPoint.apply(pjp))
.publishPercentileHistogram(timed.histogram())
.publishPercentiles(timed.percentiles().length == 0 ? null : timed.percentiles())
.register(registry));
} catch (Exception e) {
// ignoring on purpose
}
}
}
}
哇!一個現成的 @Aspect攔截器, timedMethod() 方法攔截了所有帶有 @Timed 註解的方法執行,我們僅僅要做的是構建一個Bean,使得攔截器生效
詳細用法,請看另一篇文章(還沒寫出來。。稍等)
WebMvcMetricsFilter
WebMvcMetricsFilter繼承了OncePerRequestFilter,而後者是一個SpringMVC框架的攔截器。通過org.springframework.boot.actuate.autoconfigure.metrics.web.servlet.WebMvcMetricsAutoConfiguration類的如下代碼, 自動註冊成了一個自定義攔截器。
@Bean
public FilterRegistrationBean<WebMvcMetricsFilter> webMvcMetricsFilter(MeterRegistry registry,
WebMvcTagsProvider tagsProvider) {
Server serverProperties = this.properties.getWeb().getServer();
WebMvcMetricsFilter filter = new WebMvcMetricsFilter(registry, tagsProvider,
serverProperties.getRequestsMetricName(), serverProperties.isAutoTimeRequests());
FilterRegistrationBean<WebMvcMetricsFilter> registration = new FilterRegistrationBean<>(filter);
registration.setOrder(Ordered.HIGHEST_PRECEDENCE + 1);
registration.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.ASYNC);
return registration;
}
(o゜▽゜)o☆[BINGO!] 我們甚至不需要寫任何多餘的代碼,直接在Controller中的方法上加上@Timed註解,即可對該接口的http請求數據進行統計!
package com.隱藏.controller;
import io.micrometer.core.annotation.Timed;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* HealthExamination
* 用於健康檢查。請勿刪除!
*
* @author John
* @since 2018/8/12
*/
@Controller
@Slf4j
public class HealthExaminationController {
@Timed(value = "HealthExamination",description = "健康檢查接口")
@RequestMapping("/healthExamination")
public @ResponseBody
String index(String input) {
log.info("health examination");
return "Running";
}
}
詳細用法,請看另一篇文章(瘋狂打碼中。。)