Micrometer实战

news2024/9/25 13:18:13

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/672074.html

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

相关文章

事务的历史与SSI——PostgreSQL数据库技术峰会成都站分享

前言 PostgreSQL数据库技术峰会成都站 近期&#xff08;2023年6月17日&#xff09;&#xff0c;由中国开源软件推进联盟PG分会发起的“PostgreSQL数据库技术峰会成都站”圆满举行。我也有幸作为演讲嘉宾参加了此次峰会&#xff0c;收获很多。 &#xff08;分会回顾和所有pp…

Qt编写监控实时显示和取流回放工具(回放支持切换进度)

一、前言 现在各个监控大厂做的设备&#xff0c;基本上都会支持通过rtsp直接取流显示&#xff0c;而且做的比较好的还支持通过rtsp回放取流&#xff0c;基本上都会约定一个字符串的规则&#xff0c;每个厂家都是不一样的规则&#xff0c;比如回放对应的rtsp地址还要带上时间范…

Java8 List集合如何指定打印分隔符

目录 背景方法一&#xff1a;String.join&#xff08;推荐&#xff09;方法二&#xff1a;Collectors.joining总结 背景 无论是在学习还是日常的应用开发过程中&#xff0c;我们经常会需要使用分隔符将 List 集合打印出来。 如下所示&#xff1a; import java.util.Arrays;pub…

数据结构与算法基础(青岛大学-王卓)(5)

叮叮咚咚&#xff0c;新一期来袭&#xff0c;我还在吃桃子&#xff0c;吃桃子&#xff0c;吃桃子。。。串和python的字符串差不多,数组和广义表像是python的list 文章目录 串(string) - 字符串概念及术语串的类型定义存储结构&#xff08;同线性表&#xff09;串的模式匹配算法…

leetcode46. 全排列(回溯算法-java)

全排列 leetcode46. 全排列题目描述解题思路代码演示 回溯算法专题 leetcode46. 全排列 来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 链接&#xff1a;https://leetcode.cn/problems/permutations 题目描述 给定一个不含重复数字的数组 nums &#xff0c;返回其 所有…

关于平差中误差方程 v=Bx-l 的探讨

文章目录 Part.I IntroductionPart.II 误差方程的探讨Chap.I 符号表示Chap.II 误差方程的含义Chap.III 误差方程的其他形式Chap.IV 平差的大致流程 Part.III 误差方程的表示形式 Part.I Introduction 在平时阅读文献或者整理笔记时&#xff0c;经常会看到各种各样的有关误差方…

Git安装详细教程(win11)

Git安装详细教程&#xff08;win11&#xff09; 一、下载二、安装三、配置 一、下载 官网下载&#xff1a;点击下载 网盘下载&#xff1a;点击下载 二、安装 双击程序运行&#xff0c;点击next 选择安装路径&#xff0c;我安装在了D盘&#xff0c;如下图所示&#xff0c;…

Server - 测试 GPU 的显卡使用率与张量之间的关系

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://blog.csdn.net/caroline_wendy/article/details/131331049 NVIDIA A100 是一款基于 Ampere 架构的高性能 GPU&#xff0c;专为 AI、数据分析和高性能计算等应用场景设计。NVIDIA A100 具…

【Java入门基础第10天】Java程序结构

Java程序结构 1、Java程序结构什么是Java程序结构&#xff1f; 2、Java注释1、单行注释2、多行注释3、多行注释 1、Java程序结构 什么是Java程序结构&#xff1f; 程序是由程序块组成的&#xff0c;所谓程序块&#xff0c;指的是使用一对{ } 包含若干的代码的总称。程序块是由…

【Python 基础篇】Python 变量与数据类型以及数据类型转换

文章目录 引言一、变量和常见数据类型1. 变量2. 常见数据类型 二、数据类型转换结论 引言 Python 是一种广泛应用于各个领域的高级编程语言&#xff0c;其灵活性和易用性使其成为众多开发者的首选。在 Python 中&#xff0c;变量是程序中存储数据的基本单元&#xff0c;而数据…

