micrometer自定義metrics


micrometer提供了基於Java的monitor facade,其與springboot應用和prometheus的集成方式如下圖展示

上圖中展示的很清楚,應用通過micrometer采集和暴露監控端點給prometheus,prometheus通過pull模式來采集監控時序數據信息。之后作為數據源提供給grafana進行展示。

micrometer支持的度量方式及在springboot中的應用示例

Counter
Counter(計數器)簡單理解就是一種只增不減的計數器。它通常用於記錄服務的請求數量、完成的任務數量、錯誤的發生數量等等。

package com.dxz.producter.monitor;

import org.springframework.stereotype.Service;

import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.Metrics;

@Service("collectorService")
public class CollectorService {

    static final Counter userCounter = Metrics.counter("user.counter.total", "services", "demo");

    public void processCollectResult() throws InterruptedException {

        while (true) {
            userCounter.increment(1D);
        }
    }
}

Gauge
Gauge(儀表)是一個表示單個數值的度量,它可以表示任意地上下移動的數值測量。Gauge通常用於變動的測量值,如當前的內存使用情況,同時也可以測量上下移動的"計數",比如隊列中的消息數量。

package com.dxz.producter.monitor;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

import org.springframework.stereotype.Component;

import io.micrometer.core.instrument.Gauge;
import io.micrometer.core.instrument.ImmutableTag;
import io.micrometer.core.instrument.Metrics;
import io.micrometer.core.instrument.Tag;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;

@Component("passCaseMetric")
public class PassCaseMetric {

    List<Tag> init() {
        ArrayList<Tag> list = new ArrayList() {
        };
        list.add(new ImmutableTag("service", "demo"));
        return list;
    }

    AtomicInteger atomicInteger = new AtomicInteger(0);

    Gauge passCaseGuage = Gauge.builder("pass.cases.guage", atomicInteger, AtomicInteger::get).tag("service", "demo")
            .description("pass cases guage of demo").register(new SimpleMeterRegistry());

    AtomicInteger passCases = Metrics.gauge("pass.cases.guage.value", init(), atomicInteger);

    public void handleMetrics() {

        while (true) {
            if (System.currentTimeMillis() % 2 == 0) {
                passCases.addAndGet(100);
                System.out.println("ADD + " + passCaseGuage.measure() + " : " + passCases);
            } else {
                int val = passCases.addAndGet(-100);
                if (val < 0) {
                    passCases.set(1);
                }
                System.out.println("DECR - " + passCaseGuage.measure() + " : " + passCases);
            }
        }

    }

}

增加一個controller,觸發他們:

package com.dxz.producter.web;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import com.dxz.producter.monitor.CollectorService;
import com.dxz.producter.monitor.PassCaseMetric;

@RestController
@RequestMapping("/monitor")
public class MonitorController {

    @Autowired
    CollectorService collectorService;
    
    @Autowired
    PassCaseMetric passCaseMetric;
    
    @RequestMapping(value = "/counter", method = RequestMethod.GET)
    public String counter() throws InterruptedException {
        collectorService.processCollectResult();
        return "+1";
    }
    
    @RequestMapping(value = "/gauge", method = RequestMethod.GET)
    public String gauge() throws InterruptedException {
        passCaseMetric.handleMetrics();
        return "+gauge";
    }
    
}

 

啟動springboot應用,可以在http://host:port/actuator/prometheus 看到端點收集到的數據。其他的也是類似的不再一一截圖展示。

這里使用了一個true的循環用來展示不斷更新的效果。

 

同樣的可以在grafana中看到監控展示信息

 

Timer
Timer(計時器)同時測量一個特定的代碼邏輯塊的調用(執行)速度和它的時間分布。簡單來說,就是在調用結束的時間點記錄整個調用塊執行的總時間,適用於測量短時間執行的事件的耗時分布,例如消息隊列消息的消費速率。

@Test
    public void testTimerSample(){
        Timer timer = Timer.builder("timer")
                .tag("timer", "timersample")
                .description("timer sample test.")
                .register(new SimpleMeterRegistry());
 
        for(int i=0; i<2; i++) {
            timer.record(() -> {
                try {
                    TimeUnit.SECONDS.sleep(2);
                }catch (InterruptedException e){
 
                }
 
            });
        }
 
        System.out.println(timer.count());
        System.out.println(timer.measure());
        System.out.println(timer.totalTime(TimeUnit.SECONDS));
        System.out.println(timer.mean(TimeUnit.SECONDS));
        System.out.println(timer.max(TimeUnit.SECONDS));
    }

響應數據

