Micormeter实战

Micrometer 为基于 JVM 的应用程序的性能监测数据收集提供了一个通用的 API,支持多种度量指标类型,这些指标可以用于观察、警报以及对应用程序当前状态做出响应。

前言

可接入监控系统

监控系统的三个重要特征:

  • 维度(Dimensionality):描述系统是否支持多维度数据模型。
  • 速率聚合(Rate Aggregation):指的是在规定的时间间隔内的一组样本聚合。一种是指标数据发送前在客户端做速率聚合,另一种是直接发送聚合值。
  • 发布(Publishing):描述的是指标数据的发布方式,一种是客户端定时将数据推送给监控系统,还有一种是监控系统在空闲时间自己调客户端接口拉数据。

Micrometer 的特性

  • 度量指标: 默认提供计时器、仪表、计数器、分布摘要和长任务计时器等指标与接口。

  • 丰富的指标绑定 binder: 开箱即用的缓存检测、类加载器、垃圾收集、处理器利用率、线程池,以及为可操作的洞察量身定制的更多工具,我们也可以自行扩展开发自己的绑定指标工具。

  • 方便集成到 Spring 中。

  • 支持流行的监控系统: 作为检测门面外观,Micrometer 允许您使用供应商中立的接口使用维度指标检测代码,并在最后一步决定监控系统。使用 Micrometer 检测您的核心库代码允许将库包含在将指标发送到不同后端的应用程序中。包含对 AppOptics、Azure Monitor、Netflix、Atlas、CloudWatch、Datadog、Dynatrace、Elastic、Ganglia、Graphite、Humio、 Influx /Telegraf、JMX、KairosDB、New Relic、Prometheus、SignalFx、Google Stackdriver、StatsD 和 Wavefront 的内置支持。

Micrometer实现

Registry

Micrometer 有一组包含各种监控系统实现的模块,其中的每一种实现被称为registry

Meter是一个用于收集应用程序各项指标数据的接口,Micrometer 中的所有的Meters都通过MeterRegistry创建并管理,Micrometer 支持的每一种监控系统都有对应的MeterRegistry实现。

Micrometer内部实现了多个Registry。以及其他第三方Registry。

SimpleMeterRegistry(内存注册表 )

SimpleMeterRegistry在内存中保存每个仪表的最新值并且不会将数据导出到任何地方。如果还没有首选的监控系统,可以使用简单的注册表开始使用指标,数据在内存中可以自行管理。

CompositeMeterRegistry(组合注册表 )

CompositeMeterRegistry可以添加多个注册表的工具,同时将指标发布到多个监控系统,如果只有组合注册表其实意义并不是很大,组合注册表不会创建实际存在的指标,组合注册表其实主要用来将各个注册表聚合起来的。

PrometheusMeterRegistry(普罗米修斯注册表)

引入的 micrometer-registry-prometheus 这个依赖中提供了一个PrometheusMeterRegistry用于将指标数据转换为普罗米修斯识别的格式和导出数据等功能。

GlobalRegistry(全局注册表)

是一个CompositeMeterRegistry,其内部提供了一系列用于构建 meters 的方法。

自定义 Registry

可以通过继承MeterRegistry, PushMeterRegistry, 或者 StepMeterRegistry来创建定制化的 Registry。

Meters

Micrometer 支持多种类型的度量器,包括Timer, Counter, Gauge, DistributionSummary, LongTaskTimer, FunctionCounter, FunctionTimer以及TimeGauge

在 Micrometer 中,通过名称和维度(dimensions,也可以称为"tags",即 API 中的Tag标签)来唯一确定一种meter。引入维度的概念便于我们对某一指标数据进行更细粒度的拆分研究。

