JMX最佳实践与详解

一、JMX最佳实践

    1、Object Names(对象命名)

    每个JMX MBean都需要一个“Object Name”,选择一个始终一致的、可用的Object Names将是非常重要的;客户端与你的对象模型(model)交互时,或许是直接将它展示且可读,比如jconsole;或者它被应用程序使用Object Names直接访问你声明的对象模型。所以,在多个models之间,MBean的对象命名一个尽可能唯一。

    2、Object Name语法格式

    一个Object Name是“javax.management.ObjectName”的实例,Object Name可以是一个MBean的名字,也可以是一个“表达式”、匹配多个MBeans名称。

    它看起来像这样: “domain:key-property-list(关键属性列表)”,比如“com.demo.model:type=myType,name=myModel”。其中“domain”原则上可以为任意字符串,如果domain值为空,它将使用MBean Server的名称作为默认值;如果domain中包含“*”、“?”等正则表达式字符,这些字符串只能用来匹配(访问、查询、过滤)MBeans,不能用来声明一个MBean实例。此外domain不能包含“:”特殊字符,因为这是“domain”与“propery-list”的分隔符。

    “key-property-list”有一个或者多个关键属性组成,格式为“key=value”,比如“type=Thread”;当多个properties是,它们之间以“,”分割,比如“name=DGC,type=Thread”;需要注意,空格是有意义的,我们不需要在“,”之后使用空格,比如“type=Thread, name=DGC”,那么“ name”将会被认为是属性名称。

    此外,key的名称是有字符限制的,我们建议使用JAVA允许的、合法的标识符。value的字符也是有限制的,对于有些特殊字符,我们需要进行转义:ObjectName.quote;默认情况下,如果value是字符串(不是数字),我们应该总是转义它,除非我们能够确定它不会出现特殊字符。比如下述两个Object Names包含特殊字符(需要转义):

    com.demo.myproject:type=Whatsit,name="25"

    com.demo.myproject:type=Whatsit,name="25,26"

    第二种情况,如果不转义将被认为是非法的。

    对于表达式,“key-property-list”可以与上述的格式一样,也可以包含比如“*”、“?”甚至空字符串(与“*”等同),可以为“,*”结尾表示一个列表等。最终,这种格式用于匹配多个Object Names,它们具有指定的确切关键属性(如果有)、加上任何其他关键属性。比如“*:type=Thread,*”可以匹配“somedomain:type=Thread”、“somedomain:type=Thread,name=DGC”。

    3、Object Name约定(规范)

    1)一个MBean的Object Name应该是可以“预测的”,这意味着一个可以成为MBean的对象,我们应该能够知道它的Object Name将会什么。名称中不应该包含那些不是该对象固有部分的属性;此外,同一个对象的name不应该在application不同执行阶段而改变,比如不应该包含像“JVM进程ID”、“MBean Server ID”等这些每次执行会变化的属性。(“同一对象”的改变并不总是清晰或者有意义的,不过如果是这样,那么同一个对象始终具有相同的名称)

    2)domain部分,应该以此对象的package名称作为前缀,以避免来自不同子系统(模块)所引入的冲突;比如:

    com.demo.project1:type=Whatsit,name=5

    org.demo.project2:typpe=Whatsit

    domain不应该包含“/”,此字符串为MBean Server层次结构(级联)保留的,将会在后续版本中进行解释。

    3)对于指定“type”的Object Name,应该包含相同的关键属性列表且它们具有相同的语法,且value具有相同语义。

    如果在特定domain中,对于指定“type”的MBean只允许一个实例,那么它通常不再需要除“type”之外的、其他额外的属性。

    如果同一“type”中可能有多个实例,它们可以通过使用不同的domain(即package名)或者其他关键属性来区分。大部分情况下,domain是一样的,我们通过使用不同的“name”属性值。

    4)最常用的关键属性为:name和type。

    通常“type=X,name=Y”组合属性对一个MBean命名就足够了;一些JMX感知的控制台能够使用比其他名称更容易阅读的简写形式来显示这种形式的名称。

    有时候,我们也可以声明额外的属性,来满足上述种正则表达式匹配查询MBean的情况。比如“category”、“group”等。

    4、Object containment(遏制)

    有些托管对象(MBean)在逻辑上被其他托管对象所包含,这些对象或许无法独立存在。此时,下述模式是可行的。假如Server对象包含Application对象,Application包含WebModule对象,WebModule又包含Servlet;那么它们的名称看起来是这样的:

    domain:type=Server,name=server5

    domain:type=Server.Application,Server=server5,name=app1

    domain:type=Server.Application.WebModule,Server=server5,Application=app1,name=module3

    domain:type=Server.Application.WebModule.Servlet,Server=server5....WebModule=module3,name=servlet1

    这种分层级的“type”属性可以让我们在不需要预知Model的前提下即可理解其他keys的含义;由于Object Name中key是无序的,因此无法知道例如在这里的第三个和第四个名称中,Application是否包含在Server中,反之亦然。这种模式意味着如果一个指定type的对象包含在其他type的对象中,那么它应该总是被包含。比如Server.Application总是包含在Server中。如果有其他Application没有包含在Server对象中,那么他们应该是不同的type,即事实上它们的type属性不应该为“Server.Application”;这与“特定type的MBean总是隐含相同key属性列表”的规则是一致的。

