天天看點

Prometheus+Springboot2.x實用實戰——Timer(一)之@Timed初探關于Prometheus@Timed@Timed的用法

Prometheus+Springboot2.x實用實戰——Timer(一)之@Timed初探

  • 關于Prometheus
  • @Timed
    • Timer
    • 主要參數
      • value()
      • extraTags()
      • description()
  • @Timed的用法
    • Timed的引用
    • TimeAspect
    • WebMvcMetricsFilter

關于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個核心的拓展實作:

Prometheus+Springboot2.x實用實戰——Timer(一)之@Timed初探關于Prometheus@Timed@Timed的用法

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";
    }
}
           

詳細用法,請看另一篇文章

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

上一篇: 安裝LAMP