项目运行指标:micrometer自定义metrics

micrometer自定义metrics

micrometer提供了基于Java的monitor facade,其与springboot应用和prometheus的集成方式如下图展示

上图中展示的很清楚,应用通过micrometer采集和暴露监控端点给prometheus,prometheus通过pull模式来采集监控时序数据信息。之后作为数据源提供给grafana进行展示。

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

Counter
Counter(计数器)简单理解就是一种只增不减的计数器。它通常用于记录服务的请求数量、完成的任务数量、错误的发生数量等等。

 
  1. package com.dxz.producter.monitor;
  2.  
  3. import org.springframework.stereotype.Service;
  4.  
  5. import io.micrometer.core.instrument.Counter;
  6. import io.micrometer.core.instrument.Metrics;
  7.  
  8. @Service("collectorService")
  9. public class CollectorService {
  10.  
  11. static final Counter userCounter = Metrics.counter("user.counter.total", "services", "demo");
  12.  
  13. public void processCollectResult() throws InterruptedException {
  14.  
  15. while (true) {
  16. userCounter.increment(1D);
  17. }
  18. }
  19. }
 

Gauge
Gauge(仪表)是一个表示单个数值的度量,它可以表示任意地上下移动的数值测量。Gauge通常用于变动的测量值,如当前的内存使用情况,同时也可以测量上下移动的"计数",比如队列中的消息数量。

 
  1. package com.dxz.producter.monitor;
  2.  
  3. import java.util.ArrayList;
  4. import java.util.List;
  5. import java.util.concurrent.atomic.AtomicInteger;
  6.  
  7. import org.springframework.stereotype.Component;
  8.  
  9. import io.micrometer.core.instrument.Gauge;
  10. import io.micrometer.core.instrument.ImmutableTag;
  11. import io.micrometer.core.instrument.Metrics;
  12. import io.micrometer.core.instrument.Tag;
  13. import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
  14.  
  15. @Component("passCaseMetric")
  16. public class PassCaseMetric {
  17.  
  18. List<Tag> init() {
  19. ArrayList<Tag> list = new ArrayList() {
  20. };
  21. list.add(new ImmutableTag("service", "demo"));
  22. return list;
  23. }
  24.  
  25. AtomicInteger atomicInteger = new AtomicInteger(0);
  26.  
  27. Gauge passCaseGuage = Gauge.builder("pass.cases.guage", atomicInteger, AtomicInteger::get).tag("service", "demo")
  28. .description("pass cases guage of demo").register(new SimpleMeterRegistry());
  29.  
  30. AtomicInteger passCases = Metrics.gauge("pass.cases.guage.value", init(), atomicInteger);
  31.  
  32. public void handleMetrics() {
  33.  
  34. while (true) {
  35. if (System.currentTimeMillis() % 2 == 0) {
  36. passCases.addAndGet(100);
  37. System.out.println("ADD + " + passCaseGuage.measure() + " : " + passCases);
  38. } else {
  39. int val = passCases.addAndGet(-100);
  40. if (val < 0) {
  41. passCases.set(1);
  42. }
  43. System.out.println("DECR - " + passCaseGuage.measure() + " : " + passCases);
  44. }
  45. }
  46.  
  47. }
  48.  
  49. }
 
 

增加一个controller,触发他们:

 
  1. package com.dxz.producter.web;
  2.  
  3. import org.springframework.beans.factory.annotation.Autowired;
  4. import org.springframework.web.bind.annotation.RequestMapping;
  5. import org.springframework.web.bind.annotation.RequestMethod;
  6. import org.springframework.web.bind.annotation.RestController;
  7.  
  8. import com.dxz.producter.monitor.CollectorService;
  9. import com.dxz.producter.monitor.PassCaseMetric;
  10.  
  11. @RestController
  12. @RequestMapping("/monitor")
  13. public class MonitorController {
  14.  
  15. @Autowired
  16. CollectorService collectorService;
  17.  
  18. @Autowired
  19. PassCaseMetric passCaseMetric;
  20.  
  21. @RequestMapping(value = "/counter", method = RequestMethod.GET)
  22. public String counter() throws InterruptedException {
  23. collectorService.processCollectResult();
  24. return "+1";
  25. }
  26.  
  27. @RequestMapping(value = "/gauge", method = RequestMethod.GET)
  28. public String gauge() throws InterruptedException {
  29. passCaseMetric.handleMetrics();
  30. return "+gauge";
  31. }
  32.  
  33. }
 

