前言
核心服务需要集成 Prometheus 监控,为了防止 Prometheus 监控对接口性能的影响以及灵活的控制监控的接入与解除,故增加一个开关的配置来控制启用和禁用监控服务。本文主要介绍监控开关的配置步骤及中途遇到问题的解决过程。
在进入正文前,可先去了解下 Prometheus 监控的介绍及接入步骤,这不是本文的讨论范畴。
1、配置开关
启用配置类:
@Configuration
@ConditionalOnProperty(name = "management.prometheus.enabled", havingValue = "true")
@EnablePrometheusEndpoint
public class PrometheusEnableConfig extends WebMvcConfigurerAdapter implements CommandLineRunner {
//1、清空注册
//2、拦截器(自定义 Metrics 指标,并注册)
//3、注册 Hotspot 相关信息
}
禁用配置类:
@Configuration
@ConditionalOnProperty(name = "management.prometheus.enabled", havingValue = "false")
public class PrometheusDisableConfig {
//1、清空注册
}
开关的控制,主要是通过注解@ConditionalOnProperty
来完成的。
@ConditionalOnPro
这个注解能够控制某个 Configuration 是否生效,具体操作是通过其两个属性 name 以及 havingValue 来实现的。其中,name 用来从配置文件中读取某个属性值(这里取自 Apollo 的配置),如果该值为空,则返回 false ;如果值不为空,则将该值与 havingValue 指定的值进行比较,如果一样则返回 true ,否则返回 false 。如果返回值为
2、遇到问题及解决
当开关启用的时候,如果重复发布阿波罗,此时程序会报错:
Caused by: java.lang.IllegalArgumentException: Collector already registered that provides name: xingren_app_http_requests_latency_seconds_histogram_count
通过报错信息,可以发现是重复的将 Collector 注册到了 CollectorRegistry 中。这时候需要了解一下类CollectorRegistry ,看看有没有剔除注册项的方法。
类 CollectorRegistry 中 public 修饰的方法:
/**
* Register a Collector.
* <p>
* A collector can be registered to multiple CollectorRegistries.
*/
public void register(Collector m) {
List<String> names = collectorNames(m);
synchronized (collectorsToNames) {
for (String name : names) {
if (namesToCollectors.containsKey(name)) {
throw new IllegalArgumentException("Collector already registered that provides name: " + name);
}
}
for (String name : names) {
namesToCollectors.put(name, m);
}
collectorsToNames.put(m, names);
}
}
/**
* Unregister a Collector.
*/
public void unregister(Collector m) {
synchronized (collectorsToNames) {
for (String name : collectorsToNames.get(m)) {
namesToCollectors.remove(name);
}
collectorsToNames.remove(m);
}
}
/**
* Unregister all Collectors.
*/
public void clear() {
synchronized (collectorsToNames) {
collectorsToNames.clear();
namesToCollectors.clear();
}
}
通过查看 CollectorRegistry 的源码得知,可以通过 clear() 方法将 Collector 中的注册项全部剔除掉,然后当 Apollo 发布的时候再重新注册。不用 unregister(Collector m) 方法的原因大家一看就知道:它只能一条一条的去剔除注册项,当时我也试过这个方法,构建入参 Collector 的时候,发现不是很好构建,所以既然有这么好用的 clear() 方法,就不去考虑 unregister(Collector m) 了。
- 启用监控或者说重新发布 Apollo 的时候,先去执行下面的方法,防止重复注册
- 禁用监控的时候(重启 Apollo ),也会执行下面的方法,以清除无用注册,保证程序的整洁
下面是清空注册项的方法:
@Bean
public CollectorRegistry collectorRegistry() {
CollectorRegistry.defaultRegistry.clear();
return CollectorRegistry.defaultRegistry;
}
但是,这样又出现了另外一个问题:

正常情况下:

通过对比以上两幅图片,可以清楚的发现,注册的 Hotspot 相关信息不见了,然后回来翻看注册 Hotspot 的代码。
/**
* 注册默认的 Hotspot 收集器
*/
@Override
public void run(String... strings) {
DefaultExports.initialize();
}
进入 initialize() 方法:
public class DefaultExports {
private static boolean initialized = false;
/**
* Register the default Hotspot collectors.
*/
public static synchronized void initialize() {
if (!initialized) {
new StandardExports().register();
new MemoryPoolsExports().register();
new GarbageCollectorExports().register();
new ThreadExports().register();
new ClassLoadingExports().register();
new VersionInfoExports().register();
initialized = true;
}
}
看 DefaultExports 类中的第2行、第7行和第14行代码,发现原来 initialize() 方法只将 Hotspot 信息注册了一次,即当 Apollo 重新发布的时候,不会再将 Hotspot 的相关信息注册到 CollectorRegistry 中了。
梳理下,由于前面控制的每次发布 Apollo 都会清空 Collector ,又因为 Hotspot 信息只注册一次,则将注册 Hotspot 的代码在类 DefaultExports 中全都抽取出来,即每次发布 Apollo ,都要清空一遍 CollectorRegistry ,然后再将所有信息重新注册进去:
@Configuration
@ConditionalOnProperty(name = "management.prometheus.enabled", havingValue = "true")
@EnablePrometheusEndpoint
public class PrometheusEnableConfig extends WebMvcConfigurerAdapter implements CommandLineRunner {
//1、清空注册方法
//2、拦截器(自定义 Metrics 指标,并注册)方法
/**
* 3、注册默认的Hotspot收集器
*/
@Override
public void run(String... strings) {
new StandardExports().register();
new MemoryPoolsExports().register();
new GarbageCollectorExports().register();
new ThreadExports().register();
new ClassLoadingExports().register();
new VersionInfoExports().register();
}
}
代码中实现了 CommandLineRunner 接口,并实现了它的抽象方法 run(String… strings) ,该接口抽象方法的功能是在项目服务启动的时候就去加载一些数据或做一些事情。
3、总结
配置 Prometheus 监控开关的主要步骤:
- 通过注解 @ConditionalOnProperty 控制启用和
言七墨 禁用监控 - 通过 CollectorRegistry 的 clear() 方法去清空 Co
七墨博客 llectorRegistry ,防止重复注册 - 通过实现接口 CommandLineRunner 的 run() 方法,在服务启动的时候去加载一些
言七墨 必须的数据或者做一些事情
另,通过对接入监控前后的接口压力测试,并对比测试数据,发现接入监控后对接口的性能基本没有产生影响。