JMX整理
What and Why JMX
JMX的全稱為Java Management Extensions. 顧名思義,是管理Java的一種擴展。這種機制可以方便的管理正在運行中的Java程序。常用于管理線程,內存,日志Level,服務重啟,系統環境等。
試想,一個正在運行中的程序,我們如果想改變程序中的一些屬性,可以通過什么方法呢?可能有這么幾個方法:
-
對于服務器式的程序,可以制作管理頁面,通過HTTP post與servlet來更改服務器端程序的屬性。
</li> -
對于服務器式的程序,還可以通過SOAP方式。但這需要程序開啟了SOAP端的服務。
</li> -
可以使用RMI遠程調用。但這需要設計開啟RMI服務。
</li> -
如果是SWT或Swing的程序,則可以通過設計UI管理界面,使用戶可以和程序內部交互。
</li> -
還有一種方式,是將可改變的屬性放入配置文件XML,properties或數據庫,程序輪詢配置文件,以求獲取最新的配置。
</li> </ul>上面幾個方法都是常見,但卻無法通用的。所謂通用,是指解決方案符合一個標準,使得任何符合此標準的工具都能解析針對此標準的方案實現。這樣A公司設計的方案,B公司可以根據標準來解析。JMX就是Java管理標準。
JMX的構成
JMX由三部分組成:
-
程序端的Instrumentation, 我把它翻譯成可操作的儀器。這部分就是指的MBean. MBean類似于JavaBean。最常用的MBean則是Standard MBean和MXBean.
</li> -
程序端的JMX agent. 這部分指的是MBean Server. MBean Server則是啟動與JVM內的基于各種協議的適配器。用于接收客戶端的調遣,然后調用相應的MBeans.
</li> -
客戶端的Remote Management. 這部分則是面向用戶的程序。此程序則是MBeans在用戶前投影,用戶操作這些投影,可以反映到程序端的MBean中去。這內部的原理則是client通過某種協議調用agent操控MBeans.
</li> </ol>JMX agent與Remote Management之間是通過協議鏈接的,這協議可能包含:
-
HTTP
</li> -
SNMP
</li> -
RMI
</li> -
IIOP
</li> </ul>JMX agent中有針對上面協議的各種適配器。可以解析通過相應協議傳輸過來的數據。Remote Management client則可以用現成的工具,如JConsole, 也可以自己書寫java code。
接下來,我們看是一步一步,通過代碼示例來熟悉JMX各種特性。
受監管的程序
JMX是用于管理java程序的,為了試驗,我們首先需要寫一個小程序Echo。然后加入JMX對此程序進行監管。這個程序就是每隔10秒鐘,輸出一個預先定義好的Message。
首先定義Message類。
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類
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
</blockquote>MBean
接下來,開始設計管理程序的MBean. 在設計MBean之前,必須要先了解MBean都包括哪幾種。MBean包含:
-
Standard MBean
</li> -
Dynamic MBean
</li> -
Open MBean
</li> -
Model MBean
</li> -
MXBean
</li> </ol>最常用最簡單的兩個就是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結尾。
public interface MessageEngineMXBean { //結束程序。 public void stop(); //查看程序是否暫停。 public boolean isPaused(); //暫停程序或者繼續程序。 public void pause(boolean pause); public Message getMessage(); //修改message public void changeMessage(Message m); }
實現部分。
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()); } }</pre>
Notification
在JMX中,還有一個重要的概念是Notification。構成Notification的幾個接口是:
-
NotificationEmitter, 只要實現此接口,就可以發出Notification和訂閱Notification. 類NotificationBroadcasterSupport則實現了NotificationEmitter.
</li> -
NotificationListener, 實現此接口的可以訂閱JMX的Notification。
</li> -
Notification, 消息本身。
</li> </ol>修改MessageEngine, 使它在pause的時候發送通知給訂閱者。我把修改的部分貼上。
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("*********************************"); } }, null, null); } ... ... ... ... @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或者自定義的管理器,則還需要配置兩者相互打交道的協議適配器。
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協議的連接器。
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啟動的時候,添加運行參數:
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里面定義了用戶名和密碼:
user1 password1 user2 password2
my.access里面定義了用戶授權信息:
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。
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, this, null, null); // 訪問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("*"); } }</pre>
運行一下client,看看都發生了什么。
源碼下載
http://pan.baidu.com/s/1sjLKewX
來自:http://my.oschina.net/xpbug/blog/221547
-
-
-
-