JMX - JMX监控原理与使用详解

总结

言简意赅版本:https://mikechen.cc/17310.html

一、JMX简介

参考:https://honeypps.com/java/jmx-quick-start-1-standard-mbean/

JMX的全称为Java Management Extensions. 顾名思义,是管理Java的一种扩展。这种机制可以方便的管理、监控正在运行中的Java程序。常用于管理线程,内存,日志Level,服务重启,系统环境等。

基本术语

  • MBean:是Managed Bean的简称,可以翻译为“管理构件”。在JMX中MBean代表一个被管理的资源实例,通过MBean中暴露的方法和属性,外界可以获取被管理的资源的状态和操纵MBean的行为。事实上,MBean就是一个Java Object,同JavaBean模型一样,外界使用自醒和反射来获取Object的值和调用Object的方法,只是MBean更为复杂和高级一些。MBean通过公共方法以及遵从特定的设计模式封装了属性和操作,以便暴露给管理应用程序。例如,一个只读属性在管理构件中只有Get方法,既有Get又有Set方法表示是一个可读写的属性。一共有四种类型的MBean: Standard MBean, Dynamic MBean, Open MBean, Model MBean。
  • MBeanServer:MBean生存在一个MBeanServer中。MBeanServer管理这些MBean,并且代理外界对它们的访问。并且MBeanServer提供了一种注册机制,是的外界可以通过名字来得到相应的MBean实例。
  • JMX Agent:Agent只是一个Java进程,它包括这个MBeanServer和一系列附加的MbeanService。当然这些Service也是通过MBean的形式来发布。
  • Protocol Adapters and Connectors:MBeanServer依赖于Protocol Adapters和Connectors来和运行该代理的Java虚拟机之外的管理应用程序进行通信。Protocol Adapters通过特定的协议提供了一张注册在MBeanServer的MBean的视图。例如,一个HTML Adapter可以将所有注册过的MBean显示在Web 页面上。不同的协议,提供不同的视图。Connectors还必须提供管理应用一方的接口以使代理和管理应用程序进行通信,即针对不同的协议,Connectors必须提供同样的远程接口来封装通信过程。当远程应用程序使用这个接口时,就可以通过网络透明的和代理进行交互,而忽略协议本身。Adapters和Connectors使MBean服务器与管理应用程序能进行通信。因此,一个代理要被管理,它必须提供至少一个Protocol Adapter或者Connector。面临多种管理应用时,代理可以包含各种不同的Protocol Adapters和Connectors。当前已经实现和将要实现的Protocol Adapters和Connectors包括: RMI Connector, SNMP Adapter, IIOP Adapter, HTML Adapter, HTTP Connector.

Adapter 和Connector的区别在于:Adapter是使用某种Internet协议来与JMX Agent获得联系,Agent端会有一个对象 (Adapter)来处理有关协议的细节。比如SNMP Adapter和HTTP Adapter。而Connector则是使用类似RPC的方式来访问Agent,在Agent端和客户端都必须有这样一个对象来处理相应的请求与应答。比如RMI Connector。

JMX Agent可以带有任意多个Adapter,因此可以使用多种不同的方式访问Agent。

JMX基本构架

JMX分为三层,分别负责处理不同的事务。它们分别是:

  • Instrumentation 层
    Instrumentation层主要包括了一系列的接口定义和描述如何开发MBean的规范。通常JMX所管理的资源有一个或多个MBean组成,因此这个资源可以是任何由Java语言开发的组件,或是一个JavaWrapper包装的其他语言开发的资源。
  • Agent 层
    Agent 用来管理相应的资源,并且为远端用户提供访问的接口。Agent层构建在Intrumentation层之上,并且使用并管理 Instrumentation层内部描述的组件。Agent层主要定义了各种服务以及通信模型。该层的核心是一MBeanServer,所有的MBean都要向它注册,才能被管理。注册在MBeanServer上的MBean并不直接和远程应用程序进行通信,他们通过协议适配器(Adapter)和连接器(Connector)进行通信。通常Agent由一个MBeanServer和多个系统服务组成。JMX Agent并不关心它所管理的资源是什么。
  • Distributed 层
    Distributed层关心Agent如何被远端用户访问的细节。它定义了一系列用来访问Agent的接口和组件,包括Adapter和Connector的描述。

如果一个Java对象可以由一个遵循JMX规范的管理器应用管理,那么这个Java对象就可以由JMX管理资源。要使一个Java对象可管理,则必须创建相应的MBean对象,并通过这些MBean对象管理相应的Java对象。当拥有MBean类后,需要将其实例化并注册到MBeanServer上。

 

JMX的优点

//优点就是两个字:监控!

参考文章:https://www.zhihu.com/question/36688387/answer/1264761873

这门技术是对Java应用程序和JVM进行监控和管理的,在企业实际开发过程中,所有的程序都是需要进行监控的,没有监控,程序就相当于是裸奔。