指标类型

  • Timer (计时器): 用于测量短时延迟和此类事件的频率。
  • Counter (计数器):计数器记录单一计数指标,该Counter接口允许按固定数量递增,该数量必须为正数,可以用来统计无上限的数据。
  • Gauge (仪表盘): 一般用来统计有上限可增可减的数据,仪表是获取当前值的句柄。仪表的典型示例是集合或映射的大小或处于运行状态的线程数。
  • DistributionSummary(分布摘要跟踪事件的分布): 它在结构上类似于定时器,但记录的是不代表时间单位的值。例如可以使用分布摘要来衡量到达服务器的请求的负载大小。
  • LongTaskTimer(长任务计时器): 长任务计时器是一种特殊类型的计时器,可让您在正在测量的事件仍在运行时测量时间。一个普通的 Timer 只记录任务完成后的持续时间。
  • FunctionCounter(函数计数器): 在函数编程中可以传递一个函数,在需要时调用函数进行获取数据。
  • FunctionTimer(函数计时器): 在函数编程中可以传递一个函数,在需要时调用函数进行获取数据。
  • TimeGauge(跟踪时间值的专用量规): TimeGauge是一个跟踪时间值的专用量规,可缩放到每个注册表实现所期望的基本时间单位。

Naming Meters

每种监控系统都有自己的命名风格,不同系统间的命名规则可能是不兼容的。Micrometer 采用的命名约定是通过.来分隔小写单词。在 Micrometer 中,针对每种监控系统的不同实现都会将这种.分隔单词的命名风格转换为各个监控系统推荐的命名约定,同时也会去除命名中禁止出现的特殊字符。

可以通过实现NamingConvention接口来覆盖默认的命名约定规则:

registry.config().namingConvention(myCustomNamingConvention);

Tag Naming

对于 Tag 的命名,建议也采用跟 meter 一致的点号分隔小写单词的方式,这同样有助于将命名风格转换为各个监控系统推荐的命名模式。

Common Tags

common tags 属于 registry 级别的 tag,它会被应用到报告给监控系统的所有 metric,这类 tag 通常是系统维度的一些属性,比如 host、instance、region、堆栈信息等等。

common tags 必须在添加任何 meter 之前就被加入到 registry 中。

Tag Values

首先,tag values 不能为空

除此之外,我们还需要做的就是对 tag 值做规范化,对其可能取值做限制。比如针对 HTTP 请求中的 404 异常响应,可以将这类异常的响应值设置为统一返回NOT_FOUND,否则指标数据的度量维度将会随着这类找不到资源异常数量的增加而增长,导致本该聚合的指标数据变得很离散。

Meter Filters

Meter Filter 用于控制meter注册时机、可以发布哪些类型的统计数据,我们可以给每一个 registry 配置过滤器。

