close

最近因為工作的關係,需要使用到 JMX ,上網 Google 了一下,發現資料並不多,特別是中文的資料很少,要不就是有謬誤,就想說自己寫一篇,這樣之後的人也會比較輕鬆。也當作是這個部落格的第一篇,假如有錯誤的的話,還望大家不吝指正。

因為這篇有實作,交代一下環境
OS: Window 7 Professional SP1 64bit
JAVA: 1.6.0_34
IDE: IntellijIDEA 13

什麼是JMX

JMX 是 Java Management Extensions 的簡寫,它的主要目的為了管理在 JVM 上運行的應用程式
試想今天你需要管理在不同機器執行的5個 Services,監控它的執行情況,還要監控機器上的記憶體,CPU,執行緒數量...等,你該怎麼做?
你可能會一台台登入下指令去看 Linux 的 memory,cpu 情況。那應用程式執行情況呢?寫程式將資訊透過網路傳出來?那假如程式是別人寫的呢?那你就需要知道它的溝通方式, 不同 application 就像說著不同語言的人,你需要為每一個 application 寫一個translator,轉成共同的格式,然後透過網路傳給你的 Client 端
不然你的 Client 就需要自己做這個翻譯的工作,它需要懂 N 種不同的溝通方式,哪天需要多監控一個新的application 的時候,Client 就必須修改...
想到就累不是嗎 ? JMX 就是為了解決這個難題而被發明的

JMX 基於一個管理系統架構來設計
fig001  

你可能會問為什麼要分為兩層的架構,SubAgent 主要是與 Resource 溝通的角色,Agent 則是統整訊息,
並負責網路傳遞的角色
這樣分工明確,也不用在 SubAgent 內負責網路的連線與傳遞,且 SubAgent 一般都與 Resource 處於同
一台機器,Agent 則可以位於另一台機器

對應到 JMX 是
javax.management.MBeanServer 實現了 Agent 的功能
javax.management.MBean 實現了 SubAgent 的功能

在 package 上的分類為
java.lang.management 包含了基本的 VM 監控功能
javax.management 讓用戶可以對 application 實現監控功能

簡單 MBean範例

簡單的說就是針對特定的 Application 客製化 MBean,然後使用 MBeanSever 去呼叫它
MBean 必須要用特定的規則去撰寫,並定義接口(interface),然後 MBeanServer 才可以呼叫這些接口,從 Applicaiton 取得資料或呼叫 Application 的方法
假設有一個應用程式想要管理快取,OK ! 那首先我們先定義一個擁有快取功能的 Application,就叫ApplicationCache

public class ApplicationCache { 
    private int maxCacheSize = 100; 
    private java.util.List cache = new java.util.ArrayList(); 

    public synchronized void clearCache() { 
        cache.clear(); 
    } 

    public synchronized int getCachedObjects() { 
        return cache.size(); 
    } 
    public synchronized int getMaxCacheSize() { 
        return maxCacheSize; 
    } 
    public synchronized void setMaxCacheSize(int value) { 
        if(value < 1) { 
            throw new IllegalArgumentException("Value must be >= 1");
        } 
        maxCacheSize = value; 
    } 

    public synchronized void cacheObject(Object o) { 
        while( cache.size() >= maxCacheSize ) { 
            cache.remove(0); 
        } 
        cache.add(o); 
    } 
}

很簡單吧 ! 它可以取得與設定最大快取大小,取得目前快取物件的數量,清除快取,將物件設定到快取內
接下來我們要將這個 ApplicationCache 想要透漏給 MBeanServer 使用的 methods 定義成一個 interface

public interface ApplicationCacheMBean { 
    int    getMaxCacheSize(); 
    void  setMaxCacheSize(int value); 
    int    getCachedObjects(); 
    void  clearCache(); 
}

這個類別要遵守幾個規則

  1. 1.要是 interface
  2. 2.類別名稱要用 MBean 做結尾
  3. 3.要符合 java bean 的規則,比如說有一個 getter 叫做 getCachedObjects( ),那假如也有 setter 的話,就要叫做 setCachedObjects()

接著修改 ApplicationCache 的 signature, 讓它實作此介面

public class ApplicationCache implements ApplicationCacheMBean{
  /* 略  */ 
} 

之所以這樣寫的原因是,這是假定此 Application 已經存在了,需要將它的某些功能"公開"出去給 MBeanServer 來呼叫使用
假如是重頭開發 Application 的話,那大可以先寫好 interface,再寫實作的 class

OK !! MBean 已經寫好了,接下來就是去 MBean Server 裡面去註冊它,這樣當我們連到 MBeanServer 的時候才看得到這些定義的介面
這包含幾個步驟

  1. 1.取得 MBeanServer
  2. 2.定義一個 unique ObjectName
  3. 3.使用這個unique ObjectName 將 Application 註冊到 MBeanServer

想像 Application 是一個 Resource,你今天要透過 MBeanServer去呼叫它的方法,當然先給它一個名字
回想前面講過的,Agent 對應到數個SubAgent,你當然要先給每一個 SubAgent 取一個唯一的名字,這樣之後你才可以跟Agent說『ㄟ! 叫那個 xyzSubAgent出來』,不然Agent 哪知道你要取得哪一個subAgent的資訊

public class JMXServer { 
    private static void imitateActivity(ApplicationCache cache) { 
       //  
       // add new Object to cache every sec 
       //  
        while(true) { 
            try { 
                cache.cacheObject(new Object()); 
                Thread.sleep(1000); 
            } 
            catch(InterruptedException e) { /*do nothing*/ } 
        } 
    } 
   
