1. 程式人生 > >spring boot 原始碼解析32-PublicMetrics詳解

spring boot 原始碼解析32-PublicMetrics詳解

前言

接下來的幾篇文章我們來分析一下spring-boot-actuator 中在org.springframework.boot.actuate.metrics中的程式碼,如圖:

專案結構圖

這裡的程式碼不僅多,而且還比較複雜(類與類之間的關聯關係).我們的策略是一點一點的蠶食,本文就先來分析PublicMetrics的實現,關於這部分的類圖如下:

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; }
  1. 欄位,構造器如下:

    // 啟動時間(指的是SystemPublicMetrics初始化的時間)
    private long timestamp;
    
    public SystemPublicMetrics() {
        this.timestamp = System.currentTimeMillis();
    }
  2. metrics,實現如下:

    public Collection<Metric<?>> metrics() {
        Collection<Metric<?>> result = new LinkedHashSet<Metric<?>>();
        // 1. 新增基本的統計
        addBasicMetrics(result);
        // 2. 新增uptime,負載等統計
        addManagementMetrics(result);
        return result;
    }
    1. 新增基本的統計.程式碼如下:

      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));
      }
      1. 新增記憶體使用統計,name=mem,value = 總記憶體+堆外記憶體使用量,其中堆外記憶體使用量程式碼如下:

        private long getTotalNonHeapMemoryIfPossible() {
            try {
                return ManagementFactory.getMemoryMXBean().getNonHeapMemoryUsage().getUsed();
            }
            catch (Throwable ex) {
                return 0;
            }
        }
      2. 新增可用統計,name=mem.free,value = 可用記憶體
      3. 新增處理器核心數統計,name=processors,value = 處理器核心數
      4. 新增SystemPublicMetrics 執行時間統計,name=instance.uptime,value = 當前時間-啟動時間
    2. 新增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
          }
      }
      1. 新增jvm啟動時間,name=uptime,value = 啟動時間,單位ms
      2. 新增系統負載,name=systemload.average,value = 啟動負載
      3. 新增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()));
        }
        1. 獲得所提交的位元組記憶體量–>這個記憶體量是保證java虛擬機器使用的
        2. 獲得jvm的初始化記憶體數,單位:位元組.如果初始記憶體大小未定義,則此方法返回-1
        3. 獲得記憶體的使用量
        4. 獲得記憶體的最大值,返回-1,如果未指定
      4. 新增堆外記憶體的統計,程式碼如下:

        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()));
        }
        1. 獲得所提交的位元組記憶體量–>這個記憶體量是保證java虛擬機器使用的
        2. 獲得jvm的初始化記憶體數,單位:位元組.如果初始記憶體大小未定義,則此方法返回-1
        3. 獲得記憶體的使用量
        4. 獲得記憶體的最大值,返回-1,如果未指定
      5. 新增執行緒的統計,程式碼如下:

        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()));
        }
        1. 獲得jvm啟動以來或者統計重置以來的最大值
        2. 獲得daemon執行緒的數量
        3. 獲得jvm啟動以來被建立並且啟動的執行緒數
        4. 獲得當前存活的執行緒數包括daemon,非daemon的
      6. 新增類載入相關的統計,程式碼如下:

        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()));
        }
        1. 獲得jvm目前載入的class數量
        2. 獲得jvm啟動以來載入class的所有數量
        3. 獲得jvm解除安裝class的數量
      7. 新增垃圾回收的統計,程式碼如下:

        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()));
            }
        }
        1. 獲得GarbageCollectorMXBean
        2. 遍歷之:

          1. 獲得gc的次數
          2. 獲得gc的時間
  3. 自動裝配:

    在PublicMetricsAutoConfiguration中進行自動裝配.程式碼如下:

    @Bean
    public SystemPublicMetrics systemPublicMetrics() {
        return new SystemPublicMetrics();
    }
    • @Bean–> 註冊1個id為systemPublicMetrics,型別為SystemPublicMetrics的bean

TomcatPublicMetrics

TomcatPublicMetrics–>提供tomcat的資料統計的PublicMetrics的實現.該類實現了PublicMetrics, ApplicationContextAware介面.

  1. 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();
    }
    1. 如果applicationContext 是EmbeddedWebApplicationContext的例項,則進行後續處理,否則,返回空集合
    2. 獲得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;
      }
      1. 獲得內嵌tomcat的例項,如果不是TomcatEmbeddedServletContainer的例項,則返回null
      2. 否則,獲得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.

    3. 如果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;
      }
      
      1. 如果Manager 是ManagerBase的例項,則新增 tomcat的session最大數量,-1 –>沒有限制
      2. 添加當前啟用的session數量

      addMetric 實現如下:

      private void addMetric(List<Metric<?>> metrics, String name, Integer value) {
          metrics.add(new Metric<Integer>(name, value));
      }
  2. 自動裝配:

    宣告在TomcatMetricsConfiguration中,程式碼如下:

    @Configuration
    @ConditionalOnClass({ Servlet.class, Tomcat.class })
    @ConditionalOnWebApplication
    static class TomcatMetricsConfiguration {
    
        @Bean
        @ConditionalOnMissingBean
        public TomcatPublicMetrics tomcatPublicMetrics() {
            return new TomcatPublicMetrics();
        }
    
    }
    1. 由於TomcatMetricsConfiguration上聲明瞭如下註解:

      @Configuration
      @ConditionalOnClass({ Servlet.class, Tomcat.class })
      @ConditionalOnWebApplication

      因此,滿足如下條件時該配置生效

      • @ConditionalOnClass({ Servlet.class, Tomcat.class })–> 在當前類路徑下存在Servlet.class, Tomcat.class時生效
      • @ConditionalOnWebApplication–> 在web環境下生效
    2. 由於tomcatPublicMetrics方法聲明瞭 @ConditionalOnMissingBean,因此當beanFactory中不存在TomcatPublicMetrics型別的bean時生效.

DataSourcePublicMetrics

DataSourcePublicMetrics–>提供資料來源使用方面的資料統計的PublicMetrics的實現.

  1. 該類的欄位,如下:

    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>();
  2. 由於該類的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);
            }
        }
    }
    1. 嘗試獲取主資料來源 返回null,意味著主資料來源不存在.程式碼如下:

      private DataSource getPrimaryDataSource() {
          try {
              return this.applicationContext.getBean(DataSource.class);
          }
          catch (NoSuchBeanDefinitionException ex) {
              return null;
          }
      }
    2. 例項化DataSourcePoolMetadataProvider
    3. 獲得BeanFactory中DataSource型別的bean,遍歷之

      1. 生成字首.程式碼如下:

        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;
        }
        1. 如果是主資料來源,返回datasource.primary
        2. 如果DataSource對應的id 長度大於dataSource的長度,並且是dataSource結尾的,則擷取之前的作為id,如:demoDataSource–> demo
        3. 否則,以datasource.作為字首進行拼接,如demo–>datasource.demo
      2. 獲得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.

  3. 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;
    }
    1. 遍歷metadataByPrefix

      1. 獲得字首,如果字首不是.結尾的,則加上.
      2. 新增Metric,name=prefix.active value = 已經在使用中的(啟用)連結或者返回null,如果該資訊不可用的話
      3. 新增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));
          }
      }
  4. 自動裝配:

    宣告在DataSourceMetricsConfiguration中.程式碼如下:

    @Configuration
    @ConditionalOnClass(DataSource.class)
    @ConditionalOnBean(DataSource.class)
    static class DataSourceMetricsConfiguration {
    
        @Bean
        @ConditionalOnMissingBean
        @ConditionalOnBean(DataSourcePoolMetadataProvider.class)
        public DataSourcePublicMetrics dataSourcePublicMetrics() {
            return new DataSourcePublicMetrics();
        }
    
    }
    1. 由於在DataSourceMetricsConfiguration上聲明瞭如下註解:

      @Configuration
      @ConditionalOnClass(DataSource.class)
      @ConditionalOnBean(DataSource.class)

      因此在滿足如下條件時該配置生效:

      • @ConditionalOnClass(DataSource.class)–> 在類路徑下存在DataSource.class時生效
      • @ConditionalOnBean(DataSource.class) –> 在beanFactory中存在DataSource型別的bean時生效
    2. 由於在dataSourcePublicMetrics聲明瞭 @Conditional 註解,因此滿足如下條件時生效:

      • @ConditionalOnMissingBean–> 在beanFactory中不存在DataSourcePublicMetrics型別的bean時生效
      • @ConditionalOnBean(DataSourcePoolMetadataProvider.class)–> 當在beanFactory中存在DataSourcePoolMetadataProvider型別的bean時生效