过滤器提供以下三个基本功能:

  • 拒绝/接受meter注册。
  • 变更meter的 ID 信息(io.micrometer.core.instrument.Meter.Id
  • 针对某些类型的meter配置分布统计。
registry.config()
    // 多个filter配置按顺序生效
    .meterFilter(MeterFilter.ignoreTags("too.much.information"))
    .meterFilter(MeterFilter.denyNameStartsWith("jvm"));

MeterFilter

Meter Filter功能通过 MeterFilter 接口的实现类实现。即有静态方法,也有实例方法

public interface MeterFilter {
    ... .... .... 
    
   default MeterFilterReply accept(Id id) {
        return MeterFilterReply.NEUTRAL;
    }

    default Id map(Id id) {
        return id;
    }

    @Nullable
    default DistributionStatisticConfig configure(Id id, DistributionStatisticConfig config) {
        return config;
    }
    
}

拒绝/接受Meters

​ 用于配置只接受指定形式的meters,或者屏蔽某些meters。通过 MeterFilter.accept方法实现。方法接受一个

io.micrometer.core.instrument.Id 类型的参数。返回MeterFilterReply 控制行为。

public enum MeterFilterReply {
	//拒绝meter注册请求,registry将会返回一个该meter的NOOP版本(如NoopCounter、NoopTimer)
    DENY
        //当没有任何过滤器返回DENY时,meter的注册流程继续向前推进
        , NEUTRAL
        // 表示meter注册成功,无需继续向下流转“询问”其他filter的accept(...)方法
        , ACCEPT

}

针对Meter的 deny/accept 策略, MeterFilter为我们提供了一些常用的方法:

  • accept():接受所有meter注册,该方法之后任何 filter 都是无效的。
  • accept(Predicate):接收满足给定条件meter注册。
  • acceptNameStartsWith(String):接收 name 以指定字符打头的meter注册。
  • deny()拒绝所有meter的注册请求,该方法之后的任何 filter 都是无效的。
  • denyNameStartsWith(String):拒绝所有 name 以指定字符串打头的meter的注册请求。
  • deny(Predicate):拒绝满足特定条件的meter的注册请求。
  • maximumAllowableMetrics(int):当已注册的meters数量达到允许的注册上限时,拒绝之后的所有注册请求。
  • maximumAllowableTags(String meterNamePrefix, String tagKey, int maximumTagValues, MeterFilter onMaxReached):设置一个tags上限,达到这个上限时拒绝之后的注册请求。
  • denyUnless(Predicate):白名单机制,拒绝不满足给定条件的所有meter的注册请求。

变更Meter的 ID 信息

通过Map方法实现ID信息修改。

   default Meter.Id map(Meter.Id id) {
        return id;
    }

变更Tag信息

  • commonTags(Iterable):为所有指标添加一组公共 tags。通常建议开发者为应用程序名称、host、region 等信息添加公共 tags。
  • ignoreTags(String…):用于从所有meter去除指定的 tag key。比如当我们发现某个 tag 具有过高的基数,并且已经对监控系统构成压力,此时可以在无法立即改变所有检测点的前提下优先采用这种方式来快速减轻系统压力。
  • replaceTagValues(String tagKey, Function replacement, String… exceptions):替换满足指定条件的所有 tag 值。通过这种方式可以某个 tag 的基数大小。
  • renameTag(String meterNamePrefix, String fromTagKey, String toTagKey)重命名所有以给定前缀命名的metric的 tag key。

配置分布统计信息

new MeterFilter() {
    @Override
    public DistributionStatisticConfig configure(Meter.Id id, DistributionStatisticConfig config) {
        if (id.getName().startsWith(prefix)) {
            return DistributionStatisticConfig.builder()
                    // ID名称以指定前缀开头的请求提供指标统计直方图信息
                    .publishPercentiles(0.9, 0.95)
                    .build()
                    .merge(config);
        }
        return config;
    }
};

速率聚合

速率聚合可以在指标数据发布之前在客户端完成,也可以作为服务器查询的一部分在服务端临时聚合。Micrometer 可以根据每种监控系统的风格选择聚合方式。

并不是所有的指标都需要被视为一种速率来发布或查看。例如,gauge值或者长期定时任务中的活跃任务数都不是速率。

服务端聚合

执行服务端速率计算的监控系统期望能在每个发布间隔报告计数绝对值。例如,从应用程序启动开始 counter 计数器在每个发布间隔产生的所有增量的绝对计数和。当服务重启时 counter 的计数值就会降为零。

客户端聚合

在实际应用中,有以下两类监控系统期望客户端在发布指标数据之前完成速率聚合。

  • 期望得到聚合数据。生产环境中大多数情况下我们都需要基于服务指标的速率作出决策,这种情况下服务端需要做更少的计算来满足查询要求。
  • 查询阶段只有少量或者根本没有数学计算允许我们做速率聚合。对于这些系统,发布一个预先聚合的数据是非常有意义的事情。

实战

引入

Micrometer 包含一个带有检测 SPI (Service Provider Interface 一种扩展机制)的核心库和一个不将数据导出到任何地方的内存中实现,一系列具有各种监控系统实现的模块,以及一个测试模块。这里依赖主要介绍两个一个是核心依赖,一个是适配第三方监控的扩展依赖。

核心依赖为:micrometer-core 。 核心的注册表,监控指标,默认提供的绑定配置等都在这里。

     <dependency>
      <groupId>io.micrometergroupId>
      <artifactId>micrometer-coreartifactId>
      <version>${version}version>
      <scope>compilescope>
    dependency>

适配第三方监控的依赖:用于将指标适配到第三方监控系统。

导入prometheus

   <dependency>
        <groupId>io.micrometergroupId>
        <artifactId>micrometer-registry-prometheusartifactId>
        <version>${version}version>
        <scope>compilescope>
    dependency>

完整引入:

<dependency>
    <groupId>io.micrometergroupId>
    <artifactId>micrometer-registry-prometheusartifactId>
    <version>1.10.2version>
    <exclusions>
        <exclusion>
            <artifactId>micrometer-coreartifactId>
            <groupId>io.micrometergroupId>
        exclusion>
    exclusions>
dependency>
<dependency>
    <groupId>io.micrometergroupId>
    <artifactId>micrometer-coreartifactId>
    <version>1.10.2version>
dependency>

示例

public interface Meter {
    //获取id
    Id getId();
    //获取度量集合
    Iterable<Measurement> measure();
    default <T> T match(Function<Gauge, T> visitGauge, Function<Counter, T> visitCounter, Function<Timer, T> visitTimer,
            Function<DistributionSummary, T> visitSummary, Function<LongTaskTimer, T> visitLongTaskTimer,
            Function<TimeGauge, T> visitTimeGauge, Function<FunctionCounter, T> visitFunctionCounter,
            Function<FunctionTimer, T> visitFunctionTimer, Function<Meter, T> visitMeter) {
    }
    default void use(Consumer<Gauge> visitGauge, Consumer<Counter> visitCounter, Consumer<Timer> visitTimer,
            Consumer<DistributionSummary> visitSummary, Consumer<LongTaskTimer> visitLongTaskTimer,
            Consumer<TimeGauge> visitTimeGauge, Consumer<FunctionCounter> visitFunctionCounter,
            Consumer<FunctionTimer> visitFunctionTimer, Consumer<Meter> visitMeter) {
    }
}
    private static final Random R = new Random();

    static {
        Metrics.addRegistry(new SimpleMeterRegistry());
    }

Timer

Timer用于度量短时间内的事件时延和响应频率。所有的Timer实现都记录了事件响应总耗时和事件总数Timer不支持负数,此外如果使用它来记录大批量、长时延事件的话,容易导致指标值数据越界(超过Long.MAX_VALUE)。

public interface Timer extends Meter {
    ...
    //一次记录
    void record(long amount, TimeUnit unit);
    //一次记录行为
    void record(...);
    //一次记录
    void record(Duration duration);
    //返回记录事件的总时间
    double totalTime(TimeUnit unit);
}
public static void testTimer() {
    //创建一个Timer,name为timer,tag为:createOrder,cost
        Timer timer = Metrics.timer("timer", "createOrder", "cost");

        for (int i = 0; i < 10; i++) {
            //记录任务执行的时间
            timer.record(() -> createOrder());
        }
		//记录明确的时间
        timer.record(5,TimeUnit.SECONDS);
        //获取Timer的度量值。
        Iterable<Measurement> measure = timer.measure();
        System.out.println(measure);
		//获取总耗时
        System.out.println(timer.totalTime(TimeUnit.MILLISECONDS));
    	//获取计数
        System.out.println(timer.count());
    	//获取最大耗时
        System.out.println(timer.max(TimeUnit.MILLISECONDS));
    }

    private static void createOrder() {
        try {
            TimeUnit.SECONDS.sleep(R.nextInt(5)); //模拟方法耗时
        } catch (InterruptedException e) {
            //no-op
        }
    }

timer实例的属性:

Micormeter实战_第1张图片

timer的内部真实类型为CumulativeTimer,包括具体的值。measure的值从此处获取。

Micormeter实战_第2张图片

    @Override
    protected void recordNonNegative(long amount, TimeUnit unit) {
        long nanoAmount = (long) TimeUtils.convert(amount, unit, TimeUnit.NANOSECONDS);
        count.getAndAdd(1);
        total.getAndAdd(nanoAmount);
        max.record(nanoAmount, TimeUnit.NANOSECONDS);
    }

measure值

Micormeter实战_第3张图片

Counter

Counter是一种比较简单的Meter,它是一种单值的度量类型,或者说是一个单值计数器。Counter接口允许使用者使用一个固定值(必须为正数)进行计数。

public interface Counter extends Meter {
    default void increment() {
        increment(1.0);
    }
    
     void increment(double amount);
}

CumulativeCounter

public class CumulativeCounter extends AbstractMeter implements Counter {

    private final DoubleAdder value;

    @Override
    public void increment(double amount) {
        value.add(amount);
    }

    @Override
    public double count() {
        return value.sum();
    }
}    

 Counter counter = Metrics.counter("http.request", "createOrder", "/order/create");
 counter.increment();
 System.out.println(counter.measure());
//OUTPUT;
//Measurement{statistic='COUNT', value=1.0}

Gauge

Gauge(仪表)是获取当前度量记录值的句柄,也就是它表示一个可以任意上下浮动的单数值度量Meter。

Gauge通常用于变动的测量值,测量值用ToDoubleFunction参数的返回值设置。

Gauge接口继承Meter接口,但未增加方法。

MeterRegistry中提供了一些便于构建用于观察数值、函数、集合和映射的Gauge相关的方法

        Double number = Metrics.gauge("number", new Double(8.0));

        ArrayList<String> coll = Metrics.gaugeCollectionSize("coll", Tags.empty(), new ArrayList<>());

        coll.add("5");

        Map<String, Integer> map = Metrics.gaugeMapSize("mapGauge", Tags.empty(), new HashMap<>());

        map .put("s",1);

        System.out.println(number);

和其他类型不同,Gauge不能在创建时获取到引用,而是被观察的对象。 在被创建后是自给自足的,因此您永远不需要与它交互。

DistributionSummary

它在结构上类似于定时器,但记录的是不代表时间单位的值。例如,您可以使用分布摘要来衡量到达服务器的请求的负载大小。

CumulativeDistributionSummary

public interface DistributionSummary extends Meter, HistogramSupport {
   class Builder {

        private final String name;

        private Tags tags = Tags.empty();

        private DistributionStatisticConfig.Builder distributionConfigBuilder = DistributionStatisticConfig.builder();

        @Nullable
        private String description;

        @Nullable
        private String baseUnit;
		//比例   double scaledAmount = this.scale * amount;
        private double scale = 1.0;
	}
}

示例:


output:


LongTaskTimer

LongTaskTimer是一种特殊类型的Timer,可对 被测量的事件仍在运行时 测量时间。一个普通的 Timer 只记录任务完成后的持续时间。

长任务计时器会统计以下数据:

  • 活跃任务数;
  • 所有活跃任务的总持续时间;
  • 活跃任务中的最大持续时间。

Timer不同的是,长任务计时器不会发布关于已完成任务的统计信息。

如果想在进程超过指定阈值时触发报警,当使用长任务定时器时,在任务超过指定阈值后的首次报告间隔内就可以收到报警。如果使用的是常规的Timer,只能一直等到任务结束后的首次报告间隔时才能收到报警,此时可能已经过去很长时间了。

通过 taskTimer.start();方法添加任务,产生 LongTaskTimer.Sample ,表示一个任务,通过Sample.stop()方法,标识一个任务的结束。

DefaultLongTaskTimer

public class DefaultLongTaskTimer extends AbstractMeter implements LongTaskTimer {
   private final Deque<SampleImpl> activeTasks = new ConcurrentLinkedDeque<>();

}

示例:

    public static void testLongTimer() {

        LongTaskTimer longTaskTimer = LongTaskTimer
                .builder("long.task.timer")
                .tags("region", "test")
                .register(Metrics.globalRegistry);

        //此处代码表示MeterRegistry 收集数据。
        Thread thread = new Thread(() -> {

            int i = 0;
            while (i < 10) {
                System.out.println("第" + i + "次收集。");
                Set<MeterRegistry> registries = Metrics.globalRegistry.getRegistries();

                registries.forEach(
                        x -> {
                            RequiredSearch requiredSearch = x.get("long.task.timer");
                            LongTaskTimer longTaskTimer1 = requiredSearch.longTaskTimer();
                            if (longTaskTimer1 != null) {
                                System.out.println("active task count: " + longTaskTimer.activeTasks());
                                System.out.println("max: " + longTaskTimer.max(TimeUnit.MILLISECONDS));
                                System.out.println("duration: " + longTaskTimer.duration(TimeUnit.MILLISECONDS));
                            }
                        }
                );
                try {
                    TimeUnit.MILLISECONDS.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                i++;
            }
        });

        thread.start();

        longTask(longTaskTimer, 2000).start();
        longTask(longTaskTimer, 1500).start();
        longTask(longTaskTimer, 200).start();
        longTask(longTaskTimer, 500).start();
        longTask(longTaskTimer, 800).start();
    }

    private static Thread longTask(LongTaskTimer taskTimer, int sleep) {
        Thread t = new Thread(() -> {
            try {
                LongTaskTimer.Sample sample = taskTimer.start();
                TimeUnit.MILLISECONDS.sleep(R.nextInt(sleep)); //模拟方法耗时
                sample.stop();
            } catch (InterruptedException e) {
                //no-op
            }
        });
        return t;
    }

输出:

第0次收集。
active task count: 5
max: 4.4594
duration: 32.2311
第1次收集。
active task count: 3
max: 206.5614
duration: 619.1936
... ...
第4次收集。
active task count: 1
max: 807.5339
duration: 807.6033
......
第7次收集。
active task count: 0
max: 0.0
duration: 0.0
......
第9次收集。
active task count: 0
max: 0.0
duration: 0.0

FunctionTimer

FunctionTimerTimer的特化类型,它主要提供两个单调递增的函数,一个用于计数的函数和一个用于记录总调用耗时的函数。

FunctionTimer接口的方法与Timer接口基本一致,但是之间不是继承关系。

public interface FunctionTimer extends Meter {
 
    double count();
 
    double totalTime(TimeUnit unit);
 
    default double mean(TimeUnit unit) {
        double count = count();
        return count == 0 ? 0 : totalTime(unit) / count;
    }
 
    TimeUnit baseTimeUnit();

    @Override
    default Iterable<Measurement> measure() {
        return Arrays.asList(new Measurement(this::count, Statistic.COUNT),
                new Measurement(() -> totalTime(baseTimeUnit()), Statistic.TOTAL_TIME));
    }

 
    class Builder<T> {

        private final String name;
        private final ToLongFunction<T> countFunction;
        private final ToDoubleFunction<T> totalTimeFunction;
        private final TimeUnit totalTimeFunctionUnit;
        private Tags tags = Tags.empty();
        @Nullable
        private final T obj;
        @Nullable
        private String description;
        public FunctionTimer register(MeterRegistry registry) {
            return registry.more().timer(new Meter.Id(name, tags, null, description, Type.TIMER), obj, countFunction,
                    totalTimeFunction, totalTimeFunctionUnit);
        }

    }

}

示例:

   public static void testFTimer() {
        class Holder {
            public long getCount() {
                return 10;
            }

            public double getTime() {
                return 10000;
            }
        }
        Holder holder = new Holder();
        FunctionTimer.Builder<Holder> builder = FunctionTimer.builder("cache.gets.latency", holder,
                h -> h.getCount(),
                h -> h.getTime(),
                TimeUnit.NANOSECONDS);
        FunctionTimer functionTimer = builder
                .tags("name", "holder")

                .description("Cache gets")
                .register(Metrics.globalRegistry);

        System.out.println(functionTimer.count());
        System.out.println(functionTimer.totalTime(TimeUnit.SECONDS));
        System.out.println(functionTimer.measure());
    }
/**
OUTPUT:
10.0
1.0E-5
[Measurement{statistic='COUNT', value=10.0}, Measurement{statistic='TOTAL_TIME', value=1.0E-5}]
*/

FunctionCounter

FunctionCounterCounter的特化类型,它把计数器数值增加的动作抽象成接口类型ToDoubleFunction。在函数编程中可以传递一个函数,在需要时调用函数进行获取数据。count方法返回的值为 ojb应用 ToDoubleFunction的返回值。

public interface FunctionCounter extends Meter {
        class Builder<T> {
            public FunctionCounter register(MeterRegistry registry) {
              return registry.more().counter(new Meter.Id(name, tags, baseUnit, description, Type.COUNTER), obj, f);
        }
        }
}

默认实现为CumulativeFunctionCounter

public class CumulativeFunctionCounter<T> extends AbstractMeter implements FunctionCounter {
    private final WeakReference<T> ref;
    private final ToDoubleFunction<T> f;
    //最后一次的值。
    private volatile double last;

    public CumulativeFunctionCounter(Meter.Id id, T obj, ToDoubleFunction<T> f) {
        super(id);
        this.ref = new WeakReference<>(obj);
        this.f = f;
    }

    @Override
    public double count() {
        T obj2 = ref.get();
        //监视的值不为null时,则应用ToDoubleFunction。
        return obj2 != null ? (last = f.applyAsDouble(obj2)) : last;
    }

}

示例:

        AtomicInteger n = new AtomicInteger(0);
        FunctionCounter counter = Metrics.more().counter("f.counter", Tags.empty(), n, new ToDoubleFunction<AtomicInteger>() {
            @Override
            public double applyAsDouble(AtomicInteger value) {
                return value.get() * 2;
            }
        });
        n.getAndIncrement();
        n.getAndIncrement();
        n.getAndIncrement();
        double count = counter.count();
        Iterable<Measurement> measure = counter.measure();
        System.out.println(count );
        System.out.println(measure);

//OUTPUT:
6.0   //返回的是3 * 2
[Measurement{statistic='COUNT', value=6.0}]

TimeGauge

TimeGauge是一个跟踪时间值的专用Gauge,可缩放到每个注册表实现所期望的基本时间单位。即比例尺,以免数值过大。


    @SneakyThrows
    public static void testTimeGauge() {
        AtomicInteger count = new AtomicInteger();
        TimeGauge.Builder<AtomicInteger> timeGauge = TimeGauge.builder("timeGauge", count,
                TimeUnit.MILLISECONDS, AtomicInteger::get);
        timeGauge.register(Metrics.globalRegistry);
        //设置的值是 注册TimeGauge时的TimeUnit MILLISECONDS
        count.set(5);
        TimeUnit.MILLISECONDS.sleep(200);
        count.set(10);
        TimeUnit.MILLISECONDS.sleep(150);
        count.set(2);
        TimeUnit.MILLISECONDS.sleep(300);
        count.set(8);
        TimeUnit.MILLISECONDS.sleep(100);

    }

OUTPUT:

第0次收集。
 
第1次收集。
meter id:MeterId{name='timeGauge', tags=[]} , measure:[Measurement{statistic='VALUE', value=0.005}]
第2次收集。
meter id:MeterId{name='timeGauge', tags=[]} , measure:[Measurement{statistic='VALUE', value=0.01}]
第3次收集。
meter id:MeterId{name='timeGauge', tags=[]} , measure:[Measurement{statistic='VALUE', value=0.002}]
第4次收集。
meter id:MeterId{name='timeGauge', tags=[]} , measure:[Measurement{statistic='VALUE', value=0.002}]
第5次收集。
meter id:MeterId{name='timeGauge', tags=[]} , measure:[Measurement{statistic='VALUE', value=0.008}]
.... ...
第9次收集。
meter id:MeterId{name='timeGauge', tags=[]} , measure:[Measurement{statistic='VALUE', value=0.008}]

输出值会转换为基本时间单位(BaseUnit)。

AOP

@Timed

@Timed可以被添加到包括 Web 方法在内的任何一个方法中,加入该注解后可以支持方法计时功能。

@Target({ ElementType.ANNOTATION_TYPE, ElementType.TYPE, ElementType.METHOD })
@Repeatable(TimedSet.class)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface Timed {
    /**  metric name*/
    String value() default "";
    String[] extraTags() default {};

    /** 是否是一个 LongTaskTimer */
    boolean longTask() default false;

    /**分位数 列表,例如 0.5,0.75,0.95 等  */
    double[] percentiles() default {};
 
    boolean histogram() default false;
 
    String description() default "";

}

Micrometer 的 Spring Boot 配置中无法识别@Timed

micrometer-core中提供了一个 AspectJ 切面,使得我们可以通过 Spring AOP 的方式使得@Timed注解在任意方法上可用。

@Configuration
public class TimedConfiguration {
   @Bean
   public TimedAspect timedAspect(MeterRegistry registry) {
      return new TimedAspect(registry);
   }
}


@Service
public class ExampleService {

  @Timed
  public void sync() {
    // @Timed will record the execution time of this method,
    // from the start and until it exits normally or exceptionally.
    ...
  }

  @Async
  @Timed
  public CompletableFuture<?> async() {
    // @Timed will record the execution time of this method,
    // from the start and until the returned CompletableFuture
    // completes normally or exceptionally.
    return CompletableFuture.supplyAsync(...);
  }

}

支持的切点

@Around("@within(io.micrometer.core.annotation.Timed)")
@Around("execution (@io.micrometer.core.annotation.Timed * *.*(..))")

附录

参考

https://micrometer.io/docs/concepts

https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.2-Release-Notes

与spring整合

Spring 2.x

spring boot actuator中使用了Micrometer

1、引入依赖包


<dependency>
    <groupId>io.micrometergroupId>
    <artifactId>micrometer-registry-prometheusartifactId>
    <version>1.3.1version>
dependency>

2、配置

/**配置公用tag
*/
@Bean
public MeterRegistryCustomizer<MeterRegistry> meterRegistryCustomizer() {
	return meterRegistry -> meterRegistry.config().commonTags(Collections.singletonList(Tag.of("application", "mf-micrometer-example")));
}

@Bean
public TimedAspect timedAspect(MeterRegistry registry) {
	return new TimedAspect(registry);
}

Spring Boot中无法直接使用@Timed,需要引入TimedAspect切面支持

可以自定义切面类。

 //自定义的切面类
    @Component
    @Aspect
    public class TimerAspect {

        @Around(value = "execution(* club.throwable.smp.service.*Service.*(..))")
        public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
            Signature signature = joinPoint.getSignature();
            MethodSignature methodSignature = (MethodSignature) signature;
            Method method = methodSignature.getMethod();
            Timer timer = Metrics.timer("method.cost.time", "method.name", method.getName());
            ThrowableHolder holder = new ThrowableHolder();
            Object result = timer.recordCallable(() -> {
                try {
                    return joinPoint.proceed();
                } catch (Throwable e) {
                    holder.throwable = e;
                }
                return null;
            });
            if (null != holder.throwable) {
                throw holder.throwable;
            }
            return result;
        }

        private class ThrowableHolder {

            Throwable throwable;
        }
}

3、访问

Spring Boot 默认提供了一个/actuator/prometheus端点用于服务指标数据拉取。需要先打开web暴露

management.endpoints.web.exposure.include=info,health
management.endpoints.web.exposure.include=prometheus

配置应用tag

  • 配置文件添加tag

    management.metrics.tags.application=${spring.application.name}
    
  • MeterRegistryCustomizer:见前面代码

management:
  endpoints:
    web:
      exposure:
        include: 'prometheus'
  metrics:
    export:
      prometheus:
        enabled: true  
    tags:
      application: cloud-quality

4、配置prometheus

scrape_configs:
  # The job name is added as a label `job=` to any timeseries scraped from this config.
  - job_name: 'prometheus'

    # metrics_path defaults to '/metrics'
    # scheme defaults to 'http'.
    # 这里配置需要拉取度量信息的URL路径,这里选择应用程序的prometheus端点
	metrics_path: `/actuator/promethues`
	static_configs:
	# 这里配置host和port
    - targets: ['localhost:10091']

spring 1.5

使用micrometer-spring-legacy来使用Micrometer。

Micrometer与Spring Boot Actuator比较

在spring2.x之后,Spring boot actuator使用了Micrometer来实现监控。但是在spring 2之前 Spring boot actuator 并没有使用Micrometer,而是类似如自己之前写的统计监控指标的sdk,使用了dropwizard-metrics,所以Spring1.x与Micrometer没有任何关系。

Micrometer与dropwizard-metrics

  • dropwizard-metricsmicrometer都是度量工具包,但是没有任何关系。
  • 在于Micrometer支持tags,即一组keys联合标识一个唯一键。

参考下 https://juejin.im/post/5aad3351f265da23994e4ce7

你可能感兴趣的:(Java中间件,java,Micormeter)