启动springboot应用,可以在http://host:port/actuator/prometheus 看到端点收集到的数据。其他的也是类似的不再一一截图展示。

这里使用了一个true的循环用来展示不断更新的效果。

 

同样的可以在grafana中看到监控展示信息

 

Timer
Timer(计时器)同时测量一个特定的代码逻辑块的调用(执行)速度和它的时间分布。简单来说,就是在调用结束的时间点记录整个调用块执行的总时间,适用于测量短时间执行的事件的耗时分布,例如消息队列消息的消费速率。

 

 
  1. @Test
  2. public void testTimerSample(){
  3. Timer timer = Timer.builder("timer")
  4. .tag("timer", "timersample")
  5. .description("timer sample test.")
  6. .register(new SimpleMeterRegistry());
  7.  
  8. for(int i=0; i<2; i++) {
  9. timer.record(() -> {
  10. try {
  11. TimeUnit.SECONDS.sleep(2);
  12. }catch (InterruptedException e){
  13.  
  14. }
  15.  
  16. });
  17. }
  18.  
  19. System.out.println(timer.count());
  20. System.out.println(timer.measure());
  21. System.out.println(timer.totalTime(TimeUnit.SECONDS));
  22. System.out.println(timer.mean(TimeUnit.SECONDS));
  23. System.out.println(timer.max(TimeUnit.SECONDS));
  24. }
 

响应数据

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,但是记录的值是需要直接指定,而不是通过测量一个任务的执行时间。

 

 
  1. @Test
  2. public void testSummary(){
  3.  
  4. DistributionSummary summary = DistributionSummary.builder("summary")
  5. .tag("summary", "summarySample")
  6. .description("summary sample test")
  7. .register(new SimpleMeterRegistry());
  8.  
  9. summary.record(2D);
  10. summary.record(3D);
  11. summary.record(4D);
  12.  
  13. System.out.println(summary.count());
  14. System.out.println(summary.measure());
  15. System.out.println(summary.max());
  16. System.out.println(summary.mean());
  17. System.out.println(summary.totalAmount());
  18. }
 

 

响应数据:

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

 
  1. public class DemoMetrics implements MeterBinder {
  2. AtomicInteger count = new AtomicInteger(0);
  3.  
  4. @Override
  5. public void bindTo(MeterRegistry meterRegistry) {
  6. Gauge.builder("demo.count", count, c -> c.incrementAndGet())
  7. .tags("host", "localhost")
  8. .description("demo of custom meter binder")
  9. .register(meterRegistry);
  10. }
  11. }
 
这里实现了MeterBinder接口的bindTo方法,将要采集的指标注册到MeterRegistry

注册

  • 原始方式
new DemoMetrics().bindTo(registry);
  • springboot autoconfigure
 
  1. @Bean
  2. public DemoMetrics demoMetrics(){
  3. return new DemoMetrics();
  4. }
 
在springboot只要标注下bean,注入到spring容器后,springboot会自动注册到registry。springboot已经帮你初始化了包括UptimeMetrics等一系列metrics。详见源码解析部分。

验证

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

返回实例

 
  1. {
  2. "name": "demo.count",
  3. "measurements": [
  4. {
  5. "statistic": "VALUE",
  6. "value": 6
  7. }
  8. ],
  9. "availableTags": [
  10. {
  11. "tag": "host",
  12. "values": [
  13. "localhost"
  14. ]
  15. }
  16. ]
  17. }
 

源码解析