二、MBean介绍

    MBean,即“managed bean”(托管的Bean),与普通的javaBean组件一样,只不过为JMX实现而设计;一个MBean可以表示一个“device”、“Application”或者任何需要被托管的资源。MBean可以暴露一些管理接口:

    1)一系列可读、写的属性。(getter、setter)

    2)一些可以被执行的operation。

    3)自描述信息

    管理接口,在MBean实例的整个生命周期中无法被修改;此外,MBean在遇到预定义的事件(events)发生时也可以触发通知(notification)。JMX实现中声明了5种MBean:

    1)Standard MBeans

    2)Dynamic MBeans

    3)Open MBeans

    4)Model MBeans

    5)MXBeans

    1、Standard MBeans(标准MBeans)是最常用的MBean,通常我们通过声明一个“SomethingMBean”样式的java接口、以及一个“Something”的类实现此接口的方式来创建一个MBean。此接口中方法用于声明一个属性或者操作,属性和操作的方法都遵循一定的设计规则。一个标准的MBean,由MBean接口和实现类组合而成;接口声明一系列暴露的属性和操作,实现类用于提供功能特性。

    MBean接口

    以HelloMBean为例:

package com.example; 
 
public interface HelloMBean { 
 
    public void sayHello(); 
    public int add(int x, int y); 
    
    public String getName(); 
     
    public int getCacheSize(); 
    public void setCacheSize(int size); 
}

    MBean接口采用其JAVA实现类的名称、并以“MBean”作为后缀。如示例,接口名为HelloMBean,那么其实现类为Hello。

    根据JMX实现规范,一个MBean接口由可读写(readable、writable)的、命名化和类型化的属性,还有一些可以被Application调用的操作组成。HelloMBean接口表述了2个操作:add()和sayHello;2个属性:只读的name属性、可读写的cacheSize属性;getter和setter方法允许托管的Application访问或者修改属性的值。根据JMX实现,getter是任何public方法、返回值类型不是void、且其命名以get开头;getter允许manager读取属性值,返回值对象的类型即为属性的类型。setter是任何public方法、只包含一个参数、且其命名以set开头,setter允许manager写入属性的新值,其类型与参数类型一致。

    MBean实现

public class Hello ... 
    implements HelloMBean { 
    public void sayHello() { 
        System.out.println("hello, world"); 
    } 
     
    public int add(int x, int y) { 
        return x + y; 
    } 
     
    public String getName() { 
        return this.name; 
    }  
     
    public int getCacheSize() { 
        return this.cacheSize; 
    } 
     
    public synchronized void setCacheSize(int size) {
        this.cacheSize = size; 
        ....
    } 
}

    创建JMX Agent来管理资源

    一旦资源以MBean方式表达,那么该资源将有JMX Agent来管理。JMX Agent核心组件为MBean Server,MBean Server是一个托管对象用于MBean注册;JMX Agent还包含一些管理MBeans的服务。请参见MBean Server API

import java.lang.management.*; 
import javax.management.*; 
 
public class Main { 
 
    public static void main(String[] args) 
        throws Exception { 
     
        MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); 
        ObjectName name = new ObjectName("com.example:type=Hello"); 
        Hello mbean = new Hello(); 
        mbs.registerMBean(mbean, name); 
          