在大公司中,没有监控是绝对不可能的。JMX是Java官方提供的一套用于监控Java程序和JVM运行时状态的标准API。通过JMX,我们可以监控的内容包括:

  1. 服务器中各种资源的使用情况:如CPU、内存等
  2. JVM内存使用情况
  3. JVM中的线程情况
  4. JVM中加载的类
  5. ...

很多开源软件都是用JMX来实现性能监控的,比如大名鼎鼎的Kafka:

Kafka 的每个监控指标都是以 JMX MBean 的形式定义的,具体来说,Kafka 使用基于 Metrics 的监控指标体系在方法调用的过程中设置埋点来统计 broke 端和 clients 端的各种监控指标,然后将采集的指标通过Metrics的JmxReporter送往JMX中。

JConsole是用来提供运行于Java平台中的应用程序的性能与资源消耗信息的一款工具,随着JDK一同安装,只要你的电脑上安装了JDK,就肯定安装了JConsole。JMX采集到的Kafka性能数据可以通过JConsole访问到:

通过JConsole访问JMX

Kafka 通过JMX提供了几百个性能指标,上面这样展示方式看起来肯定是有点low。优化之后:利用JMX提供的接口可以让我们自己的程序获取到JMX中的数据,比如开源Kafka监控工具Kafka Eagle通过获取Kafka的JMX数据:

 

 

二、3种访问JMX的方式

参考文章:https://www.cnblogs.com/dongguacai/p/5900507.html

2.1 通过Jconsole访问

1、 首先定义一个MBean接口,接口的命名规范为以具体的实现类为前缀(这个规范很重要)

package jmx;

public interface HelloMBean
{
     public String getName();

     public void setName(String name);

     public String getAge();

     public void setAge(String age);

     public void helloWorld();

     public void helloWorld(String str);

     public void getTelephone();
}

 

2、定义一个实现类,实现上面的接口:

1 package jmx;
 2 
 3 /*
 4  * 该类名称必须与实现的接口的前缀保持一致(即MBean前面的名称
 5  */
 6 public class Hello implements HelloMBean
 7 {
 8     private String name;
 9         
10     private String age;
11 
12     public void getTelephone()
13     {
14         System.out.println("get Telephone");
15     }
16 
17     public void helloWorld()
18     {
19         System.out.println("hello world");
20     }
21 
22     public void helloWorld(String str)
23     {
24         System.out.println("helloWorld:" + str);
25     }
26 
27     public String getName()
28     {
29         System.out.println("get name 123");
30         return name;
31     }
32 
33     public void setName(String name)
34     {
35         System.out.println("set name 123");
36         this.name = name;
37     }
38 
39     public String getAge()
40     {
41         System.out.println("get age 123");
42         return age;
43     }
44 
45     public void setAge(String age)
46     {
47         System.out.println("set age 123");
48         this.age = age;
49     }      
53 }

 

3、定义agent层:

package jmx;

import java.lang.management.ManagementFactory;

import javax.management.JMException;
import javax.management.MBeanServer;
import javax.management.ObjectName;

public class HelloAgent
{
    public static void main(String[] args) throws JMException, Exception
    {
         MBeanServer server = ManagementFactory.getPlatformMBeanServer();
         ObjectName helloName = new ObjectName("jmxBean:name=hello");
         //create mbean and register mbean
         server.registerMBean(new Hello(), helloName);
         Thread.sleep(60*60*1000);
    }
}

 

1、其中第13行是通过工厂类获取MBeanServer,用来做MBean的容器 。

2、第14行中的ObjectName中的取名是有一定规范的,格式为:“域名:name=MBean名称”,其中域名和MBean的名称可以任意取。这样定义后,就可以唯一标识我们定义的这个MBean的实现类了

3、第16行是将Hello这个类注入到MBeanServer中,注入需要创建一个ObjectName类 

这样,一个简单的JMX的DEMO已经写完了,现在我们通过JDK提供的Jconsole来进行操作。

1、首先在自己的本地路径下:C:\Program Files (x86)\Java\jdk1.6.0_43\bin找到jconsole.exe这个小工具,双击打开:

2、双击打开我们的本地进程:HelloAgent:

3.在这个界面上,我们可以给程序中HelloMBean的属性赋值,也可以调用其中的方法:

4、控制台打印如下:

 

 

2.2 通过JMX提供的工具页访问

这里,我们复用上面的接口和实现类,只需要改动适配层,这里需要到导入外部jar包jdmk

package jmx;

import java.lang.management.ManagementFactory;

import javax.management.JMException;
import javax.management.MBeanServer;
import javax.management.ObjectName;

import com.sun.jdmk.comm.HtmlAdaptorServer;

public class HelloAgent
{
    public static void main(String[] args) throws JMException, Exception
    {
         MBeanServer server = ManagementFactory.getPlatformMBeanServer();
         ObjectName helloName = new ObjectName("jmxBean:name=hello");
         //create mbean and register mbean
         server.registerMBean(new Hello(), helloName);
         
         ObjectName adapterName = new ObjectName("HelloAgent:name=htmladapter,port=8082");   
         HtmlAdaptorServer adapter = new HtmlAdaptorServer();   
         server.registerMBean(adapter, adapterName);  
         adapter.start();
    }
}

