spring boot 源码解析32-PublicMetrics详解

扫码关注公众号:Java 技术驿站

发送:vip
将链接复制到本浏览器,永久解锁本站全部文章

【公众号:Java 技术驿站】 【加作者微信交流技术,拉技术群】

前言

接下来的几篇文章我们来分析一下spring-boot-actuator 中在org.springframework.boot.actuate.metrics中的代码,如图:

20191017100454\_1.png

这里的代码不仅多,而且还比较复杂(类与类之间的关联关系).我们的策略是一点一点的蚕食,本文就先来分析PublicMetrics的实现,关于这部分的类图如下:

20191017100454\_2.png

本文只分析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> metrics() { Collection> result = new LinkedHashSet>(); // 1. 添加基本的统计 addBasicMetrics(result); // 2. 添加uptime,负载等统计 addManagementMetrics(result); return result; }
    1. 添加基本的统计.代码如下:

          protected void addBasicMetrics(Collection> 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("processors", runtime.availableProcessors()));
              // 4. 添加SystemPublicMetrics 运行时间统计,name=instance.uptime,value = 当前时间-启动时间
              result.add(new Metric("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> result) {
              try {
                  // Add JVM up time in ms
                  // 1. 添加jvm启动时间,name=uptime,value = 启动时间,单位ms
                  result.add(new Metric("uptime",
                          ManagementFactory.getRuntimeMXBean().getUptime()));
                  // 2. 添加系统负载,name=systemload.average,value = 启动负载
                  result.add(new Metric("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> 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> 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> result) {
                ThreadMXBean threadMxBean = ManagementFactory.getThreadMXBean();
                // 1. 获得jvm启动以来或者统计重置以来的最大值
                result.add(new Metric("threads.peak",
                        (long) threadMxBean.getPeakThreadCount()));
                // 2. 获得daemon线程的数量
                result.add(new Metric("threads.daemon",
                        (long) threadMxBean.getDaemonThreadCount()));
                // 3. 获得jvm启动以来被创建并且启动的线程数
                result.add(new Metric("threads.totalStarted",
                        threadMxBean.getTotalStartedThreadCount()));
                // 4. 获得当前存活的线程数包括daemon,非daemon的
                result.add(new Metric("threads", (long) threadMxBean.getThreadCount()));
            }
        1. 获得jvm启动以来或者统计重置以来的最大值
        2. 获得daemon线程的数量
        3. 获得jvm启动以来被创建并且启动的线程数
        4. 获得当前存活的线程数包括daemon,非daemon的
      6. 添加类加载相关的统计,代码如下:

            protected void addClassLoadingMetrics(Collection> result) {
                ClassLoadingMXBean classLoadingMxBean = ManagementFactory.getClassLoadingMXBean();
                // 1. 获得jvm目前加载的class数量
                result.add(new Metric("classes",
                        (long) classLoadingMxBean.getLoadedClassCount()));
                // 2.获得jvm启动以来加载class的所有数量
                result.add(new Metric("classes.loaded",
                        classLoadingMxBean.getTotalLoadedClassCount()));
                // 3. 获得jvm卸载class的数量
                result.add(new Metric("classes.unloaded",
                        classLoadingMxBean.getUnloadedClassCount()));
            }
        1. 获得jvm目前加载的class数量
        2. 获得jvm启动以来加载class的所有数量
        3. 获得jvm卸载class的数量
      7. 添加垃圾回收的统计,代码如下:

            protected void addGarbageCollectionMetrics(Collection> result) {
                // 1. 获得GarbageCollectorMXBean
                List garbageCollectorMxBeans = ManagementFactory
                        .getGarbageCollectorMXBeans();
                // 2.遍历之:
                for (GarbageCollectorMXBean garbageCollectorMXBean : garbageCollectorMxBeans) {
                    String name = beautifyGcName(garbageCollectorMXBean.getName());
                    // 2.1. 获得gc的次数
                    result.add(new Metric("gc." + name + ".count",
                            garbageCollectorMXBean.getCollectionCount()));
                    // 2.2. 获得gc的时间
                    result.add(new Metric("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> 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> metrics(Manager manager) { List> metrics = new ArrayList>(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> metrics, String name, Integer value) {
              metrics.add(new Metric(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 providers;
    
        // key---> 对DataSourcePoolMetadataProvider的id生成的前缀,value-->对应的DataSourcePoolMetadata
        private final Map metadataByPrefix = new HashMap();
  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 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> metrics() {
            Set> metrics = new LinkedHashSet>();
            // 1. 遍历metadataByPrefix
            for (Map.Entry 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  void addMetric(Set> metrics, String name,
                  T value) {
              if (value != null) {
                  metrics.add(new Metric(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时生效

来源:[]()

赞(0) 打赏
版权归原创作者所有,任何形式的转载请联系博主:daming_90:Java 技术驿站 » spring boot 源码解析32-PublicMetrics详解

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