MetricsAutoConfiguration

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

 
  1. @Configuration
  2. @ConditionalOnClass(Timed.class)
  3. @EnableConfigurationProperties(MetricsProperties.class)
  4. @AutoConfigureBefore(CompositeMeterRegistryAutoConfiguration.class)
  5. public class MetricsAutoConfiguration {
  6.  
  7. @Bean
  8. @ConditionalOnMissingBean
  9. public Clock micrometerClock() {
  10. return Clock.SYSTEM;
  11. }
  12.  
  13. @Bean
  14. public static MeterRegistryPostProcessor meterRegistryPostProcessor(
  15. ApplicationContext context) {
  16. return new MeterRegistryPostProcessor(context);
  17. }
  18.  
  19. @Bean
  20. @Order(0)
  21. public PropertiesMeterFilter propertiesMeterFilter(MetricsProperties properties) {
  22. return new PropertiesMeterFilter(properties);
  23. }
  24.  
  25. @Configuration
  26. @ConditionalOnProperty(value = "management.metrics.binders.jvm.enabled", matchIfMissing = true)
  27. static class JvmMeterBindersConfiguration {
  28.  
  29. @Bean
  30. @ConditionalOnMissingBean
  31. public JvmGcMetrics jvmGcMetrics() {
  32. return new JvmGcMetrics();
  33. }
  34.  
  35. @Bean
  36. @ConditionalOnMissingBean
  37. public JvmMemoryMetrics jvmMemoryMetrics() {
  38. return new JvmMemoryMetrics();
  39. }
  40.  
  41. @Bean
  42. @ConditionalOnMissingBean
  43. public JvmThreadMetrics jvmThreadMetrics() {
  44. return new JvmThreadMetrics();
  45. }
  46.  
  47. @Bean
  48. @ConditionalOnMissingBean
  49. public ClassLoaderMetrics classLoaderMetrics() {
  50. return new ClassLoaderMetrics();
  51. }
  52.  
  53. }
  54.  
  55. @Configuration
  56. static class MeterBindersConfiguration {
  57.  
  58. @Bean
  59. @ConditionalOnClass(name = { "ch.qos.logback.classic.LoggerContext",
  60. "org.slf4j.LoggerFactory" })
  61. @Conditional(LogbackLoggingCondition.class)
  62. @ConditionalOnMissingBean(LogbackMetrics.class)
  63. @ConditionalOnProperty(value = "management.metrics.binders.logback.enabled", matchIfMissing = true)
  64. public LogbackMetrics logbackMetrics() {
  65. return new LogbackMetrics();
  66. }
  67.  
  68. @Bean
  69. @ConditionalOnProperty(value = "management.metrics.binders.uptime.enabled", matchIfMissing = true)
  70. @ConditionalOnMissingBean
  71. public UptimeMetrics uptimeMetrics() {
  72. return new UptimeMetrics();
  73. }
  74.  
  75. @Bean
  76. @ConditionalOnProperty(value = "management.metrics.binders.processor.enabled", matchIfMissing = true)
  77. @ConditionalOnMissingBean
  78. public ProcessorMetrics processorMetrics() {
  79. return new ProcessorMetrics();
  80. }
  81.  
  82. @Bean
  83. @ConditionalOnProperty(name = "management.metrics.binders.files.enabled", matchIfMissing = true)
  84. @ConditionalOnMissingBean
  85. public FileDescriptorMetrics fileDescriptorMetrics() {
  86. return new FileDescriptorMetrics();
  87. }
  88.  
  89. }
  90.  
  91. static class LogbackLoggingCondition extends SpringBootCondition {
  92.  
  93. @Override
  94. public ConditionOutcome getMatchOutcome(ConditionContext context,
  95. AnnotatedTypeMetadata metadata) {
  96. ILoggerFactory loggerFactory = LoggerFactory.getILoggerFactory();
  97. ConditionMessage.Builder message = ConditionMessage
  98. .forCondition("LogbackLoggingCondition");
  99. if (loggerFactory instanceof LoggerContext) {
  100. return ConditionOutcome.match(
  101. message.because("ILoggerFactory is a Logback LoggerContext"));
  102. }
  103. return ConditionOutcome
  104. .noMatch(message.because("ILoggerFactory is an instance of "
  105. + loggerFactory.getClass().getCanonicalName()));
  106. }
  107.  
  108. }
  109.  
  110. }
 
