1. 程式人生 > >轉:Java 理論與實踐: 用 JMX 檢測應用程式

轉:Java 理論與實踐: 用 JMX 檢測應用程式

源:http://www.ibm.com/developerworks/cn/java/j-jtp09196/#ibm-pcon
評:
只需新增 bean 就可實現立即可視
偵錯程式和分析器可以提供對應用程式的行為的深入觀察,但在出現嚴重問題之前,這些工具通常用不上。將監視掛鉤(hook)構建到應用程式內,會使理解程式的執行變得更容易而且不會破壞偵錯程式。既然 Java 管理擴充套件(JMX)已經構建進了 Java™ SE 平臺,而且 jconsole 檢視器提供了統一的監視 GUI,那麼用 JMX 為應用程式提供一個視窗,要比以前更加容易而且更為有效。


有多少次您曾經注視著執行中的應用程式,問自己:“它到底在做什麼?為什麼用了這麼長時間呢?” 在這些時刻,您可能會想如果自己在應用程式中構建了更多的監視功能就好了。例如,在伺服器應用程式中,能夠檢視排隊等候處理的任務的數量和型別、當前正在處理的任務、過去一分鐘或一小時內的吞吐量統計、平均任務處理時間等。這些統計值容易蒐集,但是在需要資料的時候,如果沒有非侵入性的資料檢索機制,那麼這些值就不太有用。
可以用許多方式匯出操作性資料——可以把週期性統計快照寫入日誌檔案、建立 Swing GUI、使用內嵌的 HTTP 伺服器在 Web 頁面上顯示統計值或者釋出可以用來查詢應用程式的 Web 服務。但是在缺少監視和資料釋出基礎設施的情況下,多數應用程式開發人員都做不到這些,因此造成對應用程式工作情況的瞭解要比預期的少很多。
JMX
在 Java 5.0 中,類庫和和 JVM 提供了一種全面的管理和監視基礎設施——JMX。JMX 是一種用來提供可以遠端訪問的管理介面的標準措施,也是一種嚮應用程式新增靈活且強大的管理介面的簡易方式。被稱作受管 bean(MBean)的 JMX 元件,是提供與實體的管理有關的訪問器和業務方法的 JavaBean。每個受管的實體(可能是整個應用程式或應用程式中的服務)例項化一個 MBean 並用可讀懂的名稱註冊它。支援 JMX 的應用程式依賴於 MBeanServer,它充當 MBean 的容器,提供遠端訪問、名稱空間管理和安全服務。在客戶端,jconsole 工具可以充當統一的 JMX 客戶機。結合兩者,對 JMX 的平臺支援極大地降低了使應用程式支援外部管理介面所需的工作和努力。
除了提供 MBeanServer 實現,Java SE 5.0 還提供 JVM 以更方便地瞭解記憶體管理、類裝入、活動執行緒、日誌和平臺配置的狀態。多數平臺服務的監視和管理在預設情況下都是開啟的(效能影響最小),所以只需要連線應用程式與 JMX 客戶機即可。圖 1 給出了 jconsole JMX 客戶機(JDK 的一部分) ,它顯示了其中一個記憶體管理檢視——一段時間內的堆使用情況。Perform GC 按鈕則證明了 JMX 可以提供 除了檢視操作統計值之外的初始化操作的功能。
圖 1. 用 jconsole 檢視堆使用情況
用 jconsole 檢視堆使用情況
傳輸和安全性
JMX 指定了在 MBeanServer 和 JMX 客戶之間通訊所使用的協議,協議可以在各種傳輸機制上執行。可以使用針對本地連線的內建傳輸,及通過 RMI、socket 或 SSL 的遠端傳輸(可以通過 JMX Connector API 建立新的傳輸)。認證是由傳輸執行的;本地傳輸允許用相同的使用者 ID 連線到執行在本地系統上的 JVM;遠端傳輸可以用口令或證書進行認證。本地傳輸在 Java 6 下預設就是啟用的。要在 Java 5.0 下啟用它,需要在 JVM 啟動時定義系統屬性 com.sun.management.jmxremote。“Monitoring and Management using JMX” 這份文件(請參閱參考資料)描述了啟用和配置傳輸的配置步驟。
回頁首
檢測 Web 伺服器
檢測應用程式來使用 JMX 很容易。像其他許多遠端呼叫框架(RMI、EJB 和 JAX-RPC)一樣,JMX 也是基於介面的。要建立管理服務,需要建立指定管理方法的 MBean 介面。然後可以建立一個 MBean 來實現此介面、例項化它及把它註冊到 MBeanServer。
清單 1 顯示了網路服務(例如 Web 伺服器)的 MBean 介面。它提供了檢索配置資訊(例如埠號)和操作性資訊(例如服務是否啟動)的 getter。它還包含檢視和修改可配置引數(例如當前日誌級別)的 getter 和 setter,還有呼叫管理操作(例如 start() 和 stop())的方法。
清單 1. 某個 Web 伺服器的 MBean 介面
public interface WebServerMBean {
public int getPort();

public String getLogLevel();
public void setLogLevel(String level);

public boolean isStarted();
public void stop();
public void start();
}
實現 MBean 類通常非常直接明瞭,因為 MBean 介面要反映現有實體或服務的屬性和管理操作。例如,MBean 中的 getLogLevel() 和 setLogLevel() 方法會直接轉給被 Web 伺服器使用的 Logger 上的 getLevel() 和 setLevel() 方法。JMX 做了一些命名限制。例如,MBean 介面名稱必須以 MBean 結尾,FooMBean 介面的 MBean 類必須叫作 Foo。(可以用更高階的 JMX 特性——動態 MBean 來去除這個限制。)把 MBean 註冊到預設的 MBeanServer 也很容易,如清單 2 所示:
清單 2. 用內建的 JMX 實現註冊 MBean
public class WebServer implements WebServerMBean { ... }

