/**
*作者:張榮華(ahuaxuan)
*2007-8-15
*轉載請注明出處及作者
*/
前兩天在看Spring內(nèi)置的攔截器的時候,發(fā)現(xiàn)了一個之前沒有注意的類:org.springframework.aop.interceptor.JamonPerformanceMonitorInterceptor,好奇心促使我上網(wǎng)查了一下這個jamon。大概看了一下之后發(fā)現(xiàn)這個玩意還真挺好用的而且挺重要的,而且現(xiàn)在國內(nèi)對它的介紹也很少,所以寫了一篇文章和大家分享。
一,Jamon簡介:
Jamon的全名是:Java Application Monitor。它是一個小巧的,免費的,高性能的,線程安全的性能監(jiān)測工具。它可以用來測定系統(tǒng)的性能瓶頸,也可以用來監(jiān)視用戶和應用程序之間的交互情況。 Jamon主要是用來檢測jee的應用程序。它最新的版本是2.1,可以用在1.4以上的jdk上。
二,將jamon導入到你的應用程序中去
首先下載jamon的開發(fā)包,見我的附件,同時你也可以去Sourceforge上自己下載。Sourceforge的下載地址為http://jamonapi.sourceforge.net。解壓之后可以得到一個jar包和一個war包。jar包是自己會用到的,而war包是一個例子(不要小看這個例子,待會也要把它導入到項目中)。把war包之間丟到服務器上,訪問:localhost:8080/jamon就可以看到這個例子了,這個例子是一個簡單的性能監(jiān)控系統(tǒng)。
接著把例子中的所有的包都導入到項目中,并把war包中的jsp和images還有css都考到項目中,比如新建一個目錄叫monitor(它和WEB-INF是同級目錄)。
三,正確配置自己的應用
我們在性能監(jiān)測的時候最監(jiān)測的就是頁面的訪問率和類中方法的訪問率。所以在這一部分主要講解一下如何監(jiān)測自己的頁面和類中方法的訪問。
1, 檢測自己的頁面訪問率
首先我們需要在web.xml中添加一個filter,這個filter就是用來判斷哪些頁面需要被監(jiān)視的,如下所示:
- <filter>
- <filter-name>JAMonFilter</filter-name>
- <filter-class>com.easywebwork.filter.EasyPageMonFilter</filter-class>
- </filter>
- <filter-mapping>
- <filter-name>JAMonFilter</filter-name>
- <url-pattern>/*</url-pattern>
- </filter-mapping>
<filter>
<filter-name>JAMonFilter</filter-name>
<filter-class>com.easywebwork.filter.EasyPageMonFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>JAMonFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
接下來我們看看這個filter的寫法:
-
-
-
-
-
- public class PageMonFilter extends JAMonFilter{
-
- private static final long serialVersionUID = 5746197114960908454L;
-
- public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
- Monitor allPages = MonitorFactory.start(new MonKeyImp("org.easywebwork.allPages",getURI(request),"ms."));
-
- Monitor monitor = MonitorFactory.start(getURI(request));
-
- try {
- filterChain.doFilter(request, response);
- } finally {
- monitor.stop();
- allPages.stop();
- }
- }
-
- protected String getURI(ServletRequest request) {
- if (request instanceof HttpServletRequest) {
- return ((HttpServletRequest) request).getRequestURI();
- } else {
- return "Not an HttpServletRequest";
- }
- }
-
- private FilterConfig filterConfig = null;
-
- }}
/**
* @author 張榮華(ahuaxuan)
*
* @since 2007-8-13
*/
public class PageMonFilter extends JAMonFilter{
private static final long serialVersionUID = 5746197114960908454L;
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
Monitor allPages = MonitorFactory.start(new MonKeyImp("org.easywebwork.allPages",getURI(request),"ms."));
//這里就是我們要監(jiān)視的所有的頁面的配置
Monitor monitor = MonitorFactory.start(getURI(request));
//這里就是我們要監(jiān)視的某個頁面的配置
try {
filterChain.doFilter(request, response);
} finally {
monitor.stop();
allPages.stop();
}
}
protected String getURI(ServletRequest request) {
if (request instanceof HttpServletRequest) {
return ((HttpServletRequest) request).getRequestURI();
} else {
return "Not an HttpServletRequest";
}
}
private FilterConfig filterConfig = null;
}}
這個類看上去很簡單,其實也挺簡單的,就是得到uri,然后把它注冊到MonitorFactory類中。這樣只要我們?nèi)ピL問剛才創(chuàng)建的monitor目錄下的jsp就可以看到性能監(jiān)測頁面了。
2, ,接下來我們看看在使用spring的情況下如何監(jiān)測一個bean的方法調(diào)用。Spring也提供了對Jamon的支持(spring支持的東西還真多啊),也就是文章開頭提出的那個攔截器,為了給我們的bean加上攔截器,我們在spring的applicationcontext配置文件中加入如下語句:
- <bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
- <property name="beanNames">
- <list>
- <value>userService</value>
- </list>
- </property>
- <property name="interceptorNames">
- <list>
- <value>jamonInterceptor</value>
- </list>
- </property>
- </bean>
-
- <bean id="jamonInterceptor" class="org.springframework.aop.interceptor.JamonPerformanceMonitorInterceptor">
- </bean>
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="beanNames">
<list>
<value>userService</value>
</list>
</property>
<property name="interceptorNames">
<list>
<value>jamonInterceptor</value>
</list>
</property>
</bean>
<bean id="jamonInterceptor" class="org.springframework.aop.interceptor.JamonPerformanceMonitorInterceptor">
</bean>
上面這個是典型的spring的aop的配置,如果對spring的aop配置不了解的可以去看一下spring中文文檔,當然如果不想了解的話即使直接把這段配置拷到自己的項目中也是可以直接使用的。
還有一個步驟就是在你的log4j.properties中加入這句代碼:
- log4j.logger.org.springframework.aop.interceptor.JamonPerformanceMonitorInterceptor = TRACE
log4j.logger.org.springframework.aop.interceptor.JamonPerformanceMonitorInterceptor = TRACE
如果沒有這一行,那么這個攔截器是不會把方法調(diào)用的信息向MonitorFactory注冊的。
只需要這些步驟,userservice中的方法在調(diào)用的時候就可以被攔截,然后將其注冊到MonitorFactory中去了。
所有的配置完成之后我們來看一下效果吧:
http://www.javaeye.com/topics/download/b2bac96e-6c18-4340-b7e0-f84c7bb6adca從這個圖上我們可以看到,所有頁面被訪問的次數(shù),UserService中的getAllUsers被調(diào)用的次數(shù),最右邊的是訪問時間。這只是整個圖的一部分,當然這個頁面中也包括每一個頁面被訪問的次數(shù)和第一次訪問的時間等等。下載附件運行,就可以看到所有的頁面了。
三,總結
根據(jù)以上的步驟,我們就可以監(jiān)測我們的程序了,應用程序中哪些頁面被訪問的多,哪些頁面被訪問的少,哪些方法被訪問的多,哪些方法被訪問的少,以及訪問高峰期集中在什么時間等等,有了這些參數(shù),我們更可以有針對性的對應用程序進行優(yōu)化了,比如說某個頁面訪問比較頻繁,我就可以用ehcache或oscache給這個頁面做一個緩存。如果某個方法的訪問比較頻繁那就看看這個方法能否進一步優(yōu)化,是需要異步,還是需要緩存,還是需要其他等等,總之有了jamon可以給我們帶來更多的便捷,既可以讓我們知道我們的客戶的行為,也可以讓我們知道我們開發(fā)的程序的“能力”。
其實本文提供的只是對頁面和方法調(diào)用的監(jiān)控,但是jamon可以提供更多功能,比如說sql語句的監(jiān)控等等,這就需要我們共同去發(fā)掘了。
附件中包括了一個easywebwork的例子,我把jamon導入到這個例子工程中去,大家可以直接下載運行觀看效果。Easywebwork是一個旨在減少webwork2.2.x系列的xml配置文件的項目,
如果對這個主題感興趣請到
http://www.javaeye.com/topic/91614
http://www.javaeye.com/topic/93814
參加討論。
之前有一篇文章講到如何使用jamon來監(jiān)控請求以及方法得調(diào)用(原文地址見:[url]http://www.javaeye.com/post/354575 [/url]),本文屬于其姊妹篇,使用jamon監(jiān)控系統(tǒng)的sql調(diào)用及其調(diào)用效率。
需求:
1我們知道在使用hibernate得時候,我們可以打開show sql選項,可以直接查看sql語句調(diào)用的情況,那么當我們使用其他持久技術的時候我們也需要這個功能怎么辦呢,沒有關系,jamon能夠幫我們做到。
2 很多時候,不同的程序員會寫出不同的性能的sql,有時候可能會不小心或者因為不知道而寫出性能很差的sql,我自己曾經(jīng)就發(fā)生過這種事情,在500w條數(shù)據(jù)的表里使用了一個limit來分頁,到后面,執(zhí)行一條sql都需要幾分鐘,諸如此類的時候可能大家都有碰到過,如果能有監(jiān)控sql性能的工具嵌在應用里該多好,當然有jamon就可以幫我們做到。
對于jamon來說,每一個query的執(zhí)行之后的統(tǒng)計結果都會被保存下來,這些概要統(tǒng)計都以MonProxy-SQL開頭。這些統(tǒng)計中包括查詢執(zhí)行的時間,有比如平均時間,執(zhí)行總時間,最小執(zhí)行時間,最大執(zhí)行時間,這些東西難道不是我們正想要的嗎。
那么讓我們開始吧,我們知道,這些query執(zhí)行的統(tǒng)計應該是在connection中被統(tǒng)計的,也就是說我們要代理一般的connection,而connection又是由datasource產(chǎn)生的,所以我們可以代理datasource,說干就干。
一個datasource接口中關于connection的方法只有兩個:
-
-
-
-
-
-
-
- Connection getConnection() throws SQLException;
-
-
-
-
-
-
-
-
-
-
-
-
- Connection getConnection(String username, String password)
- throws SQLException;
/**
* <p>Attempts to establish a connection with the data source that
* this <code>DataSource</code> object represents.
*
* @return a connection to the data source
* @exception SQLException if a database access error occurs
*/
Connection getConnection() throws SQLException;
/**
* <p>Attempts to establish a connection with the data source that
* this <code>DataSource</code> object represents.
*
* @param username the database user on whose behalf the connection is
* being made
* @param password the user's password
* @return a connection to the data source
* @exception SQLException if a database access error occurs
* @since 1.4
*/
Connection getConnection(String username, String password)
throws SQLException;
也就是說我們只要override這兩個方法即可。
根據(jù)這個思路我寫了以下代碼:
-
-
-
-
-
- public class MonitorDataSource implements DataSource {
- public DataSource realDataSource;
-
- public void setRealDataSource(DataSource realDataSource) {
- this.realDataSource = realDataSource;
- }
-
- public DataSource getRealDataSource() {
- return realDataSource;
- }
- public Connection getConnection() throws SQLException {
-
- return MonProxyFactory.monitor(realDataSource.getConnection());
- }
-
- public Connection getConnection(String username, String password)
- throws SQLException {
-
-
- return MonProxyFactory.monitor(realDataSource.getConnection(username,
- password));
- }
- }
/**
* @author ahuaxuan(aaron zhang)
* @since 2008-2-25
* @version $Id$
*/
public class MonitorDataSource implements DataSource {
public DataSource realDataSource;
public void setRealDataSource(DataSource realDataSource) {
this.realDataSource = realDataSource;
}
public DataSource getRealDataSource() {
return realDataSource;
}
public Connection getConnection() throws SQLException {
//表示由jamon來代理realDataSource返回的Connection
return MonProxyFactory.monitor(realDataSource.getConnection());
}
public Connection getConnection(String username, String password)
throws SQLException {
//表示由jamon來代理realDataSource返回的Connection
return MonProxyFactory.monitor(realDataSource.getConnection(username,
password));
}
}
顯然這個一個代理模式。接下來就是生成這個代理類,我是在spring中注冊了這么一個類:
- <bean id="writeMonitorDataSource" class="org.ahuaxuan.MonitorDataSource" destroy-method="close">
- <property name="realDataSource" ref="writeDataSource"/>
- </bean>
<bean id="writeMonitorDataSource" class="org.ahuaxuan.MonitorDataSource" destroy-method="close">
<property name="realDataSource" ref="writeDataSource"/>
</bean>
writeMonitorDataSource 所依賴的writeDataSource就是我們真正配置的datasource,比如:
- <bean id="writeDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
- <property name="driverClassName">
- <value>${jdbc.driverClassName}</value>
- </property>
- <property name="url">
- <value>${jdbc.url}</value>
- </property>
- <property name="username">
- <value>${jdbc.username}</value>
- </property>
- <property name="password">
- <value>${jdbc.password}</value>
- </property>
- <property name="maxActive">
- <value>${jdbc.maxActive}</value>
- </property>
- <property name="maxIdle">
- <value>${jdbc.maxIdle}</value>
- </property>
- <property name="maxWait">
- <value>${jdbc.maxWait}</value>
- </property>
- </bean>
<bean id="writeDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName">
<value>${jdbc.driverClassName}</value>
</property>
<property name="url">
<value>${jdbc.url}</value>
</property>
<property name="username">
<value>${jdbc.username}</value>
</property>
<property name="password">
<value>${jdbc.password}</value>
</property>
<property name="maxActive">
<value>${jdbc.maxActive}</value>
</property>
<property name="maxIdle">
<value>${jdbc.maxIdle}</value>
</property>
<property name="maxWait">
<value>${jdbc.maxWait}</value>
</property>
</bean>
好了,那么在使用datasource的時候,我們應該用哪個呢,當然是writeMonitorDataSource這個里,我們可以把它注入給jdbcTemplate,或者sessionfactory,或者其他需要用到datasource的地方。
到這里,就一切準備完畢了,我們可以看看我們sql語句的執(zhí)行效率了(這個頁面的地址為sql.jsp):
見圖1
當然要我們的應用能夠顯示這個頁面,我們需要把jamon的一組頁面拷到我們的應用中,這一組頁面包含在我提供下載的包中,最新的jamon版本是2.7。
我們可以看到id為153的那條sql語句執(zhí)行了78ms,我要去看看這條sql語句是不是有點什么問題或者是否有優(yōu)化的可能性。
當然,剛才說到每一條sql語句都是有統(tǒng)計平均時間,最大最小執(zhí)行時間等等,沒錯,在另外一個頁面jamonadmin.jsp上就包含這些內(nèi)容
見圖2
上面的圖片代表hits表示執(zhí)行次數(shù),avg表示sql執(zhí)行的平均時間,后面的min和max表示sql執(zhí)行的最小耗時和最大耗時。從這里我們能夠更直觀的看到我們每條sql語句執(zhí)行的情況。很有用的一個功能。
而且在上面那兩個頁面上,我們還可以選擇把sql執(zhí)行的結果導出來,可以導成xml或excel格式。
總結:使用jamon來監(jiān)控我們的sql語句我覺得很有使用意義,而且使用jamon對我們的應用來說完全是松耦合的,根本不需要更改我們的業(yè)務邏輯代碼,完全是可插拔的,我們也可以開發(fā)時使用jamon,部署時拔掉jamon。有了它能夠使一些程序員能夠更多一點的關注自己所寫的sql的效率,當然如果之前開發(fā)的時候沒有使用jamon也沒有關系,即使上線后也可以查看一下sql語句是否有問題,比如哪些sql語句執(zhí)行得比較頻繁,是否存在給其做緩存得可能性等等。總之使用jamon在應用程序中來監(jiān)控我們得sql語句具有很強得實用意義,
再次總結:jamon,很好,很強大。