文章出處

What and Why JMX

 

JMX的全稱為Java Management Extensions. 顧名思義,是管理Java的一種擴展。這種機制可以方便的管理正在運行中的Java程序。常用于管理線程,內存,日志Level,服務重啟,系統環境等。

試想,一個正在運行中的程序,我們如果想改變程序中的一些屬性,可以通過什么方法呢?可能有這么幾個方法:

  • 對于服務器式的程序,可以制作管理頁面,通過HTTP post與servlet來更改服務器端程序的屬性。

  • 對于服務器式的程序,還可以通過SOAP方式。但這需要程序開啟了SOAP端的服務。

  • 可以使用RMI遠程調用。但這需要設計開啟RMI服務。

  • 如果是SWT或Swing的程序,則可以通過設計UI管理界面,使用戶可以和程序內部交互。

  • 還有一種方式,是將可改變的屬性放入配置文件XML,properties或數據庫,程序輪詢配置文件,以求獲取最新的配置。

上面幾個方法都是常見,但卻無法通用的。所謂通用,是指解決方案符合一個標準,使得任何符合此標準的工具都能解析針對此標準的方案實現。這樣A公司設計的方案,B公司可以根據標準來解析。JMX就是Java管理標準。

JMX的構成

JMX由三部分組成:

  1. 程序端的Instrumentation, 我把它翻譯成可操作的儀器。這部分就是指的MBean. MBean類似于JavaBean。最常用的MBean則是Standard MBean和MXBean.

  2. 程序端的JMX agent. 這部分指的是MBean Server. MBean Server則是啟動與JVM內的基于各種協議的適配器。用于接收客戶端的調遣,然后調用相應的MBeans.

  3. 客戶端的Remote Management. 這部分則是面向用戶的程序。此程序則是MBeans在用戶前投影,用戶操作這些投影,可以反映到程序端的MBean中去。這內部的原理則是client通過某種協議調用agent操控MBeans. 

JMX agent與Remote Management之間是通過協議鏈接的,這協議可能包含:

  • HTTP

  • SNMP

  • RMI

  • IIOP

JMX agent中有針對上面協議的各種適配器。可以解析通過相應協議傳輸過來的數據。Remote Management client則可以用現成的工具,如JConsole, 也可以自己書寫java code。

接下來,我們看是一步一步,通過代碼示例來熟悉JMX各種特性。

受監管的程序

 

JMX是用于管理java程序的,為了試驗,我們首先需要寫一個小程序Echo。然后加入JMX對此程序進行監管。這個程序就是每隔10秒鐘,輸出一個預先定義好的Message。

首先定義Message類。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public class Message {
    private String title, body, by;
     
    public Message() {
        title="none";
        body="none";
        by="none";
    }
     
    public String getTitle() {
        return title;
    }
     
    public void setTitle(String title) {
        this.title = title;
    }
     
    public String getBody() {
        return body;
    }
     
    public void setBody(String body) {
        this.body = body;
    }
     
    public String getBy() {
        return by;
    }
     
    public void setBy(String by) {
        this.by = by;
    }
     
    public void echo() {
        System.out.println("<"+title+">");
        System.out.println(body);
        System.out.println("by " + by);
    }
}