...

WebServer ws = new WebServer(...);
MBeanServer server = ManagementFactory.getPlatformMBeanServer();
server.registerMBean(ws, new ObjectName("myapp:type=webserver,name=Port 8080"));
傳遞給 registerMBean() 的 ObjectName 標識了受管實體。因為預見到指定應用程式可能包含許多受管實體,所以名稱包含域(清單 2 中的 “myapp”)和許多標識域中的受管資源的鍵-值對。“name” 和 “type” 這兩個鍵是常用的,在使用的時候,name 應當在域中所有的同類 MBean 中能夠唯一地標識受管實體。也可以指定其他鍵-值對,而且 JMX API 還包含進行物件名稱通配匹配的工具。
建立並註冊了 MBean 之後,立即就可以把 jconsole 指向應用程式(在命令列輸入 jconsole)並在 “MBeans” 檢視中檢視它的管理屬性和操作。圖 2 顯示了 jconsole 中針對新 MBean 的 Attributes 標籤,圖 3 顯示了 Operations 標籤。使用反射,JMX 可以指出哪個屬性是隻讀的(Started、Port),哪個屬性是可讀寫的(LogLevel),而且 jconsole 允許修改讀寫屬性。如果讀寫屬性的 setter 丟擲異常(例如 IllegalArgumentException),JMX 就把異常報告給客戶機。
圖 2. jconsole 中 MBean 的 Attributes 標籤
jconsole 中 MBean 的 Attributes 標籤
圖 3. jconsole 中 MBean 的 Operations 標籤
jconsole 中 MBean 的 Operations 標籤
資料型別
MBean 中的訪問器和操作能夠用任何其簽名形式的原語型別,以及 String、Date 和其他標準庫類。也可以使用這些允許的型別的陣列和集合。MBean 方法也可以使用其他可以序列化的資料型別,但是這樣做會造成互操作性問題,因為類檔案也必須對 JMX 客戶機可用。(如果使用 RMI 傳輸,可以使用 RMI 的自動類下載特性完成這項任務。)如果想在管理介面中使用結構化資料型別,還想避免與類可用性相關的互操作性問題,可以使用 JMX 的開放 MBean 特性來表達複合或表格資料。
回頁首
檢測伺服器應用程式
在建立管理介面時,某些引數和操作的特點很自然地就表明這些引數和資料應當被包含在內,例如配置引數、操作統計值、除錯操作(例如修改日誌級別或把應用程式狀態匯出到檔案)、生命週期操作(啟動、停止)。檢測一個應用程式,讓它支援對這些屬性和操作的訪問,通常相當容易。但是,要從 JMX 獲得最大價值,就要在設計時考慮什麼資料在執行時對使用者和操作員有用。
如果用 JMX 瞭解伺服器應用程式的工作情況,需要一種標識和跟蹤工作單元的機制。如果使用標準的 Runnable 和 Callable 介面描述任務,通過讓任務類自描述(例如實現toString() 方法),可以在任務生命週期內跟蹤它們,並提供 MBean 方法來返回等候中、處理中和完成的任務列表。
清單 3 中的 TrackingThreadPool 演示的是 ThreadPoolExecutor 的一個子類,它及時給出正在處理中的是哪些任務,以及已經完成的任務的時間統計值。它通過覆蓋 beforeExecute() 和 afterExecute() 掛鉤,並提供能檢索所蒐集資料的 getter,實現這些任務。
清單 3. 蒐集處理中的任務和平均的任務時間統計值的執行緒池類
public class TrackingThreadPool extends ThreadPoolExecutor {
private final Map<Runnable, Boolean> inProgress
= new ConcurrentHashMap<Runnable,Boolean>();
private final ThreadLocal<Long> startTime = new ThreadLocal<Long>();
private long totalTime;
private int totalTasks;

public TrackingThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime,
TimeUnit unit, BlockingQueue<Runnable> workQueue) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
}

