spring boot 源码解析43-JmxMetricWriter详解

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

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

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

前言

本文我们来介绍JmxMetricWriter, JmxMetricWriter是依赖于spring jmx 来实现的. 因此本文的开头先介绍一下spring boot 与jmx的集成,然后分析JmxMetricWriter源码.

spring boot jmx

  1. 首先确保在pom文件中加入了如下依赖:

        
            org.springframework.boot
            spring-boot-starter-actuator
        

    只要加入如下依赖,就会自动装配如下bean:

    • AnnotationMBeanExporter
    • objectNamingStrategy
    • MBeanServer
    • EndpointMBeanExporter
    • 一系列的jmxEndpoint…

    以上的配置类在JmxAutoConfiguration,EndpointMBeanExportAutoConfiguration中进行了声明

  2. 在com.example.demo.jmx包下创建Sumer类,代码如下:

        package com.example.demo.jmx;
        import org.springframework.jmx.export.annotation.ManagedOperation;
        import org.springframework.jmx.export.annotation.ManagedOperationParameter;
        import org.springframework.jmx.export.annotation.ManagedOperationParameters;
        import org.springframework.jmx.export.annotation.ManagedResource;
        import org.springframework.stereotype.Component;
    
        @Component
        @ManagedResource(description = "spring boot jmx demo", objectName = "bean:name=sumer")
        public class Sumer {
    
            @ManagedOperation(description = "Add two numbers")
            @ManagedOperationParameters({ @ManagedOperationParameter(name = "x", description = "The first number"),
                    @ManagedOperationParameter(name = "y", description = "The second number") })
            public int add(int x, int y) {
    
                return x + y;
            }
        }

    其中:

    • @ManagedResource(description = “spring boot jmx demo”, objectName = “bean:name=sumer”)–> 指定该bean要进行jmx暴露, description是在jmx中的描述,如图:

      20191017100444\_1.png

      objectName = “bean:name=sumer” 中的bean 指明了其scope,name=test 指定了在jmx中的名字,如图:

      20191017100444\_2.png

    • @ManagedOperation –> 指定在jmx中Operation的名字
    • @ManagedAttribute–> 指定在jmx中attribute的属性
    • @ManagedOperationParameter and @ManagedOperationParameters–> 指明ManagedOperation中的参数.

    此处为何scope是bean,name是test?

    原因: 自动装配了ParentAwareNamingStrategy.其getObjectName代码如下:

    public ObjectName getObjectName(Object managedBean, String beanKey) throws MalformedObjectNameException { ObjectName name = super.getObjectName(managedBean, beanKey); Hashtable<String, String> properties = new Hashtable<String, String>(); properties.putAll(name.getKeyPropertyList()); if (this.ensureUniqueRuntimeObjectNames) { properties.put("identity", ObjectUtils.getIdentityHexString(managedBean)); } else if (parentContextContainsSameBean(this.applicationContext, beanKey)) { properties.put("context", ObjectUtils.getIdentityHexString(this.applicationContext)); } return ObjectNameManager.getInstance(name.getDomain(), properties); }

    1. 调用父类(MetadataNamingStrategy)的getObjectName方法,获得ObjectName.代码如下:

          public ObjectName getObjectName(Object managedBean, String beanKey) throws MalformedObjectNameException {
              Class managedClass = AopUtils.getTargetClass(managedBean);
              // 1. 获得ManagedResource
              ManagedResource mr = this.attributeSource.getManagedResource(managedClass);
              // Check that an object name has been specified.
              if (mr != null && StringUtils.hasText(mr.getObjectName())) {
                  // 2. 如果存在ManagedResource,并且ObjectName有值,则直接实例化ObjectName
                  return ObjectNameManager.getInstance(mr.getObjectName());
              }
              else {
                  try {
                      // 3. 否则,根据beanKey生成ObjectName
                      return ObjectNameManager.getInstance(beanKey);
                  }
                  catch (MalformedObjectNameException ex) {
                      String domain = this.defaultDomain;
                      if (domain == null) {
                          domain = ClassUtils.getPackageName(managedClass);
                      }
                      Hashtable properties = new Hashtable();
                      properties.put("type", ClassUtils.getShortName(managedClass));
                      properties.put("name", beanKey);
                      return ObjectNameManager.getInstance(domain, properties);
                  }
              }
          }
      1. 获得ManagedResource
      2. 如果存在ManagedResource,并且ObjectName有值,则直接实例化ObjectName
      3. 否则,根据beanKey生成ObjectName
    2. 将name中的所有KeyProperty 放入到properties中
    3. 如果ensureUniqueRuntimeObjectNames等于true,则放入key–>identity,value–>该bean的hash值
    4. 如果在父Context中存在id为beanKey的bean,则存入key–>context,value–>当前上下文的hash值
    5. 生成ObjectName

      对于当前,我们在Sumer上声明了@ManagedResource,并且配置了objectName为bean:name=sumer,因此最终会在1.1.2 步返回,最后jmx中的规范生成ObjectName

  3. 打开jvisualvm,链接上我们的spring boot 程序,在Mbeans中的页面可以看到我们配置的Sumer,在其中的Operations中可以看到我们声明的方法add,输入1,2后,可以看到其返回值为3,如图:

    20191017100444\_3.png

