前言
接下来的几篇文章我们来分析一下spring-boot-actuator 中在org.springframework.boot.actuate.metrics中的代码,如图:
这里的代码不仅多,而且还比较复杂(类与类之间的关联关系).我们的策略是一点一点的蚕食,本文就先来分析PublicMetrics的实现,关于这部分的类图如下:
本文只分析PublicMetrics, SystemPublicMetrics, TomcatPublicMetrics, DataSourcePublicMetrics.其他的实现–>CachePublicMetrics,MetricReaderPublicMetrics,RichGaugeReaderPublicMetrics 我们后续的文章进行讲解.
解析
PublicMetrics
PublicMetrics,暴露指定的Metric通过MetricsEndpoint的接口.实现类应该小心metrics–> 它们提供了一个唯一的名字在application context中,但是它们不能在jvm,分布式环境时唯一的
该类只声明了1个方法,代码如下:
// 返回表示当前的状态的indication 通过metrics
Collection<Metric<?>> metrics();
这里有必要说明一下Metric,该类是1个持有系统测量值的不变类(1个被命名的数值和事件戳的类) 比如:测量1个服务器的活跃链接数或者是测量会议室的温度.该类是1个泛型类,其泛型参数T–>测量值的类型.其字段,构造器如下:
private final String name;
private final T value;
private final Date timestamp;
SystemPublicMetrics
SystemPublicMetrics–>提供各种与系统相关的度量的PublicMetrics实现,该类实现了PublicMetrics,Ordered.实现Ordered的接口的目的是在有多个PublicMetrics的集合中进行排序.,其实现如下:
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE + 10;
}
字段,构造器如下:
// 启动时间(指的是SystemPublicMetrics初始化的时间) private long timestamp; public SystemPublicMetrics() { this.timestamp = System.currentTimeMillis(); }
metrics,实现如下:
public Collection<Metric<?>> metrics() { Collection<Metric<?>> result = new LinkedHashSet<Metric<?>>(); // 1. 添加基本的统计 addBasicMetrics(result); // 2. 添加uptime,负载等统计 addManagementMetrics(result); return result; }
添加基本的统计.代码如下:
protected void addBasicMetrics(Collection<Metric<?>> result) { // NOTE: ManagementFactory must not be used here since it fails on GAE Runtime runtime = Runtime.getRuntime(); // 1. 添加内存使用统计,name=mem,value = 总内存+堆外内存使用量 result.add(newMemoryMetric("mem", runtime.totalMemory() + getTotalNonHeapMemoryIfPossible())); // 2. 添加可用统计,name=mem.free,value = 可用内存 result.add(newMemoryMetric("mem.free", runtime.freeMemory())); // 3. 添加处理器核心数统计,name=processors,value = 处理器核心数 result.add(new Metric<Integer>("processors", runtime.availableProcessors())); // 4. 添加SystemPublicMetrics 运行时间统计,name=instance.uptime,value = 当前时间-启动时间 result.add(new Metric<Long>("instance.uptime", System.currentTimeMillis() - this.timestamp)); }
添加内存使用统计,name=mem,value = 总内存+堆外内存使用量,其中堆外内存使用量代码如下:
private long getTotalNonHeapMemoryIfPossible() { try { return ManagementFactory.getMemoryMXBean().getNonHeapMemoryUsage().getUsed(); } catch (Throwable ex) { return 0; } }
- 添加可用统计,name=mem.free,value = 可用内存
- 添加处理器核心数统计,name=processors,value = 处理器核心数
- 添加SystemPublicMetrics 运行时间统计,name=instance.uptime,value = 当前时间-启动时间
添加uptime,负载等统计.代码如下:
private void addManagementMetrics(Collection<Metric<?>> result) { try { // Add JVM up time in ms // 1. 添加jvm启动时间,name=uptime,value = 启动时间,单位ms result.add(new Metric<Long>("uptime", ManagementFactory.getRuntimeMXBean().getUptime())); // 2. 添加系统负载,name=systemload.average,value = 启动负载 result.add(new Metric<Double>("systemload.average", ManagementFactory.getOperatingSystemMXBean().getSystemLoadAverage())); // 3. 添加jvm的监控统计 addHeapMetrics(result); // 4. 添加堆外内存的统计 addNonHeapMetrics(result); // 5. 添加线程的统计 addThreadMetrics(result); // 6. 添加类加载相关的统计 addClassLoadingMetrics(result); // 7. 添加垃圾回收的统计 addGarbageCollectionMetrics(result); } catch (NoClassDefFoundError ex) { // Expected on Google App Engine } }
- 添加jvm启动时间,name=uptime,value = 启动时间,单位ms
- 添加系统负载,name=systemload.average,value = 启动负载
添加jvm的监控统计,代码如下:
protected void addHeapMetrics(Collection<Metric<?>> result) { MemoryUsage memoryUsage = ManagementFactory.getMemoryMXBean() .getHeapMemoryUsage(); // 1. 获得所提交的字节内存量-->这个内存量是保证java虚拟机使用的 result.add(newMemoryMetric("heap.committed", memoryUsage.getCommitted())); // 2. 获得jvm的初始化内存数,单位:字节.如果初始内存大小未定义,则此方法返回-1 result.add(newMemoryMetric("heap.init", memoryUsage.getInit())); // 3. 获得内存的使用量 result.add(newMemoryMetric("heap.used", memoryUsage.getUsed())); // 4. 获得内存的最大值,返回-1,如果为指定 result.add(newMemoryMetric("heap", memoryUsage.getMax())); }
- 获得所提交的字节内存量–>这个内存量是保证java虚拟机使用的
- 获得jvm的初始化内存数,单位:字节.如果初始内存大小未定义,则此方法返回-1
- 获得内存的使用量
- 获得内存的最大值,返回-1,如果未指定
添加堆外内存的统计,代码如下:
private void addNonHeapMetrics(Collection<Metric<?>> result) { MemoryUsage memoryUsage = ManagementFactory.getMemoryMXBean() .getNonHeapMemoryUsage(); result.add(newMemoryMetric("nonheap.committed", memoryUsage.getCommitted())); result.add(newMemoryMetric("nonheap.init", memoryUsage.getInit())); result.add(newMemoryMetric("nonheap.used", memoryUsage.getUsed())); result.add(newMemoryMetric("nonheap", memoryUsage.getMax())); }
- 获得所提交的字节内存量–>这个内存量是保证java虚拟机使用的
- 获得jvm的初始化内存数,单位:字节.如果初始内存大小未定义,则此方法返回-1
- 获得内存的使用量
- 获得内存的最大值,返回-1,如果未指定
添加线程的统计,代码如下:
protected void addThreadMetrics(Collection<Metric<?>> result) { ThreadMXBean threadMxBean = ManagementFactory.getThreadMXBean(); // 1. 获得jvm启动以来或者统计重置以来的最大值 result.add(new Metric<Long>("threads.peak", (long) threadMxBean.getPeakThreadCount())); // 2. 获得daemon线程的数量 result.add(new Metric<Long>("threads.daemon", (long) threadMxBean.getDaemonThreadCount())); // 3. 获得jvm启动以来被创建并且启动的线程数 result.add(new Metric<Long>("threads.totalStarted", threadMxBean.getTotalStartedThreadCount())); // 4. 获得当前存活的线程数包括daemon,非daemon的 result.add(new Metric<Long>("threads", (long) threadMxBean.getThreadCount())); }
- 获得jvm启动以来或者统计重置以来的最大值
- 获得daemon线程的数量
- 获得jvm启动以来被创建并且启动的线程数
- 获得当前存活的线程数包括daemon,非daemon的
添加类加载相关的统计,代码如下:
protected void addClassLoadingMetrics(Collection<Metric<?>> result) { ClassLoadingMXBean classLoadingMxBean = ManagementFactory.getClassLoadingMXBean(); // 1. 获得jvm目前加载的class数量 result.add(new Metric<Long>("classes", (long) classLoadingMxBean.getLoadedClassCount())); // 2.获得jvm启动以来加载class的所有数量 result.add(new Metric<Long>("classes.loaded", classLoadingMxBean.getTotalLoadedClassCount())); // 3. 获得jvm卸载class的数量 result.add(new Metric<Long>("classes.unloaded", classLoadingMxBean.getUnloadedClassCount())); }
- 获得jvm目前加载的class数量
- 获得jvm启动以来加载class的所有数量
- 获得jvm卸载class的数量
添加垃圾回收的统计,代码如下:
protected void addGarbageCollectionMetrics(Collection<Metric<?>> result) { // 1. 获得GarbageCollectorMXBean List<GarbageCollectorMXBean> garbageCollectorMxBeans = ManagementFactory .getGarbageCollectorMXBeans(); // 2.遍历之: for (GarbageCollectorMXBean garbageCollectorMXBean : garbageCollectorMxBeans) { String name = beautifyGcName(garbageCollectorMXBean.getName()); // 2.1. 获得gc的次数 result.add(new Metric<Long>("gc." + name + ".count", garbageCollectorMXBean.getCollectionCount())); // 2.2. 获得gc的时间 result.add(new Metric<Long>("gc." + name + ".time", garbageCollectorMXBean.getCollectionTime())); } }
- 获得GarbageCollectorMXBean
遍历之:
- 获得gc的次数
- 获得gc的时间
自动装配:
在PublicMetricsAutoConfiguration中进行自动装配.代码如下:
@Bean public SystemPublicMetrics systemPublicMetrics() { return new SystemPublicMetrics(); }
- @Bean–> 注册1个id为systemPublicMetrics,类型为SystemPublicMetrics的bean
TomcatPublicMetrics
TomcatPublicMetrics–>提供tomcat的数据统计的PublicMetrics的实现.该类实现了PublicMetrics, ApplicationContextAware接口.
metrics 代码如下:
public Collection<Metric<?>> metrics() { // 1. 如果applicationContext 是EmbeddedWebApplicationContext的实例,则进行后续处理,否则,返回空集合 if (this.applicationContext instanceof EmbeddedWebApplicationContext) { // 2. 获得Manager Manager manager = getManager( (EmbeddedWebApplicationContext) this.applicationContext); if (manager != null) { // 3. 如果Manager 不等于null,则调用metrics 进行收集统计数据 return metrics(manager); } } return Collections.emptySet(); }
- 如果applicationContext 是EmbeddedWebApplicationContext的实例,则进行后续处理,否则,返回空集合
获得Manager,代码如下:
private Manager getManager(EmbeddedWebApplicationContext applicationContext) { // 1. 获得内嵌tomcat的实例,如果不是TomcatEmbeddedServletContainer的实例,则返回null EmbeddedServletContainer embeddedServletContainer = applicationContext .getEmbeddedServletContainer(); if (embeddedServletContainer instanceof TomcatEmbeddedServletContainer) { // 2. 否则,获得tomcat中Context所对应的Manager return getManager((TomcatEmbeddedServletContainer) embeddedServletContainer); } return null; }
- 获得内嵌tomcat的实例,如果不是TomcatEmbeddedServletContainer的实例,则返回null
否则,获得tomcat中Context所对应的Manager,代码如下:
private Manager getManager(TomcatEmbeddedServletContainer servletContainer) { for (Container container : servletContainer.getTomcat().getHost() .findChildren()) { if (container instanceof Context) { return ((Context) container).getManager(); } } return null; }
通过遍历host中的Container,如果其是Context的子类,则直接获得其对应的Manager,否则,返回null.
如果Manager 不等于null,则调用metrics 进行收集统计数据.代码如下:
private Collection<Metric<?>> metrics(Manager manager) { List<Metric<?>> metrics = new ArrayList<Metric<?>>(2); // 1. 如果Manager 是ManagerBase的实例,则添加 tomcat的session最大数量,-1 -->没有限制 if (manager instanceof ManagerBase) { addMetric(metrics, "httpsessions.max", ((ManagerBase) manager).getMaxActiveSessions()); } // 2. 添加当前激活的session数量 addMetric(metrics, "httpsessions.active", manager.getActiveSessions()); return metrics; }
- 如果Manager 是ManagerBase的实例,则添加 tomcat的session最大数量,-1 –>没有限制
- 添加当前激活的session数量
addMetric 实现如下:
private void addMetric(List<Metric<?>> metrics, String name, Integer value) { metrics.add(new Metric<Integer>(name, value)); }
自动装配:
声明在TomcatMetricsConfiguration中,代码如下:
@Configuration @ConditionalOnClass({ Servlet.class, Tomcat.class }) @ConditionalOnWebApplication static class TomcatMetricsConfiguration { @Bean @ConditionalOnMissingBean public TomcatPublicMetrics tomcatPublicMetrics() { return new TomcatPublicMetrics(); } }
由于TomcatMetricsConfiguration上声明了如下注解:
@Configuration @ConditionalOnClass({ Servlet.class, Tomcat.class }) @ConditionalOnWebApplication
因此,满足如下条件时该配置生效
- @ConditionalOnClass({ Servlet.class, Tomcat.class })–> 在当前类路径下存在Servlet.class, Tomcat.class时生效
- @ConditionalOnWebApplication–> 在web环境下生效
由于tomcatPublicMetrics方法声明了 @ConditionalOnMissingBean,因此当beanFactory中不存在TomcatPublicMetrics类型的bean时生效.
DataSourcePublicMetrics
DataSourcePublicMetrics–>提供数据源使用方面的数据统计的PublicMetrics的实现.
该类的字段,如下:
private static final String DATASOURCE_SUFFIX = "dataSource"; @Autowired private ApplicationContext applicationContext; @Autowired private Collection<DataSourcePoolMetadataProvider> providers; // key---> 对DataSourcePoolMetadataProvider的id生成的前缀,value-->对应的DataSourcePoolMetadata private final Map<String, DataSourcePoolMetadata> metadataByPrefix = new HashMap<String, DataSourcePoolMetadata>();
由于该类的initialize方法注解有@PostConstruct,因此会在初始化后执行.代码如下:
@PostConstruct public void initialize() { // 1. 尝试获取主数据源 返回null,意味着主数据源不存在 DataSource primaryDataSource = getPrimaryDataSource(); // 2. 实例化DataSourcePoolMetadataProvider DataSourcePoolMetadataProvider provider = new DataSourcePoolMetadataProviders( this.providers); // 3. 获得BeanFactory中DataSource类型的bean,遍历之 for (Map.Entry<String, DataSource> entry : this.applicationContext .getBeansOfType(DataSource.class).entrySet()) { String beanName = entry.getKey(); DataSource bean = entry.getValue(); // 3.1 生成前缀 String prefix = createPrefix(beanName, bean, bean.equals(primaryDataSource)); // 3.2 获得DataSource 所对应的DataSourcePoolMetadata,放入metadataByPrefix 中 DataSourcePoolMetadata poolMetadata = provider .getDataSourcePoolMetadata(bean); if (poolMetadata != null) { this.metadataByPrefix.put(prefix, poolMetadata); } } }
尝试获取主数据源 返回null,意味着主数据源不存在.代码如下:
private DataSource getPrimaryDataSource() { try { return this.applicationContext.getBean(DataSource.class); } catch (NoSuchBeanDefinitionException ex) { return null; } }
- 实例化DataSourcePoolMetadataProvider
获得BeanFactory中DataSource类型的bean,遍历之
生成前缀.代码如下:
protected String createPrefix(String name, DataSource dataSource, boolean primary) { // 1. 如果是主数据源,返回datasource.primary if (primary) { return "datasource.primary"; } // 2. 如果DataSource对应的id 长度大于dataSource的长度,并且是dataSource结尾的,则截取之前的作为id,如:demoDataSource--> demo if (name.length() > DATASOURCE_SUFFIX.length() && name.toLowerCase().endsWith(DATASOURCE_SUFFIX.toLowerCase())) { name = name.substring(0, name.length() - DATASOURCE_SUFFIX.length()); } // 3. 否则,以datasource.作为前缀进行拼接,如demo-->datasource.demo return "datasource." + name; }
- 如果是主数据源,返回datasource.primary
- 如果DataSource对应的id 长度大于dataSource的长度,并且是dataSource结尾的,则截取之前的作为id,如:demoDataSource–> demo
- 否则,以datasource.作为前缀进行拼接,如demo–>datasource.demo
获得DataSource 所对应的DataSourcePoolMetadata,放入metadataByPrefix 中.代码如下:
public DataSourcePoolMetadata getDataSourcePoolMetadata(DataSource dataSource) { for (DataSourcePoolMetadataProvider provider : this.providers) { DataSourcePoolMetadata metadata = provider .getDataSourcePoolMetadata(dataSource); if (metadata != null) { return metadata; } } return null; }
依次遍历持有的providers,如果能根据给定的DataSource获得DataSourcePoolMetadata,则直接返回,否则返回null.
metrics,实现如下:
public Collection<Metric<?>> metrics() { Set<Metric<?>> metrics = new LinkedHashSet<Metric<?>>(); // 1. 遍历metadataByPrefix for (Map.Entry<String, DataSourcePoolMetadata> entry : this.metadataByPrefix .entrySet()) { String prefix = entry.getKey(); // 1.1 获得前缀,如果前缀不是.结尾的,则加上. prefix = (prefix.endsWith(".") ? prefix : prefix + "."); DataSourcePoolMetadata metadata = entry.getValue(); // 1.2 添加Metric,name=prefix.active value = 已经在使用中的(激活)链接或者返回null,如果该信息不可用的话 addMetric(metrics, prefix + "active", metadata.getActive()); // 1.3 添加Metric,name=prefix.usage value = 当前数据库连接池的使用量,返回值在0至1之间(或者是-1,如果当前数据库连接池没有限制的话) addMetric(metrics, prefix + "usage", metadata.getUsage()); } return metrics; }
遍历metadataByPrefix
- 获得前缀,如果前缀不是.结尾的,则加上.
- 添加Metric,name=prefix.active value = 已经在使用中的(激活)链接或者返回null,如果该信息不可用的话
- 添加Metric,name=prefix.usage value = 当前数据库连接池的使用量,返回值在0至1之间(或者是-1,如果当前数据库连接池没有限制的话)
addMetric代码如下:
private <T extends Number> void addMetric(Set<Metric<?>> metrics, String name, T value) { if (value != null) { metrics.add(new Metric<T>(name, value)); } }
自动装配:
声明在DataSourceMetricsConfiguration中.代码如下:
@Configuration @ConditionalOnClass(DataSource.class) @ConditionalOnBean(DataSource.class) static class DataSourceMetricsConfiguration { @Bean @ConditionalOnMissingBean @ConditionalOnBean(DataSourcePoolMetadataProvider.class) public DataSourcePublicMetrics dataSourcePublicMetrics() { return new DataSourcePublicMetrics(); } }
由于在DataSourceMetricsConfiguration上声明了如下注解:
@Configuration @ConditionalOnClass(DataSource.class) @ConditionalOnBean(DataSource.class)
因此在满足如下条件时该配置生效:
- @ConditionalOnClass(DataSource.class)–> 在类路径下存在DataSource.class时生效
- @ConditionalOnBean(DataSource.class) –> 在beanFactory中存在DataSource类型的bean时生效
由于在dataSourcePublicMetrics声明了 @Conditional 注解,因此满足如下条件时生效:
- @ConditionalOnMissingBean–> 在beanFactory中不存在DataSourcePublicMetrics类型的bean时生效
- @ConditionalOnBean(DataSourcePoolMetadataProvider.class)–> 当在beanFactory中存在DataSourcePoolMetadataProvider类型的bean时生效