第4章 遠端管理
第4章 遠端管理
4.1 引言
JMX API使你能通過JMX 聯結器遠端管理你的資源。JMX聯結器使得遠端的JMX客戶端可以訪問MBean伺服器。聯結器的客戶端與伺服器本質上輸出同樣的介面。
JMX聯結器由連線客戶端和連線伺服器組成。連線伺服器被附加到MBean伺服器上,監聽客戶端的請求。連線客戶端負責建立與伺服器之間的連線。連線客戶端通常使用一個與連線伺服器不同的Java虛擬機器,並且執行在不同的機器上。JMX API定義了一個基於RMI的標準連線協議。該協議使你能夠讓JMX客戶端連線到遠端的MBean伺服器上的MBean,並執行MBean上的操作,就像在本地執行這些操作一樣。
Java SE平臺提供了一個拆箱即用的方式,即通過
4.2 將資源暴漏給遠端管理JConsole
如果你使用一個開箱即用的遠端管理代理和已經存在的監控和管理工具,例如JConsole,那麼使用JMX API將你的應用暴露給遠端管理是極其容易的。
為了將你的應用暴露給遠端管理,你需要用合適的屬性啟動你的應用。這個例子演示如何將Main JMX代理暴露給遠端管理。
安全性考慮:
這個例子為了簡化,禁用鑑權和加密安全機制。然而,當你在真實的環境裡實現遠端管理的時候,你應該實現這些安全機制。
下一步?指向如何啟用安全機制的JMX技術文件
這個例子需要的Java版本:Java SE 6平臺。為了遠端監控Main JMX代理,按照下面的步驟執行示例:
- 如果這一步還沒有做,那就儲存JMX例子的壓縮包jmx_examples.zip到你的工作目錄work_dir。
- 在命令列或者終端使用下面的命令解壓縮這個包:unzip jmx_examples.zip。
- 在work_dir目錄下編譯例子中所有類:javac com/example/*.java。
- 啟動你的應用,並指定引數將Main暴露給遠端管理。(對於Windows使用者,使用^而不是\將一個長命令列分成多行顯示):
java -Dcom.sun.management.jmxremote.port=9999 \
-Dcom.sun.management.jmxremote.authenticate=false \
-Dcom.sun.management.jmxremote.ssl=false \
com.example.Main
生成了一條Main正在等待一些事情發生的確認訊息。
- 在不同的機器上的命令列或者終端視窗,啟動JConsole:jconsole。新建連線對話方塊將會顯示出來,列表中顯示出可以本地連線的正在執行的JMX代理
- 選擇“遠端程序”,輸入如下地址:hostname:9999。
- 點選“連線”。Main執行所在的Java虛擬機器的當前活動的概覽顯示出來。
- 點選“MBean”標籤。這個面板將會顯示所有當前註冊到遠端MBean伺服器中的MBean。
- 在左邊的MBean樹中,展開com.example節點。你會看到在Main中建立並註冊的MBean Hello。如果你點選Hello,你會在MBean樹中看到它相關的“屬性”和“操作”,即使它執行一個不同的機器上。
- 要想關閉JConsole,選擇 連線->退出。
4.3 建立自定義JMX客戶端
前面幾章講述瞭如何建立JMX技術的MBean和MXBean,如何把它們註冊到JMX代理。然而,前面所有的例子都使用了現成的JMX客戶端JConsole。本章將演示如何建立自定義的JMX客戶端。
自定義客戶端的例子Client包含在jmx_examples.zip中。這個客戶端與前面幾章已經看到的相同的MBean、MXBean、JMX代理進行互動。鑑於Client類的大小,我們分成幾塊來介紹。
4.3.1 匯入JMX遠端API類
為了能建立執行在JMX客戶端並遠端連線到JMX代理的連線,你需要使用javax.management.remote包中的一些類。
package com.example;
...
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;
public class Client {...
Client類建立了JMXConnector類的例項,而這個類又需要JMXConnectorFactory工廠類和JMXServiceURL類。
4.3.2 建立通知監聽器(Notification Listener)
JMX客戶端需要有一個Notification訊息的處理器,以便監聽和處理由在JMX代理的MBean伺服器中註冊的MBean傳送的通知。JMX客戶端的通知處理器是No NotificationListener介面的例項,如下所示:
...
public static class ClientListener implements NotificationListener {
public void handleNotification(Notification notification,
Object handback) {
echo("\nReceived notification:");
echo("\tClassName: " + notification.getClass().getName());
echo("\tSource: " + notification.getSource());
echo("\tType: " + notification.getType());
echo("\tMessage: " + notification.getMessage());
if (notification instanceof AttributeChangeNotification) {
AttributeChangeNotification acn = (AttributeChangeNotification) notification;
echo("\tAttributeName: " + acn.getAttributeName());
echo("\tAttributeType: " + acn.getAttributeType());
echo("\tNewValue: " + acn.getNewValue());
echo("\tOldValue: " + acn.getOldValue());
}
}
}
通知監聽器確定它收到的Notification訊息的源,獲取儲存在Notification中的資訊。然後根據收到的通知訊息的型別執行不同的動作。當監聽器收到AttributeChangeNotification型別的通知時,監聽器就會通過呼叫AttributeChangeNotification類的方法getAttributeName、getAttributeType、getNewValue和getOldValue獲取發生改變的MBean屬性的名字、型別、新的屬性值和舊的屬性值。
在後面的程式碼中建立了ClientListener一個例項
ClientListener listener = new ClientListener();
4.3.3 建立RMI聯結器客戶端
Client類建立了一個RMI聯結器客戶端,配置RMI聯結器客戶端以便連線到RMI聯結器伺服器。RMI聯結器伺服器是在你啟動JMX代理Main的時候啟動的。這將允許執行在同一臺機器上的JMX客戶端與JMX代理進行互動。
public static void main(String[] args) throws Exception {
echo("\nCreate an RMI connector client and " + "connect it to the RMI connector server");
JMXServiceURL url = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://:9999/jmxrmi");
JMXConnector jmxc = JMXConnectorFactory.connect(url, null);
正如你所看到的,Client類定義了JMXServiceURL類的物件url,這個url表示聯結器客戶端期望能找到連線伺服器的位置。URL允許聯結器客戶端從執行在本機埠9999的RMI登錄檔中檢索RMI聯結器伺服器的存根jmxrmi,並連線到RMI聯結器伺服器。
識別了RMI登錄檔,聯結器客戶端就可以建立了。聯結器客戶端jmxc是通過工廠類JMXConnectorFactory的connect()方法建立的的一個JMXConnector介面的例項。呼叫connect()方法時傳入了兩個引數url和null。
4.3.4 連線到遠端的MBean伺服器
RMI連線就位後,JMX客戶端必須連線到遠端的MBean伺服器,這樣它就可以通過J遠端的JMX代理與不同的MBean進行互動了。
MBeanServerConnection mbsc = jmxc.getMBeanServerConnection();
然後通過呼叫JMXConnector例項jmxc的方法getMBeanServerConnection()就可以建立了MBeanServerConnection的例項mbsc。
現在聯結器客戶端已經連線到了由JMX代理建立的MBean伺服器了,可以註冊MBean,執行MBean的操作,因為連線對於兩端來說都是透明的。
首先,客戶端定義了一些簡單的操作來發現MBean的資訊:
...
echo("\nDomains:");
String domains[] = mbsc.getDomains();
Arrays.sort(domains);
for (String domain : domains) {
echo("\tDomain = " + domain);
}
...
echo("\nMBeanServer default domain = " + mbsc.getDefaultDomain());
echo("\nMBean count = " + mbsc.getMBeanCount());
echo("\nQuery MBeanServer MBeans:");
Set<ObjectName> names = new TreeSet<ObjectName>(mbsc.queryNames(null, null));
for (ObjectName name : names) {
echo("\tObjectName = " + name);
}
...
客戶端呼叫MBeanServerConnection的不同方法來獲得不同的MBean執行所在的域名,在MBean伺服器中註冊的MBean數量,以及每一個MBean的物件名(ObjectName)。
4.3.5 通過代理執行遠端的MBean的操作
客戶端通過MBean伺服器連線建立MBean代理來訪問MBean伺服器上的Hello MBean。對於客戶端來說,這個MBean代理是本地的,但模仿了遠端的MBean。
ObjectName mbeanName = new ObjectName("com.example:type=Hello");
HelloMBean mbeanProxy = JMX.newMBeanProxy(mbsc, mbeanName, HelloMBean.class, true);
echo("\nAdd notification listener...");
mbsc.addNotificationListener(mbeanName, listener, null, null);
echo("\nCacheSize = " + mbeanProxy.getCacheSize());
mbeanProxy.setCacheSize(150);
echo("\nWaiting for notification...");
sleep(2000);
echo("\nCacheSize = " + mbeanProxy.getCacheSize());
echo("\nInvoke sayHello() in Hello MBean...");
mbeanProxy.sayHello();echo("\nInvoke add(2, 3) in Hello MBean...");
echo("\nadd(2, 3) = " + mbeanProxy.add(2, 3));
waitForEnterPressed();
MBean代理允許你通過Java介面訪問MBean,允許你只調用代理的方法,而不必寫一段冗長的程式碼來訪問遠端的MBean。在這裡,通過呼叫javax.management.JMX類的getnewMBeanProxy()靜態方法建立Hello的MBean代理,該方法需要傳入MBeanServerConnection連線mbsc、物件名mbeanName、MBean介面的class型別、布林值true,這個布林值意味著代理的行為就像NotificationBroadcaster一樣。JMX客戶端可以執行Hello定義的操作,就像它們是本地註冊的MBean的操作一樣。JMX客戶端也添加了一些通知監聽器,改變MBean的CacheSize屬性,就會發送一個Notification通知。
4.3.6 通過代理執行遠端的MXBean的操作
用同樣的方式,你也可以建立MXBean的代理。
ObjectName mxbeanName = new ObjectName ("com.example:type=QueueSampler");
QueueSamplerMXBean mxbeanProxy = JMX.newMXBeanProxy(mbsc, mxbeanName, QueueSamplerMXBean.class);
QueueSample queue1 = mxbeanProxy.getQueueSample();
echo("\nQueueSample.Date = " + queue1.getDate());
echo("QueueSample.Head = " + queue1.getHead());
echo("QueueSample.Size = " + queue1.getSize());
echo("\nInvoke clearQueue() in QueueSampler MXBean...");
mxbeanProxy.clearQueue();
QueueSample queue2 = mxbeanProxy.getQueueSample();
echo("\nQueueSample.Date = " + queue2.getDate());
echo("QueueSample.Head = " + queue2.getHead());
echo("QueueSample.Size = " + queue2.getSize());
如上所示,建立MXBean的代理,只需要呼叫JMX.newMXBeanProxy()方法就可以了。MXBean代理mxbeanProxy允許客戶端觸發QueueSample MXBean的操作,就像它們是本地註冊的MXBean的操作一樣。
4.3.7 關閉連線
一旦JMX客戶端獲取了它所需要的所有資訊,並且執行了遠端伺服器的MBean所有的操作,連線必須被關閉
jmxc.close();
呼叫JMXConnector.close()方法關閉連線
4.3.8 執行自定義的客戶端示例
這個示例需要Java SE 6以上的版本。使用自定義的JMX客戶端Client監控JMX代理Main,按照下面的步驟執行:
- 如果這一步還沒有做,那就儲存JMX例子的壓縮包jmx_examples.zip到你的工作目錄work_dir。
- 在命令列或者終端使用下面的命令解壓縮這個包:unzip jmx_examples.zip。
- 在work_dir目錄下編譯例子中所有類:javac com/example/*.java。
- 啟動Main,並指定引數將Main暴露給遠端管理
java -Dcom.sun.management.jmxremote.port=9999 \
-Dcom.sun.management.jmxremote.authenticate=false \
-Dcom.sun.management.jmxremote.ssl=false \
com.example.Main
- 生成了一條Main正在等待一些事情發生的確認訊息。
- 在不同的終端或命令列視窗啟動Client客戶端:java com.example.Client顯示MBeanServerConnection已經被獲取的確認訊息。
- 敲回車(Enter)。註冊到MBean伺服器並由Main啟動的所有MBean所在的域名都顯示出來了。
- 再一次敲回車(Enter)。在MBean伺服器中註冊的MBean的數量以及這些MBean的物件名都顯示出來了。顯示的MBean包括所有執行在Java虛擬機器中的標準平臺MXBean,以及在通過Main註冊到MBean伺服器中的Hello MBean和QueueSampler MXBean。
- 再一次敲回車(Enter)。Client觸發Hello MBean的所有操作,結果如下:
- Notification監聽器被新增到Client中以監聽來自Main的Notification訊息。
- CacheSize的值由200改為150。
- 在啟動Main的終端或命令列視窗,顯示CacheSize屬性值被修改的確認訊息。
- 在啟動Client的終端或命令列視窗,來自Main的通知訊息顯示出來,通知Client客戶端CacheSize的值已經發生改變。
- Hello MBean的sayHello()方法被觸發。
- 在啟動Main的終端或命令列視窗,顯示出“Hello World”訊息。
- Hello MBean的add()方法被觸發,入參為2和3。在Client客戶端顯示出了返回結果。
- 再一次敲回車(Enter)。Client觸發QueueSampler MXBean的所有操作,結果如下:
- QueueSampler的值date、head和size顯示出來。
- clearQueue()操作被觸發。
- 再一次敲回車(Enter)。Client客戶端關閉與MBean伺服器的連線,一條確認訊息顯示出來。