    public static void runMBeanServer() throws Exception { 
        ApplicationCache cache = new ApplicationCache();  
        MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();   // step 1 
        ObjectName name = new ObjectName("com.example.jmx:type=ApplicationCacheMBean");  // step 2
        mbs.registerMBean(cache, name); // step 3 
        imitateActivity(cache); 
    } 

    public static void main(String[] args) throws Exception { 
            JMXServer.runMBeanServer(); 
    } 
}

要注意的是,使用的是 ApplicationCache,而不是 ApplicationCacheMBean,
因為 ApplicationCacheMBean 只是一個 interface 而已,它無法實體化,將來你無法透過 MBeanServer取得實體,呼叫它的方法
然後為了避免大家取名字的時候取到相同的名字,在 ObjectName取名字一般都是使用 {packageName}:type:{MBeanClassName} 這個
規則來取名字,來避免衝突

啟動的時候要在 java 指令加入參數給 JVM,這樣JVM才會知道『喔 ! 這個Applicaton 要啟動 JMX』
參數一般使用
-Dcom.sun.management.jmxremote

假如要從遠端連到這個 MBeanServer 的話,要給另外的參數,指定 port,要不要ssl,要不要認證...等
-Dcom.sun.management.jmxremote.port=8888 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote


啟動之後,開啟 jconsole,jconsole是一個執行檔案,位於 java 安裝目錄的 bin 資料夾下
剛剛啟動的 Application 會出現在 Local Process 內, 點選後按 [Connect]
假如要連去遠端的 MBean Server 的話( MBeanServer啟動時要加入port, ssl 等參數),要點選Remote Process, 並輸入
service:jmx:rmi:///jndi/rmi://{ip}:{port}/jmxrmi
比如 service:jmx:rmi:///jndi/rmi://127.0.0.1:8888/jmxrmi

為了簡單起見,以 Local Process 作例子
Image
  JConsole  

你可以取得 MBean 的 attributes 與 operations
遵照 java bean 設定的 getter 與 setter 會變成 attributes,比如說有getMaxCacheSize() 與 setMaxCacheSize(int value)
那就會有一個 MaxCacheSize 的 attribute,可以讀取也可以修改其數值
CacheObjects 因為只有 getter,所以你只能讀取它的數值,無法修改
另外我們有定義了 clearCache() 這個方法,就會出現在 operations 內,可以點選按鈕來呼叫


以上是使用 jconsole 當作 client 連到 MBeanServer 的範例
那要怎麼自己撰寫 Client 程式連到 MBeanServer 呢?這也很簡單

public class JMXClient {
    private static void testMBServer(){
       try{
           JMXServiceURL url = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://localhost:8888/jmxrmi");
           JMXConnector jmxc = JMXConnectorFactory.connect(url,null);
           MBeanServerConnection mbsc = jmxc.getMBeanServerConnection();

           final ObjectName objectName = new ObjectName("com.example.jmx:type=ApplicationCacheMBean");
           final ApplicationCacheMBean appProxy = 
                         JMX.newMBeanProxy( mbsc,objectName,ApplicationCacheMBean.class );

           // 列出所有Domain
           System.out.println("Domains:---------------");
           String domains[] = mbsc.getDomains();
           for (int i = 0; i < domains.length; i++) {
               System.out.println("\tDomain[" + i + "] = " + domains[i]);
           }

           // MBean的總數
           System.out.println("MBean count = " + mbsc.getMBeanCount());

           //
           // 透過 Attribute 的操作來存取 MBean
           //
           System.out.println("====== set by attribute operations, set MaxCacheSize to 5 ");
           mbsc.setAttribute(objectName, new Attribute("MaxCacheSize", 5));
           System.out.println("MaxCacheSize = " + mbsc.getAttribute(objectName, "MaxCacheSize"));

           //
           // 也可以直接透過 Proxy 物件來操作
           //
           System.out.println("====== set by Proxy, set MaxCacheSize to 100 ");
           appProxy.setMaxCacheSize(100);
           System.out.println("MaxCacheSize = " + appProxy.getMaxCacheSize() );

           jmxc.close();
           System.out.println("going to exit.....");
       }
       catch(Exception e){
           e.printStackTrace();
       }     
    }
    public static void main(String[] argc)
    {
       JMXClient.testMBeanServer(); 
    }
}


要特別注意的是這邊使用遠端的連線的方式連線到 MBeanServer
所以前面的 JMXServer 啟動時要使用參數
-Dcom.sun.management.jmxremote.port=8888 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote

JMXClient 執行結果如下
cmd

再談 ObjectName

前面有提到,在 ObjectName取名字一般都是使用 {packageName}:type:{MBeanClassName} 這個規則來取名字,來避免衝突
其實 ObjectName 真的規則是 [domain]:[key]=[value],[key2]=[value2],[key3]=[value3]...
domain不允許冒號在裡面,domain 冒號右邊的 key 一定是 "type",剩下的隨你命名,只要符合 key=value 的格式就好
比如說你寫 "my.mbeans:type=Cache,realm=UI,vv=ImageHandles,ooo=box"
那開啟 jconsole 的時候會看到如下
圖片 3

arrow
arrow
    文章標籤
    程式語言 java
    全站熱搜
    創作者介紹
    創作者 BSTBST 的頭像
    BSTBST

    心之所在

    BSTBST 發表在 痞客邦 留言(0) 人氣()