我们访问地址:http://localhost:8082,点击name=hello:

1、在这里创建一个AdaptorServer,这个类将决定MBean的管理界面,这里用最普通的Html型界面。AdaptorServer其实也是一个MBean。 

2、我们可以看到这个工具页,其实与我们上一个案例中的Jconsole中的管理界面类似,都可以操作资源中的属性和方法。

 

2.3 通过客户端进行远程访问JMX

1、这里需要对agent进行修改,增加ip和porta绑定部分的逻辑

package jmxTest;

import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;

import javax.management.JMException;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.management.remote.JMXConnectorServer;
import javax.management.remote.JMXConnectorServerFactory;
import javax.management.remote.JMXServiceURL;

public class HelloAgent
{
    public static void main(String[] args) throws JMException, NullPointerException
    {
        MBeanServer server = ManagementFactory.getPlatformMBeanServer();
        ObjectName helloName = new ObjectName("jmxBean:name=hello");
        //create mbean and register mbean
        server.registerMBean(new Hello(), helloName);
        try
        {
            //这个步骤很重要,注册一个端口,绑定url后用于客户端通过rmi方式连接JMXConnectorServer
            LocateRegistry.createRegistry(9999);
            //URL路径的结尾可以随意指定,但如果需要用Jconsole来进行连接,则必须使用jmxrmi
            JMXServiceURL url = new JMXServiceURL
                  ("service:jmx:rmi:///jndi/rmi://localhost:9999/jmxrmi");
            JMXConnectorServer jcs = JMXConnectorServerFactory.newJMXConnectorServer(url, null, server);
            System.out.println("begin rmi start");
            jcs.start();
            System.out.println("rmi start");
        }
        catch (RemoteException e)
        {
            e.printStackTrace();
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }
          }}

 

写到这里,如果没有client进行远程连接,可以使用Jconsole进行远程访问:

2、客户端Client程序,用于与agent进行远程连接:

package jmx;

import java.io.IOException;

import javax.management.Attribute;
import javax.management.MBeanServerConnection;
import javax.management.MBeanServerInvocationHandler;
import javax.management.ObjectName;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;


public class Client
{
    public static void main(String[] args) throws IOException, Exception, NullPointerException
    {
        JMXServiceURL url = new JMXServiceURL
            ("service:jmx:rmi:///jndi/rmi://localhost:9999/jmxrmi");
        JMXConnector jmxc = JMXConnectorFactory.connect(url,null);

        MBeanServerConnection mbsc = jmxc.getMBeanServerConnection();
        //ObjectName的名称与前面注册时候的保持一致
        ObjectName mbeanName = new ObjectName("jmxBean:name=hello");

        System.out.println("Domains ......");
        String[] domains = mbsc.getDomains();

        for(int i=0;i<domains.length;i++)
        {
            System.out.println("doumain[" + i + "]=" + domains[i] );
        }

        System.out.println("MBean count = " + mbsc.getMBeanCount());
        //设置指定Mbean的特定属性值
        //这里的setAttribute、getAttribute操作只能针对bean的属性
        //例如对getName或者setName进行操作,只能使用Name,需要去除方法的前缀
        mbsc.setAttribute(mbeanName, new Attribute("Name","杭州"));
        mbsc.setAttribute(mbeanName, new Attribute("Age","1990"));
        String age = (String)mbsc.getAttribute(mbeanName, "Age");
        String name = (String)mbsc.getAttribute(mbeanName, "Name");
        System.out.println("age=" + age + ";name=" + name);

        HelloMBean proxy = MBeanServerInvocationHandler.
            newProxyInstance(mbsc, mbeanName, HelloMBean.class, false);
        proxy.helloWorld();
        proxy.helloWorld("migu");
        proxy.getTelephone();
        //invoke调用bean的方法,只针对非设置属性的方法
        //例如invoke不能对getName方法进行调用
        mbsc.invoke(mbeanName, "getTelephone", null, null);
        mbsc.invoke(mbeanName, "helloWorld",
            new String[]{"I'll connect to JMX Server via client2"}, new String[]{"java.lang.String"});
        mbsc.invoke(mbeanName, "helloWorld", null, null);
    }
}

 

a、在35到41行,是对属性进行赋值和取值,这里我们不能直接调用方法,而是通过setAttribute、getAttrubute方法来进行操作,则属性的首字母要大写。

b、对资源里面的方法进行操作有两种方式:一是通过代理直接调用方法;二是通过JAVA的反射注入的方式进行方法的调用。

下面我们来看看执行结果,先执行agent,再执行客户端:

c、client的控制台打印结果:

 

d、agent控制台打印结果:

 

posted on 2019-04-17 13:19  frank_cui  阅读(299)  评论(0编辑  收藏  举报

导航

levels of contents