定義Echo類

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Echo {
    public static Message msg = new Message();
    public static boolean running=true;
    public static boolean pause=false;
     
    public static void main(String[] args) {
        // 開啟JMX Agent。如果不需要JMX,只是單獨運行程序,請屏蔽掉下面這行代碼。
        new MessageEngineAgent().start();
         
        while(running) {
            try {
                Thread.sleep(10000);
            catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (!pause) msg.echo();
        }
    }
}

執行Echo,得到每過10秒鐘,則會輸出一個消息:

<none>

none

by none

MBean

接下來,開始設計管理程序的MBean. 在設計MBean之前,必須要先了解MBean都包括哪幾種。MBean包含:

  1. Standard MBean

  2. Dynamic MBean

  3. Open MBean

  4. Model MBean

  5. MXBean

最常用最簡單的兩個就是Standard MBean與MXBean. 首先搞清楚,MBean和MXBean的區別是什么。

Standard MBean與MXBean的區別

這里有一些細節,列出了兩只的區別http://docs.oracle.com/javase/7/docs/api/javax/management/MXBean.html 。它 們最根本的區別是,MXBean在Agent與Client之間會將自定義的Java類型轉化為Java Open Type. 這樣的好處是Client無需獲取MXBean的接口程序,便可訪問和操作MXBean的投影。如果使用MBean, client則必須先將MBean的接口程序放到classpath中,否則無法解析MBean中自定義類型。

基于上述原因,我將使用MXBean做為例子。實際上,JVM自帶的幾乎全是MXBean。

實現

定義MXBean的接口,注意命名規則,必須以MXBean結尾。

1
2
3
4
5
6
7
8
9
10
11
public interface MessageEngineMXBean {
    //結束程序。
    public void stop();
    //查看程序是否暫停。
    public boolean isPaused();
    //暫停程序或者繼續程序。
    public void pause(boolean pause);
    public Message getMessage();
    //修改message
    public void changeMessage(Message m);
}

實現部分。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class MessageEngine implements MessageEngineMXBean {
    private final Message message = Echo.msg;
     
    @Override
    public void stop() {
        Echo.running = false;
    }
 
    @Override
    public boolean isPaused() {
        return Echo.pause;
    }
 
    @Override
    public void pause(boolean pause) {
        Echo.pause = pause;
    }
 
    @Override
    public Message getMessage() {
        return this.message;
    }
 
    @Override
    public void changeMessage(Message m) {
        this.message.setBody(m.getBody());
        this.message.setTitle(m.getTitle());
        this.message.setBy(m.getBy());
    }
}

Notification

在JMX中,還有一個重要的概念是Notification。構成Notification的幾個接口是:

  1. NotificationEmitter, 只要實現此接口,就可以發出Notification和訂閱Notification. 類NotificationBroadcasterSupport則實現了NotificationEmitter.

  2. NotificationListener, 實現此接口的可以訂閱JMX的Notification。

  3. Notification, 消息本身。

修改MessageEngine, 使它在pause的時候發送通知給訂閱者。我把修改的部分貼上。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public class MessageEngine extends NotificationBroadcasterSupport implements MessageEngineMXBean {
    private long sequenceNumber = 1;
    ... ...
    ... ...
    public MessageEngine() {
        addNotificationListener(new NotificationListener() {
            @Override
            public void handleNotification(Notification notification, Object handback) {
                System.out.println("*** Handling new notification ***");
                System.out.println("Message: " + notification.getMessage());
                System.out.println("Seq: " + notification.getSequenceNumber());
                System.out.println("*********************************");
            }
        }, nullnull);
    }
    ... ...
    ... ...
    @Override
    public void pause(boolean pause) {
        Notification n = new AttributeChangeNotification(this,
                sequenceNumber++, System.currentTimeMillis(),
                "Pause changed""Paused""boolean",
                Echo.pause, pause);
        Echo.pause = pause;
        this.sendNotification(n);
    }
    ... ...
    ... ...
    // 規定可以發送的Notification Type,不在Type list中的Notification不會被發送。
    @Override
    public MBeanNotificationInfo[] getNotificationInfo() {
        String[] types = new String[]{
            AttributeChangeNotification.ATTRIBUTE_CHANGE
        };
         
        String name = AttributeChangeNotification.class.getName();
        String description = "An attribute of this MBean has changed";
        MBeanNotificationInfo info = 
                new MBeanNotificationInfo(types, name, description);
        return new MBeanNotificationInfo[]{info};
    }
}

Client端如何使用Notification,可以查看后面的Client一節。

JMX Agent

如果說Agent只是被Local使用,比如本地的JConsole,只需要開啟MBeanServer,并注冊MBean即可。不需要配置協議適配器。但如果需要遠程管理,比如遠程的JConsole或者自定義的管理器,則還需要配置兩者相互打交道的協議適配器。

1
2
3
4
5
6
7
8
9
10
11
12
public class MessageEngineAgent {
    public void start() {
        MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); 
        try {
            ObjectName mxbeanName = new ObjectName("com.example:type=MessageEngine");
            MessageEngineMXBean mxbean = new MessageEngine();
            mbs.registerMBean(mxbean, mxbeanName);
        catch (Exception e) {
            e.printStackTrace();
        }
    }
}

因為java默認自帶的了JMX RMI的連接器。所以,只需要在啟動java程序的時候帶上運行參數,就可以開啟Agent的RMI協議的連接器。

1
2
3
4
java -Dcom.sun.management.jmxremote.port = 9999  \
     -Dcom.sun.management.jmxremote.authenticate = false \
     -Dcom.sun.management.jmxremote.ssl = false \
     jmx.Echo

認證與授權

JMX的認證與授權是非常必要的,我們不可能允許任何client都能連接我們的Server。JMX的認證和授權可以復雜的使用LDAP, SSL。也可以使用最簡單的文件存儲用戶信息方式。本文作為啟蒙,只給出最簡單的認證方式。

在java啟動的時候,添加運行參數:

1
2
3
4
5
6
java -Dcom.sun.management.jmxremote.port = 9999  \
     -Dcom.sun.management.jmxremote.authenticate = true \
     -Dcom.sun.management.jmxremote.password.file = pathTo/my.password \
     -Dcom.sun.management.jmxremote.access.file = pathTo/my.access \
     -Dcom.sun.management.jmxremote.ssl = false \
     jmx.Echo

my.password里面定義了用戶名和密碼:

1
2
user1 password1
user2 password2

my.access里面定義了用戶授權信息:

1
2
3
4
user1 readOnly
user2 readWrite \
      create jmx.*,javax.management.timer.* \
      unregister

更詳細的內容可以從這里找到: http://docs.oracle.com/javase/7/docs/technotes/guides/management/agent.html 。

現在可以啟動程序了。啟動以后,我們使用下面的Client來連接我們寫的JMX Agent.

JMX Client

JConsole

JDK提供了一個工具在jdk/bin目錄下面,這就是JConsole。使用JConsole可以遠程或本地連接JMX agent。如下圖所以:

無論是遠程還是本地,連接進去所看到的都一樣。進去MBeans面板以后,找到MessageEngine。MessageEngine下面有 Attributes, Operations和Notification。可以瀏覽MessageEngine中的Attributes并更改那些可寫的屬性。也可以執行 Operations下面的stop, pause方法。此外,必須訂閱Notifications才能收到消息。

JConsole有缺點,它只能對MXBean中的主要基本類型做修改,但不能修改復雜類型。

Custom Client

我們也可以用java寫client調用Agent。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
public class Client implements NotificationListener {
 
    public static void main(String[] args) {
        try {
            new Client().start();
        catch (Exception e) {
            e.printStackTrace();
        }
    }
     
    public void start() throws Exception {
        // 如果agent不做配置的話,默認jndi path為jmxrmi
        JMXServiceURL url = new JMXServiceURL("service:jmx:rmi://localhost/jndi/rmi://localhost:9999/jmxrmi");
        JMXConnector jmxc = JMXConnectorFactory.connect(url, null);
        MBeanServerConnection server = jmxc.getMBeanServerConnection();
        ObjectName mbeanName = new ObjectName("com.example:type=MessageEngine");
        // 訂閱Notification
        server.addNotificationListener(mbeanName, thisnullnull);
         
        // 訪問paused屬性。
        boolean paused = (Boolean)server.getAttribute(mbeanName, "Paused");
        System.out.println(paused);
        if (!paused) {
            server.invoke(mbeanName, "pause"new Object[]{true}, new String[]{"boolean"});
        }
        // 構建一個jmx.Message類型實例。
        CompositeType msgType = new CompositeType("jmx.Message""Message Class Name",
                  new String[]{"title","body""by"},
                  new String[]{"title","body""by"}, 
                  new OpenType[]{SimpleType.STRING,SimpleType.STRING,SimpleType.STRING});
        CompositeData msgData = new CompositeDataSupport(msgType,
                new String[]{"title","body","by"},
                new Object[]{"Hello""This is a new message.""xpbug"}); 
        // 調用changeMessage方法
        server.invoke(mbeanName, "changeMessage"new Object[]{msgData}, new String[]{CompositeData.class.getName()});
        server.invoke(mbeanName, "pause"new Object[]{false}, new String[]{"boolean"});
         
        // 訪問修改后的Message屬性。
        msgData = (CompositeData)server.getAttribute(mbeanName, "Message");
        System.out.println("The message changes to:");
        System.out.println(msgData.get("title"));
        System.out.println(msgData.get("body"));
        System.out.println(msgData.get("by"));
         
        Thread.sleep(1000*10);
    }
 
    @Override
    public void handleNotification(Notification notification, Object handback) {
        System.out.println("*** Handling new notification ***");
        System.out.println("Message: " + notification.getMessage());
        System.out.println("Seq: " + notification.getSequenceNumber());
        System.out.println("*********************************");        
    }
}

運行一下client,看看都發生了什么。

源碼下載

http://pan.baidu.com/s/1sjLKewX 

來自:http://my.oschina.net/xpbug/blog/221547

相關:Jetty源碼學習6-JMX


文章列表


不含病毒。www.avast.com
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()