暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

Java应用程序监控之JMX

开发自由行 2021-06-29
2219


备注:JDK版本:1.8.0,系统环境:Ubuntu16.04 LTS


JMX(Java Management Extensions,即Java管理扩展)是一个为应用程序、设备、系统等嵌入管理功能的框架。借助JMX可以方便的对应用进行监控,小到对属性的修改实时知悉,大到对应用程序接口访问的情况分析,以实时控制接口的并发量,了解应用的健康状态。

结合JMX规范(JSR003),可以知道JMX可以为我们的Java应用提供以下益处:

  • 不需要大的投入就可以对应用程序进行监控;

  • 提供一个可以伸缩的架构,无缝接入已经存在的监控系统;

  • 开发简单,只需要借助于标准的Java代码;

参考:Java Management Extensions Specification, version 1.4

https://download.oracle.com/otn-pub/jcp/jmx-1_4-mrel4-eval-spec/jsr3-jmx-1_4-mrel4-spec-FINAL-v1_0.pdf

1、JMX架构

从上图中我们知道JMX分为四个层次:

设备层(Instrumentation Level):

定义了信息模型。在JMX中,各种管理对象以管理构件的形式存在,需要管理时,向MBean服务器进行注册,该层还定义了通知机制以及一些辅助元数据类。

代理层(Agent Level):

定义各种服务以及通信模型。该层的核心是一个MBean服务器,所有的管理构件都需要向它注册,才能被管理。注册在MBean服务器上管理构件并不直接和远程应用程序进行通信,它们通过协议适配器和连接器进行通信。而协议适配器和连接器也以管理构件的形式向MBean服务器注册才能提供相应的服务。

分布服务层(Distributed Service Level):

定义能对代理层进行操作的管理接口和构件,这样管理者就可以操作代理。

附加管理协议API

定义的API主要用来支持当前已经存在的网络管理协议,如SNMP、TMN、CIM/WBEM等。

参考:Java Management Extensions Specification, version 1.4

https://download.oracle.com/otn-pub/jcp/jmx-1_4-mrel4-eval-spec/jsr3-jmx-1_4-mrel4-spec-FINAL-v1_0.pdf


2、JMX管理Bean(MBean)

JMX管理的Bean分为以下四种类型:

类型描述
StandMBean最简单MBean,它能管理的资源(包括属性,操作)必须定义在接口中,然后MBean必须实现这个接口,命名也必须遵循一定的规范,例如我们的MBean为User,则接口必须为UserMBean
DynamicBean必须实现javax.management.DynamicMBean接口,所有的属性,操作都在运行时定义
ModelMBean

与标准和动态MBean相比,你可以不用写MBean类,只需使用javax.management.modelmbean.RequiredModelMBean即可。RequiredModelMBean实现了ModelMBean接口,而ModelMBean扩展了DynamicMBean接口,因此与DynamicMBean相似,ModelMBean管理的资源也是在运行时定义的。与DynamicMBean不同的是,DynamicMBean管理的资源一般定义在DynamicMBean中(运行时才决定管理那些资源),而ModelMBean管理的资源并不在MBean中,而是在外部(通常是一个类),在运行时,通过set方法将其加入到ModelMBean中

OpenMBean

OpenMBean能够在运行时共享和使用管理数据和操作,而无需重新编译,开放的MBean有助于管理系统的灵活性和可扩展性

javax.management.DynamicMBean的接口方法如下图所示:

因此使用javax.management.DynamicMBean的时候,必须复写这些方法(下文说明用法)

本文将详细讲述如何借助StandMBean、DynamicMBean和ModelMBean来实现对应用的监控操作。


三、StandMBean演示

3.1 MBean接口定义