        ...
     
        System.out.println("Waiting forever..."); 
        Thread.sleep(Long.MAX_VALUE); 
    } 
} 

    此main方法为示例,用于获取有Platform(通常为JVM平台、框架组件)已经创建或者初始化的MBean Server示例,通过调用getPlatformMBeanServer()方法。如果Platform尚未创建或者初始化MBean Server,那么此方法将会自动创建创建一个MBean Server实例(通过MBeanServerFactory.createMBeanServer,一旦创建此后即可公用)。

    此后创建一个ObjectName实例,每个JMX MBean都必须有一个object name,其为ObjectName类型的实例,其命名语法必须符合JMX规范。即,必须包含一个domain和关键属性列表(K-V);通常domain为package名称,“type”为类名,“name”为资源名称。

    创建一个Hello实例,并将其使用objectName注册到MBean Server中,通过MBeanServer.registerMBean()方法。注册之后,即可通过JMX 管理操作调用Hello实例的operation和属性。我们可以使用jconsole来演示MBean的属性获取和操作调用。

    2、MXBeans

    MXBean是一种特殊的MBean,它仅引用预定义数据类型。通过这种方式,你的MBean可以被任何Client使用,包括remote客户端,不需要访问表示此MBean的模型特定(model-specific)的类。MXBean提供了一种将相关值绑定在一起的便捷方式,无需客户端专门配置来处理绑定包。

    与Standard MBean一样,MXBean的定义通过声明一个SomethingMXBean接口和一个实现类,不过MXBean的实现类的名称不需要必须为Something。MXBean中的每个方法声明一个属性或者操作。@MXBean注释也可以修饰在任何已有的JAVA接口上,从而不需要专门开发一个以“MXBean”为后缀的接口。

    MXBean背后的主要思想是MXBean接口中引用的诸如“java.lang.management.MemoryUsage”之类的类型,比如“java.lang.management.MemoryMXBean”,映射成标准的类型,称为“Open Types”定义在“javax.management.openmbean”包中。具体的映射规则请参见MXBean规范实现。不过,通用原则是对于简单类型比如String、int等保持不变,对于复杂类型比如MemoryUsage类型将会映射成标准类型“CompositeDataSupport”。

    有关MBean和MXBean的区别,请参考:https://docs.oracle.com/javase/8/docs/api/javax/management/MXBean.html

    MXBean接口

package com.example; 
 
public interface QueueSamplerMXBean { 
    public QueueSample getQueueSample(); 
    public void clearQueue(); 
}

    

    MXBean实现

package com.example; 
 
import java.util.Date; 
import java.util.Queue; 
 
public class QueueSampler 
                implements QueueSamplerMXBean { 
     
    private Queue<String> queue; 
         
    public QueueSampler (Queue<String> queue) { 
        this.queue = queue; 
    } 
         
    public QueueSample getQueueSample() { 
        synchronized (queue) { 
            return new QueueSample(new Date(), 
                           queue.size(), queue.peek()); 
        } 
    } 
         
    public void clearQueue() { 
        synchronized (queue) { 
            queue.clear(); 
        } 
    } 
}

    声明复杂类型

package com.example; 
 
import java.beans.ConstructorProperties; 
import java.util.Date; 
 
public class QueueSample { 
     
    private final Date date; 
    private final int size; 
    private final String head; 
         
    @ConstructorProperties({"date", "size", "head"}) 
    public QueueSample(Date date, int size, 
                        String head) { 
        this.date = date; 
        this.size = size; 
        this.head = head; 
    } 
         
    public Date getDate() { 
        return date; 
    } 
         
    public int getSize() { 
        return size; 
    } 
         