参考链接:

spring-jmx

第20章-使用JMX管理Spring Bean

Spring与JMX集成

spring通过annotation注解注册MBean到JMX实现监控java运行状态

JmxMetricWriter

JmxMetricWriter实现了MetricWriter接口.

  1. 其声明了@ManagedResource注解,如下:

        @ManagedResource(description = "MetricWriter for pushing metrics to JMX MBeans.")
  2. 字段,构造器如下:

        private static final Log logger = LogFactory.getLog(JmxMetricWriter.class);
    
        // string -->Metric 名称,value --> MetricValue
        private final ConcurrentMap values = new ConcurrentHashMap();
    
        private final MBeanExporter exporter;
    
        private ObjectNamingStrategy namingStrategy = new DefaultMetricNamingStrategy();
    
        private String domain = "org.springframework.metrics";
    
        public JmxMetricWriter(MBeanExporter exporter) {
            this.exporter = exporter;
        }
  3. 其有3个被@ManagedOperation注解的方法,如下:

    1. increment(String name, long value),代码如下:

          @ManagedOperation
          public void increment(String name, long value) {
              increment(new Delta(name, value));
          }

      调用

          public void increment(Delta delta) {
              MetricValue counter = getValue(delta.getName());
              counter.increment(delta.getValue().longValue());
          }
      1. 调用getValue方法获得对应的MetricValue.代码如下:

            private MetricValue getValue(String name) {
                // 1. 从缓存中查找,如果有则直接返回,否则,进行第2步
                MetricValue value = this.values.get(name);
                if (value == null) {
                    // 2.1 实例化MetricValue,放入缓存中
                    value = new MetricValue();
                    MetricValue oldValue = this.values.putIfAbsent(name, value);
                    if (oldValue != null) {
                        value = oldValue;
                    }
                    try {
                        // 2.2 生成ObjectName
                        // 2.3 进行注册
                        this.exporter.registerManagedResource(value, getName(name, value));
                    }
                    catch (Exception ex) {
                        // Could not register mbean, maybe just a race condition
                    }
                }
                return value;
            }
        1. 从缓存中查找,如果有则直接返回,否则,进行第2步
        2. 如果缓存中不存在,则

          1. 实例化MetricValue,放入缓存中
          2. 生成ObjectName.代码如下:

                private ObjectName getName(String name, MetricValue value)
                        throws MalformedObjectNameException {
                    // 1. 进行拼接,如传入的是couter.test.increment,则返回org.springframework.metrics:type=MetricValue,name=couter.test.increment
                    String key = String.format(this.domain + ":type=MetricValue,name=%s", name);
                    return this.namingStrategy.getObjectName(value, key);
                }
            1. 进行拼接,如传入的是couter.test.increment,则返回org.springframework.metrics:type=MetricValue,name=couter.test.increment
            2. 调用DefaultMetricNamingStrategy#getObjectName方法.代码如下:

                  public ObjectName getObjectName(Object managedBean, String beanKey)
                          throws MalformedObjectNameException {
                      // 1. 获得ObjectName
                      ObjectName objectName = this.namingStrategy.getObjectName(managedBean, beanKey);
                      // 2. 获得ObjectName对应的domain
                      String domain = objectName.getDomain();
                      // 3. 获得ObjectName对应的key properties
                      Hashtable table = new Hashtable(
                              objectName.getKeyPropertyList());
                      String name = objectName.getKeyProperty("name");
                      if (name != null) {
                          // 4. 如果ObjectName中存在name的属性,则进行删除
                          table.remove("name");
                          // 5. 将name通过点进行分割
                          String[] parts = StringUtils.delimitedListToStringArray(name, ".");
                          // 5.1 将第1个设置为type
                          table.put("type", parts[0]);
                          // 5.2 如果name存在1个点,则进行判断: 如果name存在2个点以上,则存入key-->name,value -->parts[1],否则,存入value-->name,value -->parts[1]
                          // 如传入的name是couter.test.increment,则此时存入的是key-->name,value --> test
                          if (parts.length > 1) {
                              table.put(parts.length > 2 ? "name" : "value", parts[1]);
                          }
                          // 5.2 如果name存在2个点,则进行存入,key--> value,value-->name 对应的第2个点以后的值,如传入的name是couter.test.increment,则此时存入的是key-->value,value --> increment
                          if (parts.length > 2) {
                              table.put("value",
                                      name.substring(parts[0].length() + parts[1].length() + 2));
                          }
                      }
                      return new ObjectName(domain, table);
                  }
              1. 获得ObjectName.代码如下:

                    public ObjectName getObjectName(Object managedBean, String beanKey) throws MalformedObjectNameException {
                        String objectName = null;
                        if (this.mergedMappings != null) {
                            objectName = this.mergedMappings.getProperty(beanKey);
                        }
                        if (objectName == null) {
                            objectName = beanKey;
                        }
                        return ObjectNameManager.getInstance(objectName);
                    }

                如果缓存存在,则从缓存中获取否则使用beanKey来作为ObjectName.由于此时mergedMappings等于null,因此,此时使用的是传入的beanKey来作为ObjectName

              2. 获得ObjectName对应的domain
              3. 获得ObjectName对应的key properties
              4. 如果objectName中存在name的属性

                1. 删除name
                2. 将name通过点进行分割
                3. 将name通过点进行分割
                4. 将第1个设置为type
                5. 如果name存在1个点,则进行判断: 如果name存在2个点以上,则存入key–>name,value –>parts[1],否则,存入value–>name,value –>parts[1].如传入的name是couter.test.increment,则此时存入的是key–>name,value –> test
                6. 如果name存在2个点,则进行存入,key–> value,value–>name 对应的第2个点以后的值,如传入的name是couter.test.increment,则此时存入的是key–>value,value –> increment
              5. 返回ObjectName
          3. 进行注册
      2. 调用MetricValue#increment方法

      这里有必要说明一下 MetricValue:

      1. 字段,构造器如下:

            private double value;
        
            private long lastUpdated = 0;
      2. 声明了如下方法:

        1. setValue,代码如下:

              public void setValue(double value) {
                  if (this.value != value) {
                      this.lastUpdated = System.currentTimeMillis();
                  }
                  this.value = value;
              }
        2. increment,代码如下:

              public void increment(long value) {
                  this.lastUpdated = System.currentTimeMillis();
                  this.value += value;
              }
        3. getValue,代码如下:

              @ManagedAttribute
              public double getValue() {
                  return this.value;
              }
        4. getLastUpdated,代码如下:

              @ManagedAttribute
              public Date getLastUpdated() {
                  return new Date(this.lastUpdated);
              }
    2. set,代码如下:

          @ManagedOperation
          public void set(String name, double value) {
              set(new Metric(name, value));
          }

      调用:

          public void set(Metric value) {
              MetricValue metric = getValue(value.getName());
              metric.setValue(value.getValue().doubleValue());
          }
      1. 通过 getValue方法获得对应的 MetricValue
      2. 调用MetricValue对应的setValue方法.
    3. reset,代码如下:

          @ManagedOperation
          public void reset(String name) {
                  // 1. 从缓存中删除
                  MetricValue value = this.values.remove(name);
                  if (value != null) {
                      try {
                          // We can unregister the MBean, but if this writer is on the end of an
                          // Exporter the chances are it will be re-registered almost immediately.
                          // 2.从MBeanExporter中删除
                          this.exporter.unregisterManagedResource(getName(name, value));
                      }
                      catch (MalformedObjectNameException ex) {
                          logger.warn("Could not unregister MBean for " + name);
                      }
                  }
              }
      1. 从缓存中删除
      2. 从MBeanExporter中删除
  4. 配置:

    注意,该类不支持自动装配.

    需要在配置类做如下配置即可:

        @Bean
        @ExportMetricWriter
        public JmxMetricWriter jmxMetricWriter(
                @Qualifier("mbeanExporter") MBeanExporter exporter) {
            return new JmxMetricWriter(exporter);
        }   
    • @Bean–> 注册1个id为jmxMetricWriter,类型为JmxMetricWriter的bean
    • @ExportMetricWriter–>@Qualifier注解,表明该类是用来暴露metrics数据的
    • @Qualifier(“mbeanExporter”) MBeanExporter exporter–> spring boot 中会自动装配1个id为mbeanExporter,类型为MBeanExporter的bean,在JmxAutoConfiguration中进行了声明,如下:

          @Bean
          @Primary
          @ConditionalOnMissingBean(value = MBeanExporter.class, search = SearchStrategy.CURRENT)
          public AnnotationMBeanExporter mbeanExporter(ObjectNamingStrategy namingStrategy) {
              AnnotationMBeanExporter exporter = new AnnotationMBeanExporter();
              exporter.setRegistrationPolicy(RegistrationPolicy.FAIL_ON_EXISTING);
              exporter.setNamingStrategy(namingStrategy);
              String server = this.propertyResolver.getProperty("server", "mbeanServer");
              if (StringUtils.hasLength(server)) {
                  exporter.setServer(this.beanFactory.getBean(server, MBeanServer.class));
              }
              return exporter;
          }

      启用spring boot 应用后,打开jvisualvm,链接spring boot 应用后,会发现有如下mBean:

      20191017100444\_4.png
      其Operations标签页如下:

      20191017100444\_5.png

      现在,测试吧,是不是很简单.


来源:[]()

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

评论 抢沙发

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

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

支付宝扫一扫打赏

微信扫一扫打赏