JMX超詳細解讀

JMX超詳細解讀

一、JMX的定義  

  JMX(Java Management Extensions)是一個爲應用程序植入管理功能的框架。JMX是一套標準的代理和服務,實際上,用戶可以在任何Java應用程序中使用這些代理和服務實現管理。這是官方文檔上的定義,我看過很多次也無法很好的理解。我個人的理解是JMX讓程序有被管理的功能,例如你開發一個WEB網站,它是在24小時不間斷運行,那麼你肯定會對網站進行監控,如每天的UV、PV是多少;又或者在業務高峯的期間,你想對接口進行限流,就必須去修改接口併發的配置值。

  應用場景:中間件軟件WebLogic的管理頁面就是基於JMX開發的,而JBoss則整個系統都基於JMX構架。

對於一些參數的修改,網上有一段描述還是比較形象的:

1、程序初哥一般是寫死在程序中,到要改變的時候就去修改代碼,然後重新編譯發佈。

2、程序熟手則配置在文件中(JAVA一般都是properties文件),到要改變的時候只要修改配置文件,但還是必須重啓系統,以便讀取配置文件裏最新的值。

3、程序好手則會寫一段代碼,把配置值緩存起來,系統在獲取的時候,先看看配置文件有沒有改動,如有改動則重新從配置裏讀取,否則從緩存裏讀取。

4、程序高手則懂得物爲我所用,用JMX把需要配置的屬性集中在一個類中,然後寫一個MBean,再進行相關配置。另外JMX還提供了一個工具頁,以方便我們對參數值進行修改。

二、JMX架構圖:

從圖中我們可以看到,JMX的結構一共分爲三層:

1、基礎層:主要是MBean,被管理的資源。

MBean分爲如下四種,我接下來主要介紹standard MBean

類型 描述
standard MBean 這種類型的MBean最簡單,它能管理的資源(包括屬性,方法,時間)必須定義在接口中,然後MBean必須實現這個接口。它的命名也必須遵循一定的規範,例如我們的MBean爲Hello,則接口必須爲HelloMBean。
dynamic MBean 必須實現javax.management.DynamicMBean接口,所有的屬性,方法都在運行時定義
open MBean 此MBean的規範還不完善,正在改進中
model MBean 與標準和動態MBean相比,你可以不用寫MBean類,只需使用javax.management.modelmbean.RequiredModelMBean即可。RequiredModelMBean實現了ModelMBean接口,而ModelMBean擴展了DynamicMBean接口,因此與DynamicMBean相似,Model MBean的管理資源也是在運行時定義的。與DynamicMBean不同的是,DynamicMBean管理的資源一般定義在DynamicMBean中(運行時才決定管理那些資源),而model MBean管理的資源並不在MBean中,而是在外部(通常是一個類),只有在運行時,才通過set方法將其加入到model MBean中。後面的例子會有詳細介紹

2、適配層:MBeanServer,主要是提供對資源的註冊和管理。

3、接入層:提供遠程訪問的入口。

 

接下來我這裏會用程序來介紹三種訪問JMX的方式:

三、JDK的小工具Jconsole訪問

 1、 首先定義一個MBean接口,接口的命名規範爲以具體的實現類爲前綴(這個規範很重要)

複製代碼
 1 package jmx;
 2 
 3 public interface HelloMBean
 4 {
 5      public String getName();
 6         
 7      public void setName(String name);
 8         
 9      public String getAge();
10 
11      public void setAge(String age);
12         
13      public void helloWorld();
14         
15      public void helloWorld(String str);
16         
17      public void getTelephone();
18 }
複製代碼

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層:

複製代碼
 1 package jmx;
 2 
 3 import java.lang.management.ManagementFactory;
 4 
 5 import javax.management.JMException;
 6 import javax.management.MBeanServer;
 7 import javax.management.ObjectName;
 8 
 9 public class HelloAgent
10 {
11     public static void main(String[] args) throws JMException, Exception
12     {
13          MBeanServer server = ManagementFactory.getPlatformMBeanServer();
14          ObjectName helloName = new ObjectName("jmxBean:name=hello");
15          //create mbean and register mbean
16          server.registerMBean(new Hello(), helloName);
17          Thread.sleep(60*60*1000);
18     }
19 }
複製代碼

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、控制檯打印如下:

四、通過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中的管理界面類似,都可以操作資源中的屬性和方法。

五、通過客戶端程序進行遠程訪問

 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進行遠程連接:

複製代碼
 1 package jmx;
 2 
 3 import java.io.IOException;
 4 
 5 import javax.management.Attribute;
 6 import javax.management.MBeanServerConnection;
 7 import javax.management.MBeanServerInvocationHandler;
 8 import javax.management.ObjectName;
 9 import javax.management.remote.JMXConnector;