可以看到这里注册了好多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

 
  1. class MeterRegistryPostProcessor implements BeanPostProcessor {
  2.  
  3. private final ApplicationContext context;
  4.  
  5. private volatile MeterRegistryConfigurer configurer;
  6.  
  7. MeterRegistryPostProcessor(ApplicationContext context) {
  8. this.context = context;
  9. }
  10.  
  11. @Override
  12. public Object postProcessAfterInitialization(Object bean, String beanName)
  13. throws BeansException {
  14. if (bean instanceof MeterRegistry) {
  15. getConfigurer().configure((MeterRegistry) bean);
  16. }
  17. return bean;
  18. }
  19.  
  20. @SuppressWarnings("unchecked")
  21. private MeterRegistryConfigurer getConfigurer() {
  22. if (this.configurer == null) {
  23. this.configurer = new MeterRegistryConfigurer(beansOfType(MeterBinder.class),
  24. beansOfType(MeterFilter.class),
  25. (Collection<MeterRegistryCustomizer<?>>) (Object) beansOfType(
  26. MeterRegistryCustomizer.class),
  27. this.context.getBean(MetricsProperties.class).isUseGlobalRegistry());
  28. }
  29. return this.configurer;
  30. }
  31.  
  32. private <T> Collection<T> beansOfType(Class<T> type) {
  33. return this.context.getBeansOfType(type).values();
  34. }
  35.  
  36. }
 
可以看到这里new了一个MeterRegistryConfigurer,重点注意这里使用beansOfType(MeterBinder.class)方法的返回值给其构造器

MeterRegistryConfigurer

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

 
  1. class MeterRegistryConfigurer {
  2.  
  3. private final Collection<MeterRegistryCustomizer<?>> customizers;
  4.  
  5. private final Collection<MeterFilter> filters;
  6.  
  7. private final Collection<MeterBinder> binders;
  8.  
  9. private final boolean addToGlobalRegistry;
  10.  
  11. MeterRegistryConfigurer(Collection<MeterBinder> binders,
  12. Collection<MeterFilter> filters,
  13. Collection<MeterRegistryCustomizer<?>> customizers,
  14. boolean addToGlobalRegistry) {
  15. this.binders = (binders != null ? binders : Collections.emptyList());
  16. this.filters = (filters != null ? filters : Collections.emptyList());
  17. this.customizers = (customizers != null ? customizers : Collections.emptyList());
  18. this.addToGlobalRegistry = addToGlobalRegistry;
  19. }
  20.  
  21. void configure(MeterRegistry registry) {
  22. if (registry instanceof CompositeMeterRegistry) {
  23. return;
  24. }
  25. // Customizers must be applied before binders, as they may add custom
  26. // tags or alter timer or summary configuration.
  27. customize(registry);
  28. addFilters(registry);
  29. addBinders(registry);
  30. if (this.addToGlobalRegistry && registry != Metrics.globalRegistry) {
  31. Metrics.addRegistry(registry);
  32. }
  33. }
  34.  
  35. @SuppressWarnings("unchecked")
  36. private void customize(MeterRegistry registry) {
  37. LambdaSafe.callbacks(MeterRegistryCustomizer.class, this.customizers, registry)
  38. .withLogger(MeterRegistryConfigurer.class)
  39. .invoke((customizer) -> customizer.customize(registry));
  40. }
  41.  
  42. private void addFilters(MeterRegistry registry) {
  43. this.filters.forEach(registry.config()::meterFilter);
  44. }
  45.  
  46. private void addBinders(MeterRegistry registry) {
  47. this.binders.forEach((binder) -> binder.bindTo(registry));
  48. }
  49.  
  50. }
 
可以看到configure方法里头调用了addBinders,也就是把托管给spring容器的MeterBinder实例bindTo到meterRegistry

小结

springboot2引入的micrometer,自定义metrics只需要实现MeterBinder接口,然后托管给spring即可,springboot的autoconfigure帮你自动注册到meterRegistry。

 

项目运行指标:micrometer自定义metrics_meterregistrycustomizer-CSDN博客

SpringBoot结合prometheus自定义埋点实践_springboot prometheus 自定义-CSDN博客

posted @ 2025-02-05 12:29  CharyGao  阅读(257)  评论(0)    收藏  举报