前言:做了一個監控應用服務器的項目(支持Tocmat、WebSphere、WebLogic各版本), 過程也算是磕磕絆絆,由於網上缺少相關資料,或者深陷於知識的海洋難以尋覓到有效的資料,因而走過不少彎路,遇過不少困難。為了留下點印記,給后來人留下 點經驗之談,助之少走彎路,故將這些經驗整理出來,與大家分享。水平有限,難免疏漏,還望指正。如有疑問,歡迎留言,或者加入Q群參與討論:35526521。
對監控Tomcat的前期方案調研,共有兩種方案,比較如下
使用Tomcat提供的manager應用進行數據采集
◆ 可以使用現有的成熟代碼,減少工作量
◆ 支持各不同版本時無差別
◆ 可能存在特殊需求而manager不能滿足的情況
◆ 最重要的一個優點是,配置比較簡單
使用JMX接口開發監控程序
◆ 全部代碼需要從零開始,代碼量較大
◆ 支持各不同版本比較麻煩,每個版本可能有差異
◆ 可支配性強
◆ 最重要的一個缺點是,配置比較麻煩
方案一、使用Tomcat提供的manager應用進行數據采集
ManageEngine Applications Manager(又稱opManager)就是通過這種方式實現的。
使用這種方式,所監控Tomcat必須運行manager應用,缺省情況下,該應用總是運行在服務器中的。
增加manager角色用戶
訪問manager應用的用戶的角色權限必須是 manager.
修改<TOMCAT_HOME>/conf目錄下的tomcat-users.xml文件,在<tomcat-users>節點下添加一個user節點,即可創建一個用戶。
Tomcat版本不同配置也有差異,5.x和6.x創建的用戶角色應為manager,7.x創建的用戶角色為manager-jmx,舉例如下:
1、在5.x和6.x中創建一個manager角色的用戶,用戶名為admin,密碼為admin123:
<user username="chenfeng" password="chenfeng" roles="manager"/>
2、在7.x中創建一個manager角色的用戶,用戶名為admin,密碼為admin123:
<user username="chenfeng" password="chenfeng" roles="manager-jmx,manager-script,manager-status"/>
修改配置后,需要重新啟動 Tomcat 服務器。
連接manager時將用戶名/密碼指定為chenfeng/chenfeng
通過瀏覽器訪問JMX Proxy Servlet
詳見官方說明文檔:
http://tomcat.apache.org/tomcat-6.0-doc/manager-howto.html#Using_the_JMX_Proxy_Servlet
What is JMX Proxy Servlet
The JMX Proxy Servlet is a lightweight proxy to get and set the tomcat internals. (Or any class that has been exposed via an MBean) Its usage is not very user friendly but the UI is extremely help for integrating command line scripts for monitoring and changing the internals of tomcat. You can do two things with the proxy: get information and set information. For you to really understand the JMX Proxy Servlet, you should have a general understanding of JMX. If you don't know what JMX is, then prepare to be confused.
JMX Query command
This takes the form:
http://webserver/manager/jmxproxy/?qry=STUFF
Where STUFF is the JMX query you wish to perform. For example, here are some queries you might wish to run:
◆ qry=*%3Atype%3DRequestProcessor%2C* --> type=RequestProcessorwhich will locate all workers which can process requests and report their state.
◆ qry=*%3Aj2eeType=Servlet%2c* --> j2eeType=Servletwhich return all loaded servlets.
◆ qry=Catalina%3Atype%3DEnvironment%2Cresourcetype%3DGlobal%2Cname%3DsimpleValue --> Catalina:type=Environment,resourcetype=Global,name=simpleValuewhich look for a specific MBean by the given name.
You'll need to experiment with this to really understand its capabilites. If you provide no qry parameter, then all of the MBeans will be displayed. We really recommend looking at the tomcat source code and understand the JMX spec to get a better understanding of all the queries you may run.
通過瀏覽器訪問http://localhost:8080/manager/jmxproxy ,輸入用戶名密碼,然后就可以看到返回了所有的監控信息
添加查詢參數,返回特定的監控信息:
例如 http://localhost:8080/manager/jmxproxy?qry=*%3Atype%3DRequestProcessor%2C*
其中 *%3Atype%3DRequestProcessor%2C* 其實就是 *:type=RequestProcessor,*
又如 http://localhost:8080/manager/jmxproxy?qry=*%3Aj2eeType%3DWebModule%2Cname%3D//localhost/ajaxrpc%2C*
在代碼中訪問JMX Proxy Servlet
通過瀏覽器訪問JMX Proxy Servlet需要輸入用戶名密碼,所以通過Java訪問JMX Proxy Servlet的URL也需要授權訪問:
URL url = new URL("http://localhost:8080/manager/jmxproxy?qry=*%3Atype%3DManager%2C*");
URLConnection conn = (URLConnection) url.openConnection();
// URL授權訪問 -- Begin String password = "admin:chenfeng"; // manager角色的用戶 String encodedPassword = new BASE64Encoder().encode(password.getBytes());
conn.setRequestProperty("Authorization", "Basic " + encodedPassword);
// URL授權訪問 -- End InputStream is = conn.getInputStream();
BufferedReader bufreader = new BufferedReader(new InputStreamReader(is));
String line = null;
while ((line = bufreader.readLine()) != null) {
System.out.println(line);
}
幾個具體的例子
下面展示兩個例子,一個是采集服務器基本信息,一個是采集Web應用列表信息,注意Tomcat 7.x和Tomcat 5.x、6.x之間存在很大的區別。
◆ 采集服務器基本信息
通過serverinfo命令查看服務器基本信息
http://localhost:8080/manager/serverinfo
Tomcat 7.x的查詢URL有變化:
http://localhost:8080/manager/text/serverinfo
返回信息:
OK - Server info
Tomcat Version: Apache Tomcat/7.0.11
OS Name: Windows Vista
OS Version: 6.1
OS Architecture: x86
JVM Version: 1.6.0_13-b03
JVM Vendor: Sun Microsystems Inc.
◆ 采集Web應用列表信息
通過list命令查看Web應用列表和會話數信息
http://localhost:8080/manager/list
Tomcat 7.x的查詢URL有變化:
http://localhost:8080/manager/text/list
返回信息:
OK - Listed applications for virtual host localhost
/:running:0:ROOT
/manager:running:1:manager
/docs:running:0:docs
/examples:running:0:examples
/host-manager:running:0:host-manager
方案二、使用JMX接口開發監控程序
Tomcat激活JMX遠程配置
① ■ 先修改Tomcat的啟動腳本,window下tomcat的bin/catalina.bat(linux為catalina.sh),添加以下內容,8999是jmxremote使用的端口號,第二個false表示不需要鑒權:
set JMX_REMOTE_CONFIG=-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=8999 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false
set CATALINA_OPTS=%CATALINA_OPTS% %JMX_REMOTE_CONFIG%
可以加在if "%OS%" == "Windows_NT" setlocal 一句后的大段的注釋后面。
參考官方說明:
http://tomcat.apache.org/tomcat-6.0-doc/monitoring.html#Enabling_JMX_Remote
② ■ 上面的配置是不需要鑒權的,如果需要鑒權則添加的內容為:
set JMX_REMOTE_CONFIG=-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=8999 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=true -Dcom.sun.management.jmxremote.password.file=../conf/jmxremote.password -Dcom.sun.management.jmxremote.access.file=../conf/jmxremote.access
set CATALINA_OPTS=%CATALINA_OPTS% %JMX_REMOTE_CONFIG%
然后復制並修改授權文件,$JAVA_HOME/jre/lib/management下有jmxremote.access和jmxremote.password的模板文件,將兩個文件復制到$CATALINA_BASE/conf目錄下
● 修改$CATALINA_BASE/conf/jmxremote.access 添加內容:
monitorRole readonly
controlRole readwrite
● 修改$CATALINA_BASE/conf/jmxremote.password 添加內容:
monitorRole tomcat
controlRole tomcat
注意:如果只做第一步沒有問題,進行了第二步Tomcat就啟動不了,那么很可能是密碼文件的權限問題
需要修改jmxremote.password文件的權限,只有運行Tomcat的用戶有訪問權限
Windows的NTFS文件系統下,選中文件,點右鍵 -->“屬性”-->“安全”--> 點“高級”--> 點“更改權限”--> 去掉“從父項繼承....”--> 彈出窗口中選“刪除”,這樣就刪除了所有訪問權限。再選“添加”--> “高級”--> “立即查找”,選中你的用戶,例administrator,點“確定",“確定"。來到權限項目窗口,勾選“完全控制”,點“確定”,OK了。
官方的提示:
The password file should be read-only and only accessible by the operating system user Tomcat is running as.
③ ■ 重新啟動Tomcat,在Windows命令行輸入“netstat -a”查看配置的端口號是否已打開,如果打開,說明上面的配置成功了。
④ ■ 使用jconsole測試JMX。
運行$JAVA_HOME/bin目錄下的jconsole.exe,打開J2SE監視和管理控制台,然后建立連接,如果是本地的Tomcat則直接選擇然后點擊連接,如果是遠程的,則進入遠程選項卡,填寫地址、端口號、用戶名、口令即可連接。Mbean屬性頁中給出了相應的數據,Catalina中是tomcat的,java.lang是jvm的。對於加粗的黑體屬性值,需雙擊一下才可看內容。
使用JMX監控Tomcat示例代碼
String jmxURL = "service:jmx:rmi:///jndi/rmi://192.168.10.93:8999/jmxrmi";
JMXServiceURL serviceURL = new JMXServiceURL(jmxURL);
Map map = new HashMap();
// 用戶名密碼,在jmxremote.password文件中查看 String[] credentials = new String[] { "monitorRole", "tomcat" };
map.put("jmx.remote.credentials", credentials);
JMXConnector connector = JMXConnectorFactory.connect(serviceURL, map);
MBeanServerConnection mbsc = connector.getMBeanServerConnection();
// 端口最好是動態取得 ObjectName threadObjName = new ObjectName("Catalina:type=ThreadPool,name=http-8080");
MBeanInfo mbInfo = mbsc.getMBeanInfo(threadObjName);
// tomcat的線程數對應的屬性值 String attrName = "currentThreadCount";
MBeanAttributeInfo[] mbAttributes = mbInfo.getAttributes();
System.out.println("currentThreadCount:" + mbsc.getAttribute(threadObjName, attrName));
完整的示例代碼文件
import java.lang.management.MemoryUsage;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Formatter;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import javax.management.MBeanAttributeInfo;
import javax.management.MBeanInfo;
import javax.management.MBeanServerConnection;
import javax.management.ObjectInstance;
import javax.management.ObjectName;
import javax.management.openmbean.CompositeDataSupport;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;
/** * 使用JMX監控Tomcat示例 * * @author 陳峰
*/ public class JMXTest {
/** * @param args
*/ public static void main(String[] args) {
try {
String jmxURL = "service:jmx:rmi:///jndi/rmi://127.0.0.1:8999/jmxrmi";
JMXServiceURL serviceURL = new JMXServiceURL(jmxURL);
Map map = new HashMap();
String[] credentials = new String[] { "monitorRole", "tomcat" };
map.put("jmx.remote.credentials", credentials);
JMXConnector connector = JMXConnectorFactory.connect(serviceURL,
map);
MBeanServerConnection mbsc = connector.getMBeanServerConnection();
// 端口最好是動態取得 ObjectName threadObjName = new ObjectName(
"Catalina:type=ThreadPool,name=http-8080");
MBeanInfo mbInfo = mbsc.getMBeanInfo(threadObjName);
String attrName = "currentThreadCount";// tomcat的線程數對應的屬性值 MBeanAttributeInfo[] mbAttributes = mbInfo.getAttributes();
System.out.println("currentThreadCount:"
+ mbsc.getAttribute(threadObjName, attrName));
// heap for (int j = 0; j < mbsc.getDomains().length; j++) {
System.out.println("###########" + mbsc.getDomains()[j]);
}
Set MBeanset = mbsc.queryMBeans(null, null);
System.out.println("MBeanset.size() : " + MBeanset.size());
Iterator MBeansetIterator = MBeanset.iterator();
while (MBeansetIterator.hasNext()) {
ObjectInstance objectInstance = (ObjectInstance) MBeansetIterator
.next();
ObjectName objectName = objectInstance.getObjectName();
String canonicalName = objectName.getCanonicalName();
System.out.println("canonicalName : " + canonicalName);
if (canonicalName
.equals("Catalina:host=localhost,type=Cluster")) {
// Get details of cluster MBeans System.out.println("Cluster MBeans Details:");
System.out
.println("=========================================");
// getMBeansDetails(canonicalName); String canonicalKeyPropList = objectName
.getCanonicalKeyPropertyListString();
}
}
// ------------------------- system ---------------------- ObjectName runtimeObjName = new ObjectName("java.lang:type=Runtime");
System.out.println("廠商:"
+ (String) mbsc.getAttribute(runtimeObjName, "VmVendor"));
System.out.println("程序:"
+ (String) mbsc.getAttribute(runtimeObjName, "VmName"));
System.out.println("版本:"
+ (String) mbsc.getAttribute(runtimeObjName, "VmVersion"));
Date starttime = new Date((Long) mbsc.getAttribute(runtimeObjName,
"StartTime"));
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println("啟動時間:" + df.format(starttime));
Long timespan = (Long) mbsc.getAttribute(runtimeObjName, "Uptime");
System.out.println("連續工作時間:" + JMXTest.formatTimeSpan(timespan));
// ------------------------ JVM -------------------------
// 堆使用率 ObjectName heapObjName = new ObjectName("java.lang:type=Memory");
MemoryUsage heapMemoryUsage = MemoryUsage
.from((CompositeDataSupport) mbsc.getAttribute(heapObjName,
"HeapMemoryUsage"));
long maxMemory = heapMemoryUsage.getMax();// 堆最大 long commitMemory = heapMemoryUsage.getCommitted();// 堆當前分配 long usedMemory = heapMemoryUsage.getUsed();
System.out.println("heap:" + (double) usedMemory * 100
/ commitMemory + "%");// 堆使用率 MemoryUsage nonheapMemoryUsage = MemoryUsage
.from((CompositeDataSupport) mbsc.getAttribute(heapObjName,
"NonHeapMemoryUsage"));
long noncommitMemory = nonheapMemoryUsage.getCommitted();
long nonusedMemory = heapMemoryUsage.getUsed();
System.out.println("nonheap:" + (double) nonusedMemory * 100
/ noncommitMemory + "%");
ObjectName permObjName = new ObjectName(
"java.lang:type=MemoryPool,name=Perm Gen");
MemoryUsage permGenUsage = MemoryUsage
.from((CompositeDataSupport) mbsc.getAttribute(permObjName,
"Usage"));
long committed = permGenUsage.getCommitted();// 持久堆大小 long used = heapMemoryUsage.getUsed();//
System.out.println("perm gen:" + (double) used * 100 / committed
+ "%");// 持久堆使用率
// -------------------- Session --------------- ObjectName managerObjName = new ObjectName(
"Catalina:type=Manager,*");
Set<ObjectName> s = mbsc.queryNames(managerObjName, null);
for (ObjectName obj : s) {
System.out.println("應用名:" + obj.getKeyProperty("path"));
ObjectName objname = new ObjectName(obj.getCanonicalName());
System.out.println("最大會話數:"
+ mbsc.getAttribute(objname, "maxActiveSessions"));
System.out.println("會話數:"
+ mbsc.getAttribute(objname, "activeSessions"));
System.out.println("活動會話數:"
+ mbsc.getAttribute(objname, "sessionCounter"));
}
// ----------------- Thread Pool ---------------- ObjectName threadpoolObjName = new ObjectName(
"Catalina:type=ThreadPool,*");
Set<ObjectName> s2 = mbsc.queryNames(threadpoolObjName, null);
for (ObjectName obj : s2) {
System.out.println("端口名:" + obj.getKeyProperty("name"));
ObjectName objname = new ObjectName(obj.getCanonicalName());
System.out.println("最大線程數:"
+ mbsc.getAttribute(objname, "maxThreads"));
System.out.println("當前線程數:"
+ mbsc.getAttribute(objname, "currentThreadCount"));
System.out.println("繁忙線程數:"
+ mbsc.getAttribute(objname, "currentThreadsBusy"));
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static String formatTimeSpan(long span) {
long minseconds = span % 1000;
span = span / 1000;
long seconds = span % 60;
span = span / 60;
long mins = span % 60;
span = span / 60;
long hours = span % 24;
span = span / 24;
long days = span;
return (new Formatter()).format("%1$d天 %2$02d:%3$02d:%4$02d.%5$03d",
days, hours, mins, seconds, minseconds).toString();
}
}
說明: 本文為原創,原發表在ITEye,詳見本人專欄: 監控應用服務器