持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第7天,点击查看活动详情。
SpringBatch从入门到精通-2-StepScope作用域和用法【掘金日新计划】
SpringBatch从入门到精通-3-并行处理【掘金日新计划】
SpringBatch从入门到精通-3.2-并行处理-远程分区【掘金日新计划】
SpringBatch从入门到精通-3.3-并行处理-远程分区(消息聚合)【掘金日新计划】
SpringBatch从入门到精通-4 监控和指标【掘金日新计划】
1. 提供了哪些指标
指标收集不需要任何特定配置。框架提供的所有指标都在 前缀 下注册在Micrometer 的全局注册表中。spring.batch下表详细解释了所有指标:
| 指标名称 | 类型 | 描述 | 标签 |
|---|---|---|---|
spring.batch.job | TIMER | 作业执行的持续时间 | name,status |
spring.batch.job.active | LONG_TASK_TIMER | 目前活跃得数量 | name |
spring.batch.step | TIMER | 步骤执行的持续时间 | name, job.name,status |
spring.batch.item.read | TIMER | 阅读时长 | job.name, step.name,status |
spring.batch.item.process | TIMER | 处理的持续时间 | job.name, step.name,status |
spring.batch.chunk.write | TIMER | 块写入的持续时间 | job.name, step.name,status |
2. 指标是如何定义的
SpringBatch 主要是提供了一个org.springframework.batch.core.metrics.BatchMetrics.java
定义了两种类型Timer和LongTaskTimer
spring.batch.job.active在AbstractJob#execute
结束的时候调用timerSamep.stop方法。
使用/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;
}