SpringBatch从入门到精通-4.2 监控和指标-原理【掘金日新计划】

425 阅读5分钟

持续创作,加速成长,6月更文活动来啦!| 掘金·日新计划

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第7天,点击查看活动详情

SpringBatch从入门到精通-1【掘金日新计划】

SpringBatch从入门到精通-2-StepScope作用域和用法【掘金日新计划】

SpringBatch从入门到精通-3-并行处理【掘金日新计划】

SpringBatch从入门到精通-3.2-并行处理-远程分区【掘金日新计划】

SpringBatch从入门到精通-3.3-并行处理-远程分区(消息聚合)【掘金日新计划】

SpringBatch从入门到精通-4 监控和指标【掘金日新计划】

1. 提供了哪些指标

指标收集不需要任何特定配置。框架提供的所有指标都在 前缀 下注册在Micrometer 的全局注册表中。spring.batch下表详细解释了所有指标:

指标名称类型描述标签
spring.batch.jobTIMER作业执行的持续时间name,status
spring.batch.job.activeLONG_TASK_TIMER目前活跃得数量name
spring.batch.stepTIMER步骤执行的持续时间name, job.name,status
spring.batch.item.readTIMER阅读时长job.name, step.name,status
spring.batch.item.processTIMER处理的持续时间job.name, step.name,status
spring.batch.chunk.writeTIMER块写入的持续时间job.name, step.name,status

2. 指标是如何定义的

SpringBatch 主要是提供了一个org.springframework.batch.core.metrics.BatchMetrics.java

image-20220609142819670

定义了两种类型Timer和LongTaskTimer

spring.batch.job.active在AbstractJob#execute

image-20220609142755388

结束的时候调用timerSamep.stop方法。

image-20220609150212492

使用/actuator/metrics/spring.batch.job 查看

{
    "name": "spring.batch.job",
    "description": "Job duration",
    "baseUnit": "seconds",
    "measurements": [{
            "statistic": "COUNT",
            "value": 1
        },
        {
            "statistic": "TOTAL_TIME",
            "value": 0.0420251
        },
        {
            "statistic": "MAX",
            "value": 0.0420251
        }
    ],
    "availableTags": [{
            "tag": "name",
            "values": [
                "metricsJob"
            ]
        },
        {
            "tag": "region",
            "values": [
                "jackssybin"
            ]
        },
        {
            "tag": "status",
            "values": [
                "COMPLETED"
            ]
        }
    ]
}

3.指标是如何生效的

springboot核心包org.springframework.boot.actuate.autoconfigure.metrics

MetricsAutoConfiguration.java

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(Timed.class)
//得到 MetricsProperties 对应的配置属性 @ConfigurationProperties("management.metrics")
@EnableConfigurationProperties(MetricsProperties.class)
@AutoConfigureBefore(CompositeMeterRegistryAutoConfiguration.class)
public class MetricsAutoConfiguration {
   @Bean
   @ConditionalOnMissingBean
   public Clock micrometerClock() {
      return Clock.SYSTEM;
   }
   //向 spring 容器中注入 postProcessor 用于处理容器中的 MeterRegistry 类
    //得到一个 BeanPostProcessor,用来处理 MeterRegistry 类   
   //将所有的 MeterRegistry,添加到 Metrics.globalRegistry 中(如果 this.metricsProperties.getObject().isUseGlobalRegistry() 为 true)
   //将所有的 meterFilters,meterBinders 绑定到所有的 MeterRegistry 中
   //所以用户可以自定义  MeterRegistry,meterFilters,meterBinders
   @Bean
   public static MeterRegistryPostProcessor meterRegistryPostProcessor(ObjectProvider<MeterBinder> meterBinders,
         ObjectProvider<MeterFilter> meterFilters,
         ObjectProvider<MeterRegistryCustomizer<?>> meterRegistryCustomizers,
         ObjectProvider<MetricsProperties> metricsProperties, ApplicationContext applicationContext) {
      return new MeterRegistryPostProcessor(meterBinders, meterFilters, meterRegistryCustomizers, metricsProperties,
            applicationContext);
   }
​
   @Bean
   @Order(0)
    //向容器中注入 meter 过滤器
   public PropertiesMeterFilter propertiesMeterFilter(MetricsProperties properties) {
      return new PropertiesMeterFilter(properties);
   }
}
​

CompositeMeterRegistryAutoConfiguration.java

@Configuration(proxyBeanMethods = false)
@Import({ NoOpMeterRegistryConfiguration.class, CompositeMeterRegistryConfiguration.class })
@ConditionalOnClass(CompositeMeterRegistry.class)
public class CompositeMeterRegistryAutoConfiguration {
}

NoOpMeterRegistryConfiguration.java