10 import javax.management.remote.JMXConnectorFactory;
11 import javax.management.remote.JMXServiceURL;
12 
13 
14 public class Client
15 {
16     public static void main(String[] args) throws IOException, Exception, NullPointerException
17     {
18         JMXServiceURL url = new JMXServiceURL
19             ("service:jmx:rmi:///jndi/rmi://localhost:9999/jmxrmi");
20         JMXConnector jmxc = JMXConnectorFactory.connect(url,null);
21         
22         MBeanServerConnection mbsc = jmxc.getMBeanServerConnection();
23         //ObjectName的名稱與前面註冊時候的保持一致
24         ObjectName mbeanName = new ObjectName("jmxBean:name=hello");
25         
26         System.out.println("Domains ......");
27         String[] domains = mbsc.getDomains();
28         
29         for(int i=0;i<domains.length;i++)
30         {
31             System.out.println("doumain[" + i + "]=" + domains[i] );
32         }
33         
34         System.out.println("MBean count = " + mbsc.getMBeanCount());
35         //設置指定Mbean的特定屬性值
36         //這裏的setAttribute、getAttribute操作只能針對bean的屬性
37         //例如對getName或者setName進行操作,只能使用Name,需要去除方法的前綴
38         mbsc.setAttribute(mbeanName, new Attribute("Name","杭州"));
39         mbsc.setAttribute(mbeanName, new Attribute("Age","1990"));
40         String age = (String)mbsc.getAttribute(mbeanName, "Age");
41         String name = (String)mbsc.getAttribute(mbeanName, "Name");
42         System.out.println("age=" + age + ";name=" + name);
43         
44         HelloMBean proxy = MBeanServerInvocationHandler.
45             newProxyInstance(mbsc, mbeanName, HelloMBean.class, false);
46         proxy.helloWorld();
47         proxy.helloWorld("migu");
48         proxy.getTelephone();
49         //invoke調用bean的方法,只針對非設置屬性的方法
50         //例如invoke不能對getName方法進行調用
51         mbsc.invoke(mbeanName, "getTelephone", null, null);
52         mbsc.invoke(mbeanName, "helloWorld", 
53             new String[]{"I'll connect to JMX Server via client2"}, new String[]{"java.lang.String"});
54         mbsc.invoke(mbeanName, "helloWorld", null, null);
55     }
56 }
複製代碼

a、在35到41行,是對屬性進行賦值和取值,這裏我們不能直接調用方法,而是通過setAttribute、getAttrubute方法來進行操作,則屬性的首字母要大寫。

b、對資源裏面的方法進行操作有兩種方式:一是通過代理直接調用方法;二是通過JAVA的反射注入的方式進行方法的調用。

下面我們來看看執行結果,先執行agent,再執行客戶端:

c、client的控制檯打印結果:

 

d、agent控制檯打印結果:

 六、Notification

 MBean之間的通信是必不可少的,Notification就起到了在MBean之間溝通橋樑的作用。JMX 的通知由四部分組成:

1、Notification這個相當於一個信息包,封裝了需要傳遞的信息

2、Notification broadcaster這個相當於一個廣播器,把消息廣播出。

3、Notification listener 這是一個監聽器,用於監聽廣播出來的通知信息。

4、Notification filiter 這個一個過濾器,過濾掉不需要的通知。這個一般很少使用。

這裏我們使用日常打招呼的場景:jack與我偶遇,jack說:hi;我禮貌的回答:hello,jack。

這裏我們先分別創建兩個資源:

複製代碼
package jmx;

/*
 * 該類名稱必須與實現的接口的前綴保持一致(即MBean前面的名稱
 */
public class Hello implements HelloMBean
{
    private String name;

    public String getName()
    {
        return name;
    }

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

    public void printHello()
    {
        System.out.println("Hello World, " + name);
    }

    public void printHello(String whoName)
    {
        System.out.println("Hello , " + whoName);
    }
}
複製代碼
複製代碼
package jmx;

/*
 * 接口名必須以MBean結尾
 */
public interface HelloMBean
{
    public String getName();   
    
    public void setName(String name);   
  
    public void printHello();   
  
    public void printHello(String whoName);
}
複製代碼
複製代碼
package jmx;

import javax.management.Notification;
import javax.management.NotificationBroadcasterSupport;

public class Jack extends NotificationBroadcasterSupport implements JackMBean
{
    private int seq = 0;
    public void hi()
    {
         //創建一個信息包
        Notification notify = 
            //通知名稱;誰發起的通知;序列號;發起通知時間;發送的消息
            new Notification("jack.hi",this,++seq,System.currentTimeMillis(),"jack");
        sendNotification(notify);
    }

}
複製代碼
複製代碼
package jmx;

public interface JackMBean
{
    public void hi();
}
複製代碼

這裏的類Jack不僅實現了MBean接口,還繼承了NotificationBroadcasterSupport。jack在這裏創建併發送了一個消息包。

複製代碼
package jmx;

import javax.management.Notification;
import javax.management.NotificationListener;

public class HelloListener implements NotificationListener
{

    public void handleNotification(Notification notification, Object handback)
    {
        if(handback instanceof Hello)
        {
            Hello hello = (Hello)handback;
            hello.printHello(notification.getMessage());
        }
    }
    
}
複製代碼
複製代碼
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("yunge:name=Hello");    
        Hello hello=new Hello();          
        server.registerMBean(hello, helloName);  
        Jack jack = new Jack();
        server.registerMBean(jack, new ObjectName("jack:name=Jack"));
        jack.addNotificationListener(new HelloListener(), null, hello);
        Thread.sleep(500000);
    }
}
複製代碼

我們用Jconsole來進行訪問:

這裏我們可以看到有兩個MBean,一個是yunge,一個是jack。我們執行jack的hi方法後,去看下控制檯上的打印信息;

 七、linux下利用JMX監控Tomcat

  利用JMX監控Tomcat,就是相當於部署在tomcat上的應用作爲服務端,也就是被管理資源的對象。然後通過程序或者jconsole遠程連接到該應用上來。遠程連接需要服務器端提供ip和port。如果需要加密訪問的話,還需要配置用戶名、密碼等參數。

主要是在tomcat下的文件catalina.sh中進行一些環境變量的配置配置:

 

-Dcom.sun.management.jmxremote=true                 相關 JMX 代理偵聽開關

-Djava.rmi.server.hostname                                     服務器端的IP
-Dcom.sun.management.jmxremote.port=29094             相關 JMX 代理偵聽請求的端口

-Dcom.sun.management.jmxremote.ssl=false              指定是否使用 SSL 通訊

-Dcom.sun.management.jmxremote.authenticate=false     指定是否需要密碼驗證

這樣就可以通過客戶端或者jconsole對tomcat進行監控。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章