    public String getHead() { 
        return head; 
    } 
}

    在QueueSample类中,MXBean框架通过所有的getter方法来将此实例转换为CompositeData实例(数组);(在反序列化时)使用@ConstructorProperties注释再从CompositeData实例中重建QueueSample实例。

    JMX Agent中注册MXBean的方式与MBean一样,没有任何区别。

    简单来说,如果你的MBean中的属性均为简单类型,用Standard MBean即可;如果有复杂类型,比如自定义的类,那么需要使用MXBean,并由CompositeData将复杂类型的属性进行转换。(复杂类型也是由JAVA的简单类型组合而成)。

     JAVA已经内置了多个MXBean实现,可以帮助大家来学习JMX的相关技术。

    1、BufferPoolMXBean:有关“direct”、“mapped” buffer的资源信息;如果Application为网络IO系统(比如Netty编程)、或者有大量文件操作,你应该考虑关注此MXBean。

    2、ClassLoadingMXBean:有关JVM类加载相关的资源信息;如果Application为序列化相关的组件、脚本化集成组件、有较多代理类(包括动态加载,OSGI)等,你应该关注此MXBean。

    3、GarbageCollectorMXBean:有关JVM GC相关的资源,包括GC时长、GC次数和相关内存状态。

    4、MemoryPoolMXBean有关JVM中“内存池”的相关资源信息,可以配合MemoryManagerMXBean一起使用。一个Application中可能有多个“内存池”实例,我们可以通过MemoryManagerMXBean获取内存池的列表,并查看此内存池的存量和GC相关信息。

    5、OperatingSystemMXBean:有关操作系统的相关资源信息,比如CPU负载等。

    6、PlatformManagedObject:内部接口,所有的JAVA平台有关的MXBean都扩展此接口,比如上述几个MXBean;通常应用程序不应该实现它。

    7、RuntimeMXBean:有关runtime的信息,比如VM的参数、版本等。

    8、ThreadMXBean:有关运行时线程状态的资源信息,比如“CPU高耗线程”、“死锁线程”等,可以帮助我们优化并发操作等。

    

三、JMX代码样例

    我们可以参考很多开源组件的JMX实现,比如tomcat-jdbc等,如下示例仅供参考,我们建议JMX MBean注册、注销操作应该在MBean实现类中,此外也要求大家在开发组件时,尽量增加MBean信息托管,这对我们探测组件运行时状态数据非常有效,而且对监控良好。

/**
 * Description
 * <p>
 * </p>
 * DATE 17/12/9.
 *
 * @author liuguanqing.
 */
public class SlowQuery implements SlowQueryMXBean{

    private String name;
    public SlowQuery() {

    }

    public SlowQuery(String name) {
        this.name = name;
    }

    protected void registerJmx() {
        try {
            MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();
            ObjectName objectName = getObjectName();
            if(mBeanServer.isRegistered(objectName)) {
                return;
            }
            mBeanServer.registerMBean(this, objectName);
        } catch (MalformedObjectNameException e) {
            //ObjectName不合法时,建议统一ObjectName.quote(string)
        } catch (RuntimeOperationsException e) {
            //JMX operation操作异常时
        } catch (MBeanException e) {
            //其他异常
        } catch (InstanceAlreadyExistsException e) {
            //如果相同名称的MBean已注册
        } catch (NotCompliantMBeanException e) {
            //如果MBean无法被JMX Agent无法兼容
        } catch (Exception e) {
            //
        }
    }

    protected ObjectName getObjectName() throws Exception{
        Class clazz  = getClass();
        if(name == null) {
            name = clazz.getSimpleName();
        }
        return new ObjectName(clazz.getPackage().getName() + ":type=" + clazz.getSimpleName() + ",name=" + name);
    }

    protected void deRegisterJmx() {
        try {
            ManagementFactory.getPlatformMBeanServer().unregisterMBean(getObjectName());
        } catch (MBeanRegistrationException e) {
            //
        } catch (InstanceNotFoundException e) {
            //
        } catch (MalformedObjectNameException e) {
            //
        } catch (RuntimeOperationsException e) {
            //
        } catch (Exception e) {
            //
        }

    }
}

四、Spring与JMX(简述)

    Spring中接入JMX的方式比较多,我们仅描述一种常用的、易于实施的方式。具体参见:Spring与JMX

    1、声明MBean,基于Spring注释

import org.springframework.jmx.export.annotation.ManagedAttribute;
import org.springframework.jmx.export.annotation.ManagedOperation;
import org.springframework.jmx.export.annotation.ManagedOperationParameter;
import org.springframework.jmx.export.annotation.ManagedResource;
import org.springframework.stereotype.Component;

/**
 * Description
 * <p>
 * </p>
 * DATE 17/12/9.
 *
 * @author liuguanqing.
 */
