首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 理論與實踐:善用 JMX 監控與管理應用程式

     編輯:Java 相關知識

有多少次您凝視著運行中的應用程式,自問:「它到底在做什麼?為什麼花這麼久?」此時您可能希望在應用程式中內建更多監控功能。例如在伺服器應用中,能檢視排隊待處理任務的數量與類型、正在處理的任務、過去一分鐘或一小時的吞吐量統計、平均任務處理時間等。這些統計值易於收集,但若缺乏非侵入式的資料檢索機制,在需要時卻難以派上用場。

導出操作數據的方式很多:可將定期統計快照寫入日誌、建立 Swing GUI、使用內嵌 HTTP 伺服器在網頁顯示統計、或發布可查詢應用程式的 Web Service。但受限於缺乏監控與數據發布基礎設施,多數開發者難以做到,導致對應用程式運行狀況的了解遠低於預期。

JMX:標準化的管理與監控基礎設施

Java 5.0 中,類庫與 JVM 提供了全面的管理監控基礎設施——JMX。JMX 是提供可遠程存取管理介面的標準措施,也是向應用程式添加靈活強大管理介面的簡易方式。受管 Bean(MBean)是提供與實體管理相關的存取器與業務方法的 JavaBean。每個受管實體(可能是整個應用程式或其中的服務)實例化一個 MBean 並以可讀名稱註冊。支援 JMX 的應用程式依賴 MBeanServer,它充當 MBean 容器,提供遠程存取、命名空間管理與安全服務。客戶端方面,jconsole 工具可作為統一的 JMX 客戶端。兩者結合,平台對 JMX 的支援極大降低了使應用程式支援外部管理介面所需的工作量。

除了提供 MBeanServer 實作,Java SE 5.0 還讓 JVM 更容易洞悉記憶體管理、類載入、活動執行緒、日誌與平台配置狀態。大多數平台服務的監控與管理預設開啟(效能影響極小),只需將應用程式與 JMX 客戶端連接即可。圖 1 展示 jconsole JMX 客戶端(JDK 一部分)顯示的記憶體管理視圖——一段時間內的堆使用情況。「Perform GC」按鈕證明了 JMX 不僅能檢視操作統計,還能執行初始化操作。

圖 1. 使用 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(可用進階特性——動態 MBean 移除此限制)。向預設 MBeanServer 註冊 MBean 也很容易,如清單 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 標籤

圖 3. 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 搜尋應用程式中的活動任務與排隊任務

結語

平台內建 JMX 支援與 jconsole JMX 客戶端結合,提供向應用程式添加管理監控功能的輕鬆途徑。即使無具體管理需求的應用程式,建置這些功能也能讓您深入了解程式運行及其處理資料的性質,且不費太多力氣。若應用程式導出管理介面,讓您能檢視它操作的內容,您就能更了解其運行狀態——對其是否按預期工作更有信心——而無需求助額外工具(如添加日誌代碼、使用除錯器或分析器)。

參考資料

學習

  • 可參閱本文在 developerWorks 全球站點上的 英文原文。
  • 「Monitoring and Managing Using JMX」(Sun Microsystems Inc.,2004):詳細介紹內建 JMX 代理的配置與使用。
  • JMX Best Practices (Sun Developer Network, 1994-2006):描述命名管理物件、選擇 JMX 特性與受管屬性資料類型的最佳實踐。
  • 使用 JMX 管理 Apache Geronimo (developerWorks, J. Jeffrey Hanson, 2006 年 8 月):學習 Geronimo 應用程式伺服器如何利用 JMX 便利應用程式管理。
  • Java 技術專區:數百篇關於 Java 程式設計各方面的文章。

關於作者

Brian Goetz 專業軟體開發 20 年。Sun Microsystems 高級工程師,參與多個 JCP 專家組。新書 Java Concurrency In Practice 2006 年 5 月由 Addison-Wesley 出版。請參閱 Brian 在業界出版物上 已發表與即將發表的文章。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved