spring boot 原始碼解析32-PublicMetrics詳解
前言
接下來的幾篇文章我們來分析一下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個伺服器的活躍連結數或者是測量會議室的溫度
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時生效