Micormeter实战

news2024/9/21 12:44:26

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

变更Meter的 ID 信息

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

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

变更Tag信息

  • commonTags(Iterable<Tag>):为所有指标添加一组公共 tags。通常建议开发者为应用程序名称、host、region 等信息添加公共 tags。
  • ignoreTags(String…):用于从所有meter去除指定的 tag key。比如当我们发现某个 tag 具有过高的基数,并且已经对监控系统构成压力,此时可以在无法立即改变所有检测点的前提下优先采用这种方式来快速减轻系统压力。
  • replaceTagValues(String tagKey, Function<String, String> 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.micrometer</groupId>
      <artifactId>micrometer-core</artifactId>
      <version>${version}</version>
      <scope>compile</scope>
    </dependency>

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

导入prometheus

   <dependency>
        <groupId>io.micrometer</groupId>
        <artifactId>micrometer-registry-prometheus</artifactId>
        <version>${version}</version>
        <scope>compile</scope>
    </dependency>

完整引入:

<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-registry-prometheus</artifactId>
    <version>1.10.2</version>
    <exclusions>
        <exclusion>
            <artifactId>micrometer-core</artifactId>
            <groupId>io.micrometer</groupId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-core</artifactId>
    <version>1.10.2</version>
</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实例的属性:

image-20230421200234660

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

image-20230421200412867

    @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值

image-20230421200758714

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、引入依赖包

<!-- https://mvnrepository.com/artifact/io.micrometer/micrometer-registry-prometheus -->
<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-registry-prometheus</artifactId>
    <version>1.3.1</version>
</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=<job_name>` 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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/659792.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

[保姆教程] Windows平台OpenCV以及它的Golang实现gocv安装与测试(亲测通过)

一、MinGW & CMake 预备步骤 首先打开cmd: c: md mingw-w64 md cmake下载安装MinGW-W64 访问&#xff1a; https://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting%20Win32/Personal%20Builds/mingw-builds/7.3.0/ 下载&#xff1a; MinGW-W64 GCC-8…

一文详解Softmax的性质与Self-Attention初步解析

概述 最近研究超平面排列(Hyperplane Arrangement)问题的时候&#xff0c;发现ReLU有其缺陷&#xff0c;即举例来说&#xff0c;ReLU 无法使用单一的超平面将分离的所有数据&#xff0c;完整的输出&#xff0c;即只会输出半个空间映射的数据&#xff0c;而另一半空间的数据被置…

面试---简历

项目 1.1、商品管理 新增商品 同时插入商品对应的使用时间数据&#xff0c;需要操作两张表&#xff1a;product&#xff0c;product_usetime。在productService接口中定义save方法&#xff0c;该方法接受一张Dto对象&#xff0c;dto对象继承自product类&#xff0c;并将prod…

学习open62541 --- [78] 单线程和多线程的使用场景

open62541提供多线程功能&#xff0c;默认不开启&#xff0c;即单线程&#xff0c; 把UA_MULTITHREADING的值设置为 > 100就可以开启多线程了。 这里单线程/多线程的意思是基于open62541运行的server内部是否使用锁去保护数据。只要server运行后还有读写操作需要做&#x…

从源码全面解析 dubbo 消费端服务调用的来龙去脉

&#x1f44f;作者简介&#xff1a;大家好&#xff0c;我是爱敲代码的小黄&#xff0c;独角兽企业的Java开发工程师&#xff0c;CSDN博客专家&#xff0c;阿里云专家博主&#x1f4d5;系列专栏&#xff1a;Java设计模式、Spring源码系列、Netty源码系列、Kafka源码系列、JUC源码…

3D 顶点着色与Phong 反射模型

Phong 反射模型有时被称为“Phong 照明”或“Phong 照明”。它由环境光照、漫反射&#xff08;朗伯反射&#xff09;、镜面反射三部分组成。 Phong 反射模型提供了一个方程式&#xff0c;用于计算表面上每个点的光照&#xff0c;I_p&#xff1a; 第一部分代表环境光项。在GLSL代…

X书hmac参数

被删重新发送 全文可以查看&#xff1a; 上面一遍unidbg解密shield文章 unidbg - 》 callObjectMethodV方法填写你的小红书路径下s.xml里的值: 或者在抓包响应头中: 查找xy-ter-str hmac 结果都是在&#xff0c;响应头里&#xff0c;所以 hmac 是服务器下发给客户端的. Over…

黑马头条.

文章目录 前言一、项目概述1.1 能收获什么1.2 项目概述1.3 项目术语1.4 业务说明 二、技术栈2.1技术栈整体框架图2.2技术栈简介 三、nacos环境搭建3.1 虚拟机镜像准备3.2 nacos的安装 四、初始工程搭建4.1 开发环境准备 五、实现登录功能5.1 需求分析5.2 表结构分析5.3 思路分析…

22道常见RocketMQ面试题以及答案

面试宝典到手&#xff0c;搞定面试&#xff0c;不再是难题&#xff0c;系列文章传送地址&#xff0c;请点击本链接。 1、RocketMQ是什么? 2、RocketMQ有什么作用&#xff1f; 3、RoctetMQ的架构 4、RoctetMQ的优缺点 8、消息过滤,如何实现&#xff1f; 9、消息去重,如果…

Elasticsearch 基本使用(四)聚合查询

聚合查询 概述单字段聚合查询统计分组后的数量非文档字段分组文档字段分组 其他聚合运算统计平均值统计总金额统计最大值自定义聚合结果排序简单聚合小结 多字段聚合查询 概述 说到聚合查询&#xff0c;马上会想到 SQL 中的 group by&#xff0c;ES中也有类似的功能&#xff0…

编程语言发展历史

文章目录 语言的发展时间轴语言世代时间轴1940年前-机器语言时代1940年后-汇编语言时代1950年-高级语言的初生1960年-高级语言的进一步成熟1980年-各大语言的进一步增强1990年代-飞速发展时代2000年-新时代 高级编程语言的分类解释型与编译型面向过程与面向对象 对语言的评价Ti…

【学习学习】NLP理解层次模型

NLP&#xff08;Neuro-Linguistic Programming&#xff0c;神经语言程序学&#xff09;&#xff0c;由两位美国人理查得.班德勒&#xff08;Richard Bandler&#xff09;与约翰.葛瑞德&#xff08;John Grinder&#xff09;于1976年创办&#xff0c;并在企业培训中广泛使用。美…

PyTorch 深度学习 || 4. 自编码网络 | Ch4.3 卷积自编码网络图像去噪

卷积自编码网络图像去噪 1. 数据的准备 先简单介绍一下训练网络使用到的图像数据集——STL10&#xff0c;该数据集可以通过torchvision.datasets模块中的STL10()函数进行下载&#xff0c;该数据集共包含三种类型数据,分别是带有标签的训练集和验证集&#xff0c;分别包含5000…

Cookie增删改查方法封装(低内存开销版)

本文章中的低内存开销是指在获取cookie的时候不进行字符串—>数组的转变&#xff0c;全程使用sliceindexOf切割字符串&#xff0c;不创建和操作数组&#xff0c;节约内存&#xff0c;本文代码已存放到github中&#xff0c;后续会持续完善功能&#xff0c;传送门&#xff1a;…

二进制方式部署kubernetes集群

二进制方式部署kubernetes集群 1、部署k8s常见的几种方式 1.1 kubeadm Kubeadm 是一个 k8s 部署工具&#xff0c;提供 kubeadm init 和 kubeadm join&#xff0c;用于快速部署 Kubernetes 集群。 Kubeadm 降低部署门槛&#xff0c;但屏蔽了很多细节&#xff0c;遇到问题很难…

掌握Python的X篇_4_开发工具ipython与vscode的安装使用

本篇将会介绍两个工具的安装及使用来提高Python的编程效率。 ipython&#xff1a;比python更好用的交互式开发环境vscode&#xff1a;本身是文本编辑器&#xff0c;通过安装相关的插件vscode可以作为python集中开发环境使用 掌握Python的X篇_4_开发工具ipython与vscode的安装使…

第四章 linux编辑器——vim的使用

第四章 linux编辑器——vim的使用 一、什么是vim&#xff1f;二、vim的基本操作1、模式之间的相互切换2、vim的常见命令集&#xff08;1&#xff09;正常模式的常见命令a. 模式切换b. 光标移动c.删除文字d.复制e.替换f.撤销g.更改 &#xff08;2&#xff09;底行模式的常见命令…

复习之linux的网络配置

一、基本定义 1.IP IP指网际互连协议&#xff0c;Internet Protocol的缩写&#xff0c;是TCP/IP体系中的网络层协议。 电脑之间要实现网络通信&#xff0c;就必须要有一个合法的ip地址。 IP地址网络地址主机地址&#xff08;又称&#xff1a;主机号和网络号组成&#xff09…

【MySQL】MyISAM中的索引方案

介绍 B树索引使用存储引擎如表所示&#xff1a; 索引/存储引擎MyISAMInnoDBMemoryB树索引支持支持支持 多个存储引擎支持同一种类型的索引&#xff0c;但是他们的实现原理是不同的。 InnoDB和MyISAM默认的索引是B树索引&#xff0c;而Memory默认的索引是Hash索引。 MyISAM…

【软件测试】在Windows使用Docker搭建CentOS环境(详细)

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 我们做软件测试在…