数据结构学习笔记:概论

✨博文作者&#xff1a;烟雨孤舟 &#x1f496; 喜欢的可以 点赞 收藏 关注哦~~ ✍️ 作者简介: 一个热爱大数据的学习者 ✍️ 笔记简介&#xff1a;作为大数据爱好者&#xff0c;以下是个人总结的学习笔记&#xff0c;如有错误&#xff0c;请多多指教&#xff01; 目录 数据结…

哈工大计算网络课程网络层协议之:IP数据报、IP子网、子网掩码详解

哈工大计算网络课程网络层协议之&#xff1a;IP数据报、IP子网、子网掩码详解 文章目录 哈工大计算网络课程网络层协议之&#xff1a;IP数据报、IP子网、子网掩码详解Internet网络层IP数据报&#xff08;分组&#xff09;格式IP数据报分片最大传输单元&#xff08;MTU&#xff…

网工内推 | 云计算专场,有通讯补助,13薪,带薪年假

01 中电信数智科技有限公司湖南分公司 招聘岗位&#xff1a;云计算工程师 职责描述&#xff1a; 1、云计算平台环境的搭建&#xff1a;安装、部署、配置、优化&#xff1b; 2、云计算平台有关的解决方案、平台测试&#xff1b; 3、桌面云和虚拟化项目的交付和维护工作&#…

功能强大却十分小众的5款软件

有些软件虽然功能强大&#xff0c;使用便捷&#xff0c;但是却没有得到广泛的关注和推荐&#xff0c;这并不意味着它们不值得一试&#xff0c;相反&#xff0c;它们可能是你不知道的宝藏。我的任务就是要把这些隐藏的好软件分享给大家。 轻量级笔记——CintaNotes CintaNotes…

PCB设计系列分享-LDO的布局布线指南

目录 概要 整体架构流程 技术名词解释 技术细节 小结 概要 “噪声问题” 这是每位电路板设计师都会听到的四个字。为了解决噪声问题&#xff0c;往往要花费数小时的时间进行实验室测试&#xff0e;以便揪出元凶&#xff0c;但最终却发现&#xff0c;噪声是由开关电源的布局不当…

27-1BP_Adaboost强分类器公司财务预管建模——强预测器和弱预测器(附matlab程序)

1.简述 学习目标&#xff1a;进行强预测器和弱预测器的训练来减小误差 BP_Adaboost模型 Adaboost算法的思想是合并多个“弱”分类器的输出以产生有效分类。其主要步骤为&#xff1a;首先给出弱学习算法和样本空间&#xff0c;从样本空间中找出m组训练数据&#xff0c;每组训练…

0015-TIPS-pawnyable : userfaultfd

原文 Linux Kernel PWN | 040303 Pawnyable之userfaultfd userfaultfdの利用 题目下载 代码分析 #include <linux/cdev.h> #include <linux/fs.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/random.h> #include &…

学生党可以做的暑期兼职,让暑假生活不再躺平

夏季期间有几种兼职工作可供选择&#xff1a; 1.许多超市在暑假期间会雇佣一些短期工来从事收银和理货等工作&#xff0c;每小时报酬一般约为15元左右&#xff0c;算是不错的待遇。 2.在暑假期间&#xff0c;你可以寻找一些人力资源工作&#xff0c;借助他们的帮助来安排一些临…

常用工具类之AJ-Captcha入门

1.引入MAVEN依赖 若依官方引入的是1.2.7版本。我选择了目前最常用的1.3.0版本。 在项目中给的 ruoyi-framework\pom.xml 添加依赖 <!-- anji滑块验证码 --><dependency><groupId>com.anji-plus</groupId><artifactId>spring-boot-starter-captc…

android native hook简介

&#xff08;一&#xff09;简介 android中的 native Hook是一个吸引人的技术点&#xff0c;诱使和带来很多特别精彩的想法和体验&#xff0c;在调试和其他场景中有很多应用。 本文代码基本都来自github上的源码&#xff0c;只做了适当的编辑和修改&#xff0c;主要是为了验证…