根据Standard MBean的要求,需要一个MBean接口,接口的命名规范以具体的实现类为前缀,MBean为后缀

    package com.zhru.wechat.jdk.jmx;


    /**
    * 定义的MBean
    * @Author zhru
    * @Date 2019-10-30
    **/
    public interface UserMBean {


    String getName();


    void setName(String name);
    }

    3.2  MBean接口实现

      package com.zhru.wechat.jdk.jmx;


      /**
      * UserMBean接口实现
      * @Author zhru
      * @Date 2019-10-30
      **/
      public class User implements UserMBean {


      private String name;


      @Override
      public String getName() {
      return this.name;
      }


      @Override
      public void setName(String name) {
      this.name = name;
      }


      @Override
      public String toString() {
      return "User{" +
      "name='" + name + '\'' +
      '}';
      }
      }

      3.3 定义Agent层

        package com.zhru.wechat.jdk.jmx;


        import javax.management.MBeanServer;
        import javax.management.ObjectName;
        import java.lang.management.ManagementFactory;


        /**
        * 1.定义MBeanServer
        * 2.定义{@link javax.management.ObjectName}
        * 3.注册MBean
        * @Author zhru
        * @Date 2019-10-30
        **/
        public class JmxAgent {


        public static void main(String[] args) throws Exception {
        // 定义MBeanServer
        MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();


        //定义ObjectName
        ObjectName userObjectName = new ObjectName("jmxBean:name=jmx");
        User user = new User();
        mBeanServer.registerMBean(user, userObjectName);


        // 30s后观察user对象的值
        Thread.sleep(30 * 1000);
        System.out.println(user);
        }
        }

        上面的代码流程分为3个步骤:

        1、 通过工厂类获取MbeanServer,用来做MBean的容器
        2、 定义ObjectName,取名规范为 域名:name=Mbean名称
        其中域名和MBean的名称可以任取。
        3、最后将User这个类注册到MBeanServer中

        3.4 借助Jconsole操作

        通过Java安装目录/bin/jconsole程序可以看到我们暴露出来的UserMBean,对它的name属性进行操作

        30秒后输出的user结果:

          /usr/lib/jdk1.8.0_171/bin/java
          User{name='zhru'}


          Process finished with exit code 0

          4、DynamicMBean演示

          4.1 定义DynamicMBean

          从JMX MBean的定义中,我们知道动态MBean必须实现javax.management.DynamicMBean接口,并复写接口中的方法

            package com.zhru.wechat.jdk.jmx;


            import ......;


            /**
            * DynamicMBean
            * @Author zhru
            * @Date 2019-10-30
             **/
            public class DomainBean implements DynamicMBean {
            //属性Name
            private String name;
            //属性Age
                private int age;
                
            public int getAge() {
            return age;
            }


            public void setAge(int age) {
            this.age = age;
            }




            public void setName(String name) {
            this.name = name;
            }


            public String getName() {
            return name;
            }


            public int add(int x, int y) {
            return x + y;
            }


            /**
            * 获取属性值
            * @param attribute
            * @return
            * @throws AttributeNotFoundException
            * @throws MBeanException
            * @throws ReflectionException
            */
            @Override
            public Object getAttribute(String attribute) throws AttributeNotFoundException, MBeanException, ReflectionException {
            if (attribute == null) {
            return null;
            }
            if ("name".equals(attribute)) {
            return name;
            }
            if ("age".equals(attribute)) {
            return age;
            }
            return null;
            }


            /**
            * 设置属性值
            * @param attribute
            * @throws AttributeNotFoundException
            * @throws InvalidAttributeValueException
            * @throws MBeanException
            * @throws ReflectionException
            */
            @Override
            public void setAttribute(Attribute attribute) throws AttributeNotFoundException, InvalidAttributeValueException, MBeanException, ReflectionException {
            if (attribute == null) {
            return;
            }
            if ("name".equals(attribute.getName())) {
            this.name = (String) attribute.getValue();
            }
            if ("age".equals(attribute.getName())) {
            this.age = (int) attribute.getValue();
            }
            }


            /**
            * 获取Attribute集合
            * @param attributes
            * @return
            */
            @Override
            public AttributeList getAttributes(String[] attributes) {


            List<Attribute> collect = Stream.of(attributes)
            .map(attribute -> {
            Attribute bute = null;
            try {
            Object o = getAttribute(attribute);
            bute = new Attribute(attribute, o);
            } catch (Exception e) {
            throw new RuntimeException("getAttributes()方法异常:" + e);
            }


            return bute;
            }).collect(Collectors.toList());


            return new AttributeList(collect);
            }


            /**
            * 批量设置属性
            * @param attributes
            * @return
            */
            @Override
            public AttributeList setAttributes(AttributeList attributes) {
            List<Attribute> collect = attributes
            .asList().stream().map(attribute -> {
            try {
            setAttribute(attribute);
            return attribute;
            } catch (Exception e) {
            throw new RuntimeException("setAttributes()方法异常:" + e);
            }
            }).collect(Collectors.toList());
            return new AttributeList(collect);
            }


            /**
            * 设置属性时调用的方法
            * @param actionName 方法名
            * @param params 方法参数
            * @param signature 签名数组
            * @return
            * @throws MBeanException
            * @throws ReflectionException
            */
            @Override
            public Object invoke(String actionName, Object[] params, String[] signature) throws MBeanException, ReflectionException {
            return invokeMethod(actionName, params);
            }


            /**
            * MBeanInfo信息,MBeanInfo中的信息会被暴露到MBeanServer中
            * 包括属性,操作等
            * @return
            */
            @Override
            public MBeanInfo getMBeanInfo() {
            MBeanInfo mBeanInfo = null;
            try {
            mBeanInfo = new MBeanInfo(DomainBean.class.getName(),
                                "DynamicMbeanInfo使用",
            new MBeanAttributeInfo[]{
                                    new MBeanAttributeInfo("name", String.class.getName(), "Name属性"truetruefalse),
                                    new MBeanAttributeInfo("age"int.class.getName(), "age属性"truetruefalse)
            },
            new MBeanConstructorInfo[]{
                                 new MBeanConstructorInfo("SpringUser构造器", DomainBean.class.getConstructor())
            },
            new MBeanOperationInfo[]{
                                    new MBeanOperationInfo("setName操作"this.getClass().getMethod("setName", String.class)),
                                    new MBeanOperationInfo("getName操作", DomainBean.class.getMethod("getName")),
                                    new MBeanOperationInfo("setAge操作", DomainBean.class.getMethod("setAge"int.class)),
                                    new MBeanOperationInfo("getAge操作", DomainBean.class.getMethod("getAge")),
                                    new MBeanOperationInfo("add操作"this.getClass().getMethod("add"int.class, int.class))
            },
            new MBeanNotificationInfo[]{
                     }
                         );
            } catch (Exception e) {
            throw new RuntimeException("getMBeanInfo()方法异常:" + e);
            }
            return mBeanInfo;
            }


            /**
            * 公共方法
            * @param actionName 方法名
            * @param params 方法参数
            * @return
            */
            private Object invokeMethod(String actionName, Object[] params) {
            AtomicReference<Object> obj = new AtomicReference<>();
            Method[] methods = this.getClass().getMethods();
            Arrays.stream(methods).forEach(method -> {
            if (method.getName().equals(actionName)) {
            try {
            obj.set(method.invoke(DomainBean.this, params));
            } catch (Exception e) {
            e.printStackTrace();
            }


            }
            });


            return obj.get();
            }
            }

            4.2 定义Agent层

              /**
              * 1.定义MBeanServer
              * 2.定义{@link javax.management.ObjectName}
              * 3.注册MBean
              * @Author zhru
              * @Date 2019-10-30
              **/
              public class JmxAgent {


              public static void main(String[] args) throws Exception {
              // 定义MBeanServer
              MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();


              //定义ObjectName
              ObjectName domainObjName = new ObjectName("jmxBean:name=dynamicMBean");


              // 注册MBeanServer
              mBeanServer.registerMBean(new DomainBean(), domainObjName);


              Thread.sleep(30 * 60 * 1000);
              }
              }

              4.3 借助Jconsole操作

              从Jsoncole的图中我们可以看出,我们定义MBeanInfo时暴露的属性和操作,都会在Jconsole看到,并能手动的对属性进行设置,执行暴露的操作。

              5、ModelMBean演示

              StandMBean和DynamicMBean虽然可以将MBean暴露给MBeanServer,完成Bean的控制工作,但是两者都有直接的劣势:

              StandMBean强制必须定义一个以MBean结尾的接口,并加以实现;

              DynamicMBean必须实现javax.management.DynamicMBean接口,并复写接口的方法;

              也就是说,都需要对类进行定制化的修改。倘若是接入第三方的代码,本身无法对第三方的代码进行更改,这个时候,StandMBean和DynamicMBean的劣势就会显现出来,而使用ModelMBean可以解决这个问题,实现不修改类完成对Bean的管理工作。

              Model MBean也是一种专门化的动态管理构件,允许在运行时添加或覆盖需要定制的实现。JMX规范规定该类必须实现javax.management.modelmbean.RequiredModelMBean,管理者要做的就是实例化该类,将需要管理的Bean和它需要暴露的属性和方法组成的ModelMBeanInfo信息注入到这个对象,并注册到JMX代理中,即可实现对资源的管理。

              ModelMBeanInfo对象描述了将会暴露给代理的构造函数、属性、操作和监听器。

              下面将讲述ModelMBean类型的实现流程

              5.1 定义ModelMBean

                package com.zhru.wechat.jdk.jmx;


                /**
                * ModelMBean
                 * 不需要实现任何接口
                 * @Author zhru 
                 * @Date 2019-10-30 
                **/
                public class ModelBean {


                private String beanName;


                private String beanDesc;


                public String getBeanName() {
                return beanName;
                }


                public void setBeanName(String beanName) {
                this.beanName = beanName;
                }


                public String getBeanDesc() {
                return beanDesc;
                }


                public void setBeanDesc(String beanDesc) {
                this.beanDesc = beanDesc;
                }


                @Override
                public String toString() {
                return "ModelBean{" +
                "beanDesc='" + beanDesc + '\'' +
                ", beanName='" + beanName + '\'' +
                '}';
                }
                }

                5.2  封装RequiredModelMBean

                javax.management.modelmbean.RequiredModelMBean完成Bean的暴露,必须至少设置需要暴露的Bean对象,以及该Bean对象的ModelInfo信息(描述了将会暴露给代理的构造函数、属性、操作和监听器)

                RequiredModelMBean的类结构图:

                上图中,将该类常用的方法标注了出来,通过这些方法可以设置需要暴露的Bean对象,设置BeanInfo信息和监听器(NotificationListener),发布通知(下面将详解)等行为。

                主要看下面两个方法:javax.management.modelmbean.RequiredModelMBean#setManagedResource(java.lang.Object, java.lang.String)和javax.management.modelmbean.RequiredModelMBean#setModelMBeanInfo(javax.management.modelmbean.ModelMBeanInfo)

                  /**
                   * 基于ModelBeanInfo创建ModelBean.
                   * 
                   * 一旦ModelBeanInfo设置给了ModelBean,
                   * 这个ModelBean就可以在 MBeanServer注册
                  *
                  * @param mbi The ModelMBeanInfo object to be used
                   *        by the ModelMBean.  
                  **/
                  public void setModelMBeanInfo(ModelMBeanInfo mbi)
                          throws MBeanException, RuntimeOperationsException {
                          .......
                   }
                   
                    /**
                     * 设置需要暴露属性和方法的对象
                  *
                     * @param mr 需要被管理的对象
                     * @param mr_type 资源管理类型.
                  * 只能是"ObjectReference", "Handle", "IOR", "EJBHandle",
                  * or "RMIReference".
                     *     目前只支持"ObjectReference" 
                  **/
                  public void setManagedResource(Object mr, String mr_type) {
                       .......
                  }

                  为了便于RequiredModelMBean对象的创建,设计了一个工具类:


                    /**
                    * 创建RequireModelMBean工具类
                    *
                    * @Author zhru
                    * @Date 2019-10-30
                    */
                    public class ModelMBeanUtils {


                    /**
                    * 创建{@link RequiredModelMBean}
                    *
                    * @param obj
                    * @return
                    */
                    public static RequiredModelMBean createModelMBean(Object obj) {
                    RequiredModelMBean mBean = null;
                    try {
                    mBean = new RequiredModelMBean();
                    /**
                    * RequiredModelMBean#setManagedResource(String mr, String mr_type)
                    * @param mr Object that is the managed resource
                    * @param mr_type The type of reference for the managed resource.
                    * Can be: "ObjectReference", "Handle", "IOR", "EJBHandle",
                    * or "RMIReference".
                    * In this implementation only "ObjectReference" is supported.
                    */
                    mBean.setManagedResource(obj, "ObjectReference");
                                // 创建ModelMBeanInfo
                    ModelMBeanInfo info = createModelMBeanInfo(obj.getClass());
                    mBean.setModelMBeanInfo(info);


                    } catch (Exception e) {
                    throw new RuntimeException(
                    String.format("创建对象[%s]的RequireModelBean异常:" + e,
                    obj.getClass().getName()));
                    }


                    return mBean;
                    }


                    /**
                    * 创建{@link ModelMBeanInfo}
                    * 也就是对外暴露的方法集合
                    *
                    * @param clazz 需要暴露成MBean的类
                    * @return
                    */
                    private static ModelMBeanInfo createModelMBeanInfo(Class clazz) {
                    ModelMBeanOperationInfo[] modelMBeanOperationInfos = null;
                    ModelMBeanAttributeInfo[] modelMBeanAttributeInfos = null;
                    ModelMBeanConstructorInfo[] modelMBeanConstructorInfos = null;


                    if (clazz == null) {
                    throw new RuntimeException("clazz为空");
                    }
                            // Methods生产ModelMBeanOperationInfo
                    Method[] methods = clazz.getMethods();
                    List<Method> methodList = Arrays.stream(methods)
                    .filter(method ->
                    !Object.class.equals(method.getDeclaringClass())
                    ).collect(Collectors.toList());
                    if (methodList != null && methodList.size() > 0) {
                    modelMBeanOperationInfos = new ModelMBeanOperationInfo[methodList.size()];
                    for (int i = 0; i < methodList.size(); i++) {
                    modelMBeanOperationInfos[i] =
                    new ModelMBeanOperationInfo(
                    methodList.get(i).getName() + "方法",
                    methodList.get(i));
                    }
                    }


                            // Fields生成ModelMBeanAttributeInfo
                    Field[] fields = clazz.getFields();
                    if (fields != null && fields.length > 0) {
                    modelMBeanAttributeInfos = new ModelMBeanAttributeInfo[fields.length];
                    for (int i = 0; i < fields.length; i++) {
                    modelMBeanAttributeInfos[i] =
                    new ModelMBeanAttributeInfo(
                    fields[i].getName(),
                    fields[i].getType().getName(),
                    fields[i].getName() + "属性",
                    true,
                    true,
                    false);


                    }
                    }
                            // 构造函数生产ModelMBeanConstructorInfo
                    Constructor[] constructors = clazz.getConstructors();
                    if (constructors != null && constructors.length > 0) {
                    modelMBeanConstructorInfos = new ModelMBeanConstructorInfo[constructors.length];
                    for (int i = 0; i < constructors.length; i++) {
                    modelMBeanConstructorInfos[i] =
                    new ModelMBeanConstructorInfo(
                    constructors[i].getName() + "构造方法",
                    constructors[i]
                    );
                    }
                    }


                            // 实例化ModelMBeanInfo
                    ModelMBeanInfo info = new ModelMBeanInfoSupport(
                    clazz.getName(),
                    "创建ModelMBeanInfo对象",
                    modelMBeanAttributeInfos,
                    modelMBeanConstructorInfos,
                    modelMBeanOperationInfos,
                    null);


                    return info;
                    }
                    }

                    5.3 定义Agent层

                      public static void main(String[] args) throws Exception {
                      //定义MBean Server
                      MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();


                      ModelBean bean = new ModelBean();
                      ObjectName objName = new ObjectName("jmxBean:name=modelBean");


                      //创建ModelMBean
                      RequiredModelMBean mBean = ModelMBeanUtils.createModelMBean(bean);
                      // 注册ModelMBean
                      mBeanServer.registerMBean(mBean, objName);


                      Thread.sleep(30 * 60 * 1000);
                      }

                      在Jconsole对属性进行设置和调用,结果如下:

                      6、JMX MBean通知(Notification)

                      当MBean的属性改变了的时候,是否能够被外界监听到呢?带着这个疑问,我们去查阅JMX规范,发现规范中提到了通知机制(Notification),JMX的通知机制相当于事件监听机制。当有事件发生时动态的通知监听的社备执行操作指令。

                      JMX 的通知(Notification)由四部分组成:
                      1、Notification:继承于java.util.EventObject,相当于一个信息包,封装了需要传递的信息

                      2、Notification broadcaster:通知广播器,把消息广播出。

                      3、Notification listener:通知监听器,继承于java.util.EventListener,用于监听广播出来的通知信息。

                      4、Notification filiter:通知过滤器,只有能够通知的消息才会被发送给Notification listener处理

                      以StandMBean为例,模拟UserMBean的属性(name)值发生变化时的通知。

                      修改UserMBean的setName()方法

                        /**
                          * 继承{@link javax.management.NotificationBroadcasterSupport} 
                          * 实现通知的发送操作
                          */
                        public class User extends NotificationBroadcasterSupport implements UserMBean {
                        private AtomicInteger seq = new AtomicInteger();
                        ......


                        @Override
                        public void setName(String name) {
                        String oldName = this.name;
                        this.name = name;
                                // 定义属性变化通知
                        Notification notification = new AttributeChangeNotification(
                        this,
                        seq.getAndIncrement(),
                        System.currentTimeMillis(),
                                    String.format("User的[name]属性从%s, 更新到%s", oldName, name),
                        "name",
                        name.getClass().getName(),
                        oldName,
                        name);
                                // 发送通知
                        sendNotification(notification);
                        }
                        }

                        既然有通知,基于事件监听模式,必然也有对应的通知监听器

                          /**
                          * 通知监听器
                          * @Author zhru
                          * @Date 2019-10-30
                          **/
                          public class UserNotifyListener implements NotificationListener {


                          /**
                          * 通知处理方法
                          * @param notification 通知
                          * @param handback 得到这个通知后处理回调的对象
                          */
                          @Override
                          public void handleNotification(Notification notification, Object handback) {
                                  System.out.println("得到的通知:" + notification);


                          if (handback instanceof User) {
                          User user = (User) handback;


                          // 事件操作......


                          System.out.println("属性变化后的user对象:" + user);
                          }
                          }

                          修改MBean Agent层

                            public class JmxAgent {


                            public static void main(String[] args) throws Exception {
                            //定义MBean Server
                            MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();


                            User user = new User();
                            ObjectName userObjectName = new ObjectName("jmxBean:name=jmx");


                            // 设置通知过滤器,只监听Attribuate_Change的类型
                            NotificationFilterSupport notificationFilter =
                            new NotificationFilterSupport();
                            notificationFilter.enableType(ATTRIBUTE_CHANGE);


                            //注册MBean
                            mBeanServer.registerMBean(user, userObjectName);
                            // 设置监听器
                            mBeanServer.addNotificationListener(
                            userObjectName,
                            new UserNotifyListener(),
                            notificationFilter, user);




                            Thread.sleep(30 * 60 * 1000);
                            }
                            }

                            在Jconsole中对属性name进行设值,控制台输出:

                              /usr/lib/jdk1.8.0_171/bin/java 
                              得到的通知:javax.management.AttributeChangeNotification[source=jmxBean:name=jmx][type=jmx.attribute.change][message=User的[name]属性从null, 更新到zh]
                              属性变化后的user对象:User{name='zh'}
                              得到的通知:javax.management.AttributeChangeNotification[source=jmxBean:name=jmx][type=jmx.attribute.change][message=User的[name]属性从zh, 更新到zhru]
                              属性变化后的user对象:User{name='zhru'}


                              因作者能力有限,文中可能出现描述不清的地方,不足之处请指出!希望与你一起共进步^

                              本公众号旨在记录人开发工作、学习过程中的所得、所感和所想,欢迎一起交流学习



                              文章转载自开发自由行,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

                              评论