2
[Measurement{statistic='COUNT', value=2.0}, Measurement{statistic='TOTAL_TIME', value=4.005095763}, Measurement{statistic='MAX', value=2.004500494}]
4.005095763
2.0025478815
2.004500494

Summary
Summary(摘要)用於跟蹤事件的分布。它類似於一個計時器,但更一般的情況是,它的大小並不一定是一段時間的測量值。在micrometer中,對應的類是DistributionSummary,它的用法有點像Timer,但是記錄的值是需要直接指定,而不是通過測量一個任務的執行時間。

@Test
    public void testSummary(){
 
        DistributionSummary summary = DistributionSummary.builder("summary")
                .tag("summary", "summarySample")
                .description("summary sample test")
                .register(new SimpleMeterRegistry());
 
        summary.record(2D);
        summary.record(3D);
        summary.record(4D);
 
        System.out.println(summary.count());
        System.out.println(summary.measure());
        System.out.println(summary.max());
        System.out.println(summary.mean());
        System.out.println(summary.totalAmount());
    }

響應數據:

3
[Measurement{statistic='COUNT', value=3.0}, Measurement{statistic='TOTAL', value=9.0}, Measurement{statistic='MAX', value=4.0}]
4.0
3.0
9.0

 




本文主要研究下如何使用自定義micrometer的metrics

實例

DemoMetrics

public class DemoMetrics implements MeterBinder { AtomicInteger count = new AtomicInteger(0); @Override public void bindTo(MeterRegistry meterRegistry) { Gauge.builder("demo.count", count, c -> c.incrementAndGet()) .tags("host", "localhost") .description("demo of custom meter binder") .register(meterRegistry); } }
這里實現了MeterBinder接口的bindTo方法,將要采集的指標注冊到MeterRegistry

注冊

  • 原始方式
new DemoMetrics().bindTo(registry);
  • springboot autoconfigure
@Bean public DemoMetrics demoMetrics(){ return new DemoMetrics(); }
在springboot只要標注下bean,注入到spring容器后,springboot會自動注冊到registry。springboot已經幫你初始化了包括UptimeMetrics等一系列metrics。詳見源碼解析部分。

驗證

curl -i http://localhost:8080/actuator/metrics/demo.count

返回實例

{
  "name": "demo.count", "measurements": [ { "statistic": "VALUE", "value": 6 } ], "availableTags": [ { "tag": "host", "values": [ "localhost" ] } ] }

源碼解析

MetricsAutoConfiguration

spring-boot-actuator-autoconfigure-2.0.0.RELEASE-sources.jar!/org/springframework/boot/actuate/autoconfigure/metrics/MetricsAutoConfiguration.java

@Configuration @ConditionalOnClass(Timed.class) @EnableConfigurationProperties(MetricsProperties.class) @AutoConfigureBefore(CompositeMeterRegistryAutoConfiguration.class) public class MetricsAutoConfiguration { @Bean @ConditionalOnMissingBean public Clock micrometerClock() { return Clock.SYSTEM; } @Bean public static MeterRegistryPostProcessor meterRegistryPostProcessor( ApplicationContext context) { return new MeterRegistryPostProcessor(context); } @Bean @Order(0) public PropertiesMeterFilter propertiesMeterFilter(MetricsProperties properties) { return new PropertiesMeterFilter(properties); } @Configuration @ConditionalOnProperty(value = "management.metrics.binders.jvm.enabled", matchIfMissing = true) static class JvmMeterBindersConfiguration { @Bean @ConditionalOnMissingBean public JvmGcMetrics jvmGcMetrics() { return new JvmGcMetrics(); } @Bean @ConditionalOnMissingBean public JvmMemoryMetrics jvmMemoryMetrics() { return new JvmMemoryMetrics(); } @Bean @ConditionalOnMissingBean public JvmThreadMetrics jvmThreadMetrics() { return new JvmThreadMetrics(); } @Bean @ConditionalOnMissingBean public ClassLoaderMetrics classLoaderMetrics() { return new ClassLoaderMetrics(); } } @Configuration static class MeterBindersConfiguration { @Bean @ConditionalOnClass(name = { "ch.qos.logback.classic.LoggerContext", "org.slf4j.LoggerFactory" }) @Conditional(LogbackLoggingCondition.class) @ConditionalOnMissingBean(LogbackMetrics.class) @ConditionalOnProperty(value = "management.metrics.binders.logback.enabled", matchIfMissing = true) public LogbackMetrics logbackMetrics() { return new LogbackMetrics(); } @Bean @ConditionalOnProperty(value = "management.metrics.binders.uptime.enabled", matchIfMissing = true) @ConditionalOnMissingBean public UptimeMetrics uptimeMetrics() { return new UptimeMetrics(); } @Bean @ConditionalOnProperty(value = "management.metrics.binders.processor.enabled", matchIfMissing = true) @ConditionalOnMissingBean public ProcessorMetrics processorMetrics() { return new ProcessorMetrics(); } @Bean @ConditionalOnProperty(name = "management.metrics.binders.files.enabled", matchIfMissing = true) @ConditionalOnMissingBean public FileDescriptorMetrics fileDescriptorMetrics() { return new FileDescriptorMetrics(); } } static class LogbackLoggingCondition extends SpringBootCondition { @Override public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { ILoggerFactory loggerFactory = LoggerFactory.getILoggerFactory(); ConditionMessage.Builder message = ConditionMessage .forCondition("LogbackLoggingCondition"); if (loggerFactory instanceof LoggerContext) { return ConditionOutcome.match( message.because("ILoggerFactory is a Logback LoggerContext")); } return ConditionOutcome .noMatch(message.because("ILoggerFactory is an instance of " + loggerFactory.getClass().getCanonicalName())); } } }
可以看到這里注冊了好多metrics,比如UptimeMetrics,JvmGcMetrics,ProcessorMetrics,FileDescriptorMetrics等