//@ManagedResource(objectName = "com.demo.web.MyApp:type=HttpRequestGlobalInfo,name=httpRequestGlobal")
@Component
@ManagedResource
public class HttpRequestGlobalInfo {

    private long totalRequestTimes;//请求总次数
    private long avgResponseTime;//平均响应时间

    @ManagedAttribute()
    public long getTotalRequestTimes() {
        return totalRequestTimes;
    }

    public void setTotalRequestTimes(long totalRequestTimes) {
        this.totalRequestTimes = totalRequestTimes;
    }

    @ManagedAttribute()
    public long getAvgResponseTime() {
        return avgResponseTime;
    }

    public void setAvgResponseTime(long avgResponseTime) {
        this.avgResponseTime = avgResponseTime;
    }

    @ManagedOperation
    public void reset(@ManagedOperationParameter(name = "all",description = "all")boolean all) {
        if(all) {
            totalRequestTimes = 0;
        }
        avgResponseTime = 0;
    }

    @ManagedOperation
    public void reset() {
        reset(false);
    }
}

    1)此Bean需要为SpringBean,我们可以通过@Component或者@Resource声明,当然你也可以在Spring.xml配置。

    2)MBean上必须使用@ManagedResource注释,此后Spring将会通过代理 + 自动扫描的方式(配置注释驱动)来创建MBean实例以及注册到MBean Server。

    ManagedResource注释中,可以声明objectName,此值建议遵循上述种JMX规范。如果不指定objectName和相关参数,那么将会采用“domain”(来自配置文件)作为域,“name”属性为SpringBean名称(默认值为类名简写,首字母小写,比如:httpRequestGlobalInfo)、“type”属性为“类名简写”(比如HttpRequestGlobalInfo)。

    通常有两种选择:

        A)你可以统一在spring.xml中指定domain,@ManagedResource中不再指定objectName;这种方式也利于remote Client来统一操作,因为这些MBean都在一个domain中。

        B)你希望自定义domain(比如package名作为domain),那么你可以在@ManagedResource中指定objectName。(推荐方式)

    3)@ManagedAttribute,声明在属性的getter或者setter方法上。主要是Spring与JMX Agent兼容(代理类)。

    4)@ManagedOperation,声明在操作方法上,表示此方法问MBean的operation。(否则,只认为是普通的java方法,不会被export)

    5)@ManagedOperationParameter,可以声明在operation的方法上,也可以声明在方法的参数上,用于表示此operation允许的参数列表。(代理类)

    2、spring.xml配置

<bean id="jmxExporter" class="org.springframework.jmx.export.annotation.AnnotationMBeanExporter" lazy-init="false">
    <!-- 需要人工注册的、非Spring实现的MBean,比如dataSource -->
    <property name="beans">
        <map>
            <entry
                    key="org.apache.tomcat.jdbc.pool.jmx:name=dataSourceMBean,type=ConnectionPool"
                    value="#{datasource.getPool().getJmxPool()}"/>
        </map>
    </property>
    <!-- 对于Spring注释驱动的MBean,将自动加载 -->
    <!-- 此处domain,仅供示例展示,通常建议在@ManagedResource中指定objectName -->
    <property name="defaultDomain" value="Application" />
</bean>
<!-- 如果你没有开启Component-scan,那么你需要声明MBean作为springBean -->

    3、JMX Agent

    最常用的Agent就是jconsole工具,你可以运行此UI,查看MBean的所有信息。不过在Production环境中,或许remote端口无法访问,jconsole则无法使用,我们建议大家的WEB项目可以基于jolokia组件作为嵌入式的JMX Agent。接入jolokia也是非常简单,请参见:jolokia与Spirng JMX监控

文档参考:

    1、JMX最佳实践:http://www.oracle.com/us/technologies/java/best-practices-jsp-136021.html

    2、ObjectName:https://docs.oracle.com/javase/1.5.0/docs/api/javax/management/ObjectName.html

    3、MBean介绍:https://docs.oracle.com/javase/tutorial/jmx/mbeans/index.html

    4、JVM内部MXBean:https://docs.oracle.com/javase/8/docs/api/java/lang/management/package-summary.html

猜你喜欢

转载自shift-alt-ctrl.iteye.com/blog/2404103
JMX