protected void beforeExecute(Thread t, Runnable r) {
super.beforeExecute(t, r);
inProgress.put(r, Boolean.TRUE);
startTime.set(new Long(System.currentTimeMillis()));
}

protected void afterExecute(Runnable r, Throwable t) {
long time = System.currentTimeMillis() - startTime.get().longValue();
synchronized (this) {
totalTime += time;
++totalTasks;
}
inProgress.remove(r);
super.afterExecute(r, t);
}

public Set<Runnable> getInProgressTasks() {
return Collections.unmodifiableSet(inProgress.keySet());
}

public synchronized int getTotalTasks() {
return totalTasks;
}

public synchronized double getAverageTaskTime() {
return (totalTasks == 0) ? 0 : totalTime / totalTasks;
}
}
清單 4 中的 ThreadPoolStatusMBean 顯示了 TrackingThreadPool 的 MBean 介面,它提供了活動任務、活動執行緒、完成任務、等候任務的計數,還提供了當前等候執行和正在執行的任務的列表。在管理介面中包含等候和執行任務的列表,讓您既可以看到應用程式的工作難度,又可以看到它目前的工作內容。這個特性不僅讓您可以洞察應用程式的行為,還能洞察它正在操作的資料集的性質。
清單 4. TrackingThreadPool 的 MBean 介面
public interface ThreadPoolStatusMBean {
public int getActiveThreads();
public int getActiveTasks();
public int getTotalTasks();
public int getQueuedTasks();
public double getAverageTaskTime();
public String[] getActiveTaskNames();
public String[] getQueuedTaskNames();
}
如果任務的重量級足夠,那麼甚至可以再進一步,在每個任務提交時都為它註冊一個 MBean (然後在任務完成時再取消註冊)。然後可以用管理介面查詢每個任務的當前狀態、運行了多長時間,或者請求取消任務。
清單 5 中的 ThreadPoolStatus 實現了 ThreadPoolStatusMBean 介面,它提供了每個訪問器的明顯實現。與 MBean 實現類中的典型情況一樣,每個操作實現起來都很細碎,所以把實現委託給了底層受管物件。在這個示例中,JMX 程式碼完全獨立於受管實體的程式碼。TrackingThreadPool 對於 JMX 一無所知;通過為相關的屬性提供管理方法和訪問器,它提供了自己的程式設計管理介面。 還可以選擇在實現類中直接實現管理功能(讓 TrackingThreadPool 實現 TrackingThreadPoolMBean 介面),或者單獨實現(如清單 4 和 5 所示)。
清單 5. TrackingThreadpool 的 MBean 實現
public class ThreadPoolStatus implements ThreadPoolStatusMBean {
private final TrackingThreadPool pool;

public ThreadPoolStatus(TrackingThreadPool pool) {
this.pool = pool;
}

public int getActiveThreads() {
return pool.getPoolSize();
}

public int getActiveTasks() {
return pool.getActiveCount();
}

public int getTotalTasks() {
return pool.getTotalTasks();
}

public int getQueuedTasks() {
return pool.getQueue().size();
}

public double getAverageTaskTime() {
return pool.getAverageTaskTime();
}

public String[] getActiveTaskNames() {
return toStringArray(pool.getInProgressTasks());
}

public String[] getQueuedTaskNames() {
return toStringArray(pool.getQueue());
}

private String[] toStringArray(Collection<Runnable> collection) {
ArrayList<String> list = new ArrayList<String>();
for (Runnable r : collection)
list.add(r.toString());
return list.toArray(new String[0]);
}
}
為了演示這些類如何提供對應用程式操作的內容的瞭解,請考慮這樣一個 Web 搜尋應用程式,它把工作分成兩類任務:獲取遠端頁面,對頁面進行索引。每個任務分別用清單 6 所示的 FetchTask 或 IndexTask 描述。可以建立 ThreadPoolStatus MBean,提供處理這些任務所使用的執行緒池的管理介面,並把它用 JMX 註冊。
清單 6. Web 搜尋應用程式中使用的 FetchTask 類
public class FetchTask implements Runnable {
private final String name;

public FetchTask(String name) {
this.name = name;
}

public String toString() {
return "FetchTask: " + name;
}

public void run() { /* Fetch remote resource */ }
}
當此程式處理每個頁面時,可能還會對新任務進行排隊以獲取這個頁面上鍊接的頁面,所以在指定時間內,可能會既有獲取任務又有尚未完成的索引任務。能夠正確地判斷正在處理哪個頁面,或者正在等候處理哪個頁面,不僅讓您可以理解應用程式的效能特徵,還可以理解應用程式所操作的資料的特徵。
圖 4 顯示了正在處理 whitehouse.gov 站點的 Web 搜尋程式的快照。從圖中可以看到已經獲取並索引了主頁,程式現在的工作是獲取和索引直接從該主頁連結出的頁面。單擊 Refresh 按鈕,可以對應用程式的工作流程進行取樣,它可以提供許多關於應用程式工作情況的資訊,卻不需引入大量日誌或者在偵錯程式中執行應用程式。
圖 4. Web 搜尋應用程式中的活動任務和排隊任務
Web 搜尋應用程式中的活動任務和排隊任務
回頁首
結束語
結合平臺內的 JMX 支援和 jconsole JMX 客戶機可以提供一種嚮應用程式新增管理和監視功能的輕鬆方式。即使是沒有具體管理需求的應用程式,為它們構建這些功能也會讓您對程式的執行及其所處理的資料的性質獲得深入瞭解,而且不需太多的工作和努力。如果應用程式匯出管理介面,此介面讓您可以檢視它操作的內容,那麼您就會更加了解它的執行狀態——對它是否按預期的方式工作也會更有信心——而不必求助於額外的工具(例如新增日誌程式碼或使用偵錯程式或分析器)。