這里重點看使用@Bean標注了MeterRegistryPostProcessor

MeterRegistryPostProcessor

spring-boot-actuator-autoconfigure-2.0.0.RELEASE-sources.jar!/org/springframework/boot/actuate/autoconfigure/metrics/MeterRegistryPostProcessor.java

class MeterRegistryPostProcessor implements BeanPostProcessor { private final ApplicationContext context; private volatile MeterRegistryConfigurer configurer; MeterRegistryPostProcessor(ApplicationContext context) { this.context = context; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { if (bean instanceof MeterRegistry) { getConfigurer().configure((MeterRegistry) bean); } return bean; } @SuppressWarnings("unchecked") private MeterRegistryConfigurer getConfigurer() { if (this.configurer == null) { this.configurer = new MeterRegistryConfigurer(beansOfType(MeterBinder.class), beansOfType(MeterFilter.class), (Collection<MeterRegistryCustomizer<?>>) (Object) beansOfType( MeterRegistryCustomizer.class), this.context.getBean(MetricsProperties.class).isUseGlobalRegistry()); } return this.configurer; } private <T> Collection<T> beansOfType(Class<T> type) { return this.context.getBeansOfType(type).values(); } }
可以看到這里new了一個MeterRegistryConfigurer,重點注意這里使用beansOfType(MeterBinder.class)方法的返回值給其構造器

MeterRegistryConfigurer

spring-boot-actuator-autoconfigure-2.0.0.RELEASE-sources.jar!/org/springframework/boot/actuate/autoconfigure/metrics/MeterRegistryConfigurer.java

class MeterRegistryConfigurer { private final Collection<MeterRegistryCustomizer<?>> customizers; private final Collection<MeterFilter> filters; private final Collection<MeterBinder> binders; private final boolean addToGlobalRegistry; MeterRegistryConfigurer(Collection<MeterBinder> binders, Collection<MeterFilter> filters, Collection<MeterRegistryCustomizer<?>> customizers, boolean addToGlobalRegistry) { this.binders = (binders != null ? binders : Collections.emptyList()); this.filters = (filters != null ? filters : Collections.emptyList()); this.customizers = (customizers != null ? customizers : Collections.emptyList()); this.addToGlobalRegistry = addToGlobalRegistry; } void configure(MeterRegistry registry) { if (registry instanceof CompositeMeterRegistry) { return; } // Customizers must be applied before binders, as they may add custom // tags or alter timer or summary configuration. customize(registry); addFilters(registry); addBinders(registry); if (this.addToGlobalRegistry && registry != Metrics.globalRegistry) { Metrics.addRegistry(registry); } } @SuppressWarnings("unchecked") private void customize(MeterRegistry registry) { LambdaSafe.callbacks(MeterRegistryCustomizer.class, this.customizers, registry) .withLogger(MeterRegistryConfigurer.class) .invoke((customizer) -> customizer.customize(registry)); } private void addFilters(MeterRegistry registry) { this.filters.forEach(registry.config()::meterFilter); } private void addBinders(MeterRegistry registry) { this.binders.forEach((binder) -> binder.bindTo(registry)); } }
可以看到configure方法里頭調用了addBinders,也就是把托管給spring容器的MeterBinder實例bindTo到meterRegistry

小結

springboot2引入的micrometer,自定義metrics只需要實現MeterBinder接口,然后托管給spring即可,springboot的autoconfigure幫你自動注冊到meterRegistry。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM