• Springboot Actuator之十一:actuator 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();
    1
    2
    这里有必要说明一下Metric,该类是1个持有系统测量值的不变类(1个被命名的数值和事件戳的类) 比如:测量1个服务器的活跃链接数或者是测量会议室的温度.该类是1个泛型类,其泛型参数T–>测量值的类型.其字段,构造器如下:

    private final String name;

    private final T value;

    private final Date timestamp;

    1
    2
    3
    4
    5
    6
    7
    SystemPublicMetrics
    SystemPublicMetrics–>提供各种与系统相关的度量的PublicMetrics实现,该类实现了PublicMetrics,Ordered.实现Ordered的接口的目的是在有多个PublicMetrics的集合中进行排序.,其实现如下:

    public int getOrder() {
    return Ordered.HIGHEST_PRECEDENCE + 10;
    }
    1
    2
    3
    字段,构造器如下:

    // 启动时间(指的是SystemPublicMetrics初始化的时间)
    private long timestamp;

    public SystemPublicMetrics() {
    this.timestamp = System.currentTimeMillis();
    }
    1
    2
    3
    4
    5
    6
    metrics,实现如下:

    public Collection<Metric<?>> metrics() {
    Collection<Metric<?>> result = new LinkedHashSet<Metric<?>>();
    // 1. 添加基本的统计
    addBasicMetrics(result);
    // 2. 添加uptime,负载等统计
    addManagementMetrics(result);
    return result;
    }
    1
    2
    3
    4
    5
    6
    7
    8
    添加基本的统计.代码如下:

    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
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    添加内存使用统计,name=mem,value = 总内存+堆外内存使用量,其中堆外内存使用量代码如下:

    private long getTotalNonHeapMemoryIfPossible() {
    try {
    return ManagementFactory.getMemoryMXBean().getNonHeapMemoryUsage().getUsed();
    }
    catch (Throwable ex) {
    return 0;
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    添加可用统计,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
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    添加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()));
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    获得所提交的字节内存量–>这个内存量是保证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()));
    }
    1
    2
    3
    4
    5
    6
    7
    8
    获得所提交的字节内存量–>这个内存量是保证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()));
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    获得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()));
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    获得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()));
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    获得GarbageCollectorMXBean
    遍历之:

    获得gc的次数
    获得gc的时间
    自动装配:

    在PublicMetricsAutoConfiguration中进行自动装配.代码如下:

    @Bean
    public SystemPublicMetrics systemPublicMetrics() {
    return new SystemPublicMetrics();
    }
    1
    2
    3
    4
    @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();
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    如果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;
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    获得内嵌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;
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    通过遍历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;
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    如果Manager 是ManagerBase的实例,则添加 tomcat的session最大数量,-1 –>没有限制
    添加当前激活的session数量
    addMetric 实现如下:

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

    声明在TomcatMetricsConfiguration中,代码如下:

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

    @Bean
    @ConditionalOnMissingBean
    public TomcatPublicMetrics tomcatPublicMetrics() {
    return new TomcatPublicMetrics();
    }

    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    由于TomcatMetricsConfiguration上声明了如下注解:

    @Configuration
    @ConditionalOnClass({ Servlet.class, Tomcat.class })
    @ConditionalOnWebApplication
    1
    2
    3
    因此,满足如下条件时该配置生效

    @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>();
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    由于该类的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
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    尝试获取主数据源 返回null,意味着主数据源不存在.代码如下:

    private DataSource getPrimaryDataSource() {
    try {
    return this.applicationContext.getBean(DataSource.class);
    }
    catch (NoSuchBeanDefinitionException ex) {
    return null;
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    实例化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;
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    如果是主数据源,返回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;
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    依次遍历持有的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;
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    遍历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));
    }
    }
    1
    2
    3
    4
    5
    6
    自动装配:

    声明在DataSourceMetricsConfiguration中.代码如下:

    @Configuration
    @ConditionalOnClass(DataSource.class)
    @ConditionalOnBean(DataSource.class)
    static class DataSourceMetricsConfiguration {

    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnBean(DataSourcePoolMetadataProvider.class)
    public DataSourcePublicMetrics dataSourcePublicMetrics() {
    return new DataSourcePublicMetrics();
    }

    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    由于在DataSourceMetricsConfiguration上声明了如下注解:

    @Configuration
    @ConditionalOnClass(DataSource.class)
    @ConditionalOnBean(DataSource.class)
    1
    2
    3
    因此在满足如下条件时该配置生效:

    @ConditionalOnClass(DataSource.class)–> 在类路径下存在DataSource.class时生效
    @ConditionalOnBean(DataSource.class) –> 在beanFactory中存在DataSource类型的bean时生效
    由于在dataSourcePublicMetrics声明了 @Conditional 注解,因此满足如下条件时生效:

    @ConditionalOnMissingBean–> 在beanFactory中不存在DataSourcePublicMetrics类型的bean时生效
    @ConditionalOnBean(DataSourcePoolMetadataProvider.class)–> 当在beanFactory中存在DataSourcePoolMetadataProvider类型的bean时生效
    ---------------------
    版权声明:本文为CSDN博主「一个努力的码农」的原创文章,遵循CC 4.0 by-sa版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/qq_26000415/article/details/79134941

  • 相关阅读:
    绝对路径相对路径
    LN项目重构之职责链模式
    年度回忆录(2011.072011.12)
    协议学习建议
    UBUNTU下制作软盘映
    从汇编看c语言函数调用
    计算机底层入门知识杂记(一)——计算机启动流程解析
    自己动手写操作体统 pmtest1.asm 详细解释
    汇编函数与C函数的相互调用
    嵌入式linux驱动开发班
  • 原文地址:https://www.cnblogs.com/duanxz/p/11328683.html
Copyright © 2020-2023  润新知