如果容器中没有其他 MeterRegistry 类时,会注入这个 无操作 MeterRegistry 类

@Configuration(proxyBeanMethods = false)
@ConditionalOnBean(Clock.class)
@ConditionalOnMissingBean(MeterRegistry.class)
class NoOpMeterRegistryConfiguration {
   @Bean
   CompositeMeterRegistry noOpMeterRegistry(Clock clock) {
      return new CompositeMeterRegistry(clock);
   }
}

CompositeMeterRegistryConfiguration.java

当容器中有 MeterRegistry 并且没有一个 primary MeterRegistry 时,才注入默认的组合 MeterRegistry

@Configuration(proxyBeanMethods = false)
@Conditional(MultipleNonPrimaryMeterRegistriesCondition.class)
class CompositeMeterRegistryConfiguration {
   @Bean
   @Primary
   CompositeMeterRegistry compositeMeterRegistry(Clock clock, List<MeterRegistry> registries) {
      return new CompositeMeterRegistry(clock, registries);
   }
   static class MultipleNonPrimaryMeterRegistriesCondition extends NoneNestedConditions {
      MultipleNonPrimaryMeterRegistriesCondition() {
         super(ConfigurationPhase.REGISTER_BEAN);
      }
      //没有 REGISTER_BEAN.class  
      @ConditionalOnMissingBean(MeterRegistry.class)
      static class NoMeterRegistryCondition {}
      //只有一个 primary MeterRegistry 
      @ConditionalOnSingleCandidate(MeterRegistry.class)
      static class SingleInjectableMeterRegistry {}
   }
}

MeterRegistryPostProcessor.java

class MeterRegistryPostProcessor implements BeanPostProcessor {
​
   MeterRegistryPostProcessor(ObjectProvider<MeterBinder> meterBinders,
         ObjectProvider<MeterFilter> meterFilters,
         ObjectProvider<MeterRegistryCustomizer<?>> meterRegistryCustomizers,
         ObjectProvider<MetricsProperties> metricsProperties) {
      //查找得到容器中所有的  meterBinders/meterFilters/meterRegistryCustomizers
      this.meterBinders = meterBinders;
      this.meterFilters = meterFilters;
      this.meterRegistryCustomizers = meterRegistryCustomizers;
      //得到 metricsProperties 配置
      this.metricsProperties = metricsProperties;
   }
   //生成 bean 时,会调用  postProcessAfterInitialization 方法
   //具体时机请参考: 
   @Override
   public Object postProcessAfterInitialization(Object bean, String beanName)
         throws BeansException { 
      if (bean instanceof MeterRegistry) {
         //使用  MeterRegistryConfigurer 对 MeterRegistry 类做自定义操作
         getConfigurer().configure((MeterRegistry) bean);
      }
      return bean;
   }
   private MeterRegistryConfigurer getConfigurer() {
      if (this.configurer == null) {
         //通过几个参数创建  MeterRegistryConfigurer
         this.configurer = new MeterRegistryConfigurer(this.meterRegistryCustomizers,
               this.meterFilters, this.meterBinders,
               this.metricsProperties.getObject().isUseGlobalRegistry());
      }
      return this.configurer;
   }
}

MeterRegistryConfigurer.java

class MeterRegistryConfigurer {
    void configure(MeterRegistry registry) {
        // Customizers must be applied before binders, as they may add custom
        // tags or alter timer or summary configuration.
        customize(registry);
        addFilters(registry);//为所有 MeterRegistry 添加 Fitler
        addBinders(registry);//为 CompositeMeterRegistry 添加 Binder,如果没有 CompositeMeterRegistry,则为所有 registry 添加 binder
        if (this.addToGlobalRegistry && registry != Metrics.globalRegistry) {
            //如果需要加入 GlobalRegistry 且自身又不是 globalRegistry 则加入其中
            //globRegistry 是一个 CompositeMeterRegistry
            //这个 Metrics.globalRegistry 很关键
            Metrics.addRegistry(registry);
        }
    }
}

MetricsEndpointAutoConfiguration.java

  • 这个配置类向容器中注入了 metrics 的 endpoint,将所有 metrics 信息通过 web 端口暴露出去
  • 使得所有的 metrics 生效!
@Configuration
@ConditionalOnClass(Timed.class)
@ConditionalOnEnabledEndpoint(endpoint = MetricsEndpoint.class)
@AutoConfigureAfter({ MetricsAutoConfiguration.class,
      CompositeMeterRegistryAutoConfiguration.class })
public class MetricsEndpointAutoConfiguration {
   @Bean
   @ConditionalOnBean(MeterRegistry.class)
   //如果开发人员没有自定义 MetricsEndpoint 则创建一个 
   @ConditionalOnMissingBean
   //必须有一个 primary MeterRegistry,以这个为主 
   public MetricsEndpoint metricsEndpoint(MeterRegistry registry) {
      return new MetricsEndpoint(registry);
   }
}

获取所有 registry 名字

  • 重要变量:meterMap
private void collectNames(Set<String> names, MeterRegistry registry) {
   if (registry instanceof CompositeMeterRegistry) {
       //默认是组合的,搜集每个子 registry 下注册的 registry
      ((CompositeMeterRegistry) registry).getRegistries().forEach((member) -> collectNames(names, member));
   } else {
      registry.getMeters().stream().map(this::getName).forEach(names::add);
   }
}
//得到每个 registry 中注册的 meter
public List<Meter> getMeters() {
    //每个 registry 中的 meterMap 通过 getOrCreateMeter()、remove(Meter.Id id) 修改的
    //key 为每个 meter 的 id,我们取的是 value
    return Collections.unmodifiableList(new ArrayList<>(meterMap.values()));
}

获取特定 meter 的信息

  • 重要变量:meterMap、meter.getId().getTags()->获取meter 对应的所有 tags
  • 重要方法: meter.measure():返回 meter 对于的 List Map.merge(key,value,function):按照 function 合并相同 key 的value 值
@ReadOperation
public MetricResponse metric(@Selector String requiredMetricName, @Nullable List<String> tag) {
   // 将 tag 列表对,转换为 Tag 
   List<Tag> tags = parseTags(tag);
   // 通过 metric name 和 tags 找出对应的 Meters,4.1.2.1
   Collection<Meter> meters = findFirstMatchingMeters(this.registry, requiredMetricName, tags);
   if (meters.isEmpty()) {
      return null;
   }
   //4.1.2.2
   Map<Statistic, Double> samples = getSamples(meters);
   //获取所有的 tags 
   Map<String, Set<String>> availableTags = getAvailableTags(meters);
   //去除以参数传进来的 tags 
   tags.forEach((t) -> availableTags.remove(t.getKey()));
   Meter.Id meterId = meters.iterator().next().getId();
   //包装 metricName,meter description,baseUnit,samples,tags 
   return new MetricResponse(requiredMetricName, meterId.getDescription(), meterId.getBaseUnit(),
         asList(samples, Sample::new), asList(availableTags, AvailableTag::new));
}

获取所有 meters

private Stream<Meter> meterStream() {
    //通过 meter name 过滤得到对应的 meters
    Stream<Meter> meterStream = registry.getMeters().stream().filter(m -> nameMatches.test(m.getId().getName()));
	//通过 tags 过滤一些 meters
    if (!tags.isEmpty() || !requiredTagKeys.isEmpty()) {
        meterStream = meterStream.filter(m -> {
            boolean requiredKeysPresent = true;
            if (!requiredTagKeys.isEmpty()) {
                final List<String> tagKeys = new ArrayList<>();
                m.getId().getTags().forEach(t -> tagKeys.add(t.getKey()));
                requiredKeysPresent = tagKeys.containsAll(requiredTagKeys);
            }

            return requiredKeysPresent && m.getId().getTags().containsAll(tags);
        });
    }
	//最终返回 name、tags 对应的 meters
    return meterStream;
}

将 meters 转换为 Map<Statistic, Double> 形式

  • 从这里可以看出 一个 meter 的 所有类型必须匹配 Statistic 枚举类中的类型
private Map<Statistic, Double> getSamples(Collection<Meter> meters) {
   Map<Statistic, Double> samples = new LinkedHashMap<>();
   //遍历 meters,设置 samples,这些 meters 是经过 name 和 tags 过滤后的
   meters.forEach((meter) -> mergeMeasurements(samples, meter));
   return samples;
}

private void mergeMeasurements(Map<Statistic, Double> samples, Meter meter) {
    //调用 meter 的 measure 方法,遍历meter 返回的 List<Measurement>(每个 meter 实现类,都实现了这个方法)
    //measure() 方法将 meter 中计算数值的方法包装为 Measurement,由后面的 measurement.getValue() 调用,得到具体数值
    //gague 会调用构造时传入的方法,得到数值。其它 meter 一般会直接获取 value 的数值
    meter.measure().forEach((measurement) -> samples.merge(measurement.getStatistic(), measurement.getValue(),
                                                           mergeFunction(measurement.getStatistic())));
}
//对于一个meter 返回了 多个 Measurement,要遍历它们,按照 statistic 的类型进行 合并,合并规则如下
//即,如果有多个 count 类型,则合并这些 count 类型的值,作为总的 count 类型的值
private BiFunction<Double, Double, Double> mergeFunction(Statistic statistic) {
    return Statistic.MAX.equals(statistic) ? Double::max : Double::sum;
}