- 論壇徽章:
- 0
|
概述
在本文中, Rahul Chaudhary將描述性能調(diào)整技術(shù) (PTT performance-tuning techniques) 的使用,來提升servlets 和JSP 的性能,以此來提升你的J2EE應(yīng)用的性能。筆者假設(shè)讀者具有基礎(chǔ)的servlets 和JSPs知識(shí)。
作者:Rahul Chaudhary
譯者:guipei
你的J2EE應(yīng)用程序運(yùn)行緩慢么?它們可以滿足足夠的壓力么?本文將會(huì)描述如何在開發(fā)高性能的應(yīng)用和JSP以及servlets中使用性能調(diào)整技術(shù) (PTT performance-tuning techniques)。使用這些技術(shù)可以構(gòu)建更加快速、穩(wěn)健的系統(tǒng),以滿足更多用戶或者更多請(qǐng)求的需要。在本文中,我將會(huì)帶你進(jìn)行實(shí)際的實(shí)踐,試驗(yàn)如何調(diào)整性能提升你的servlets 和 JSP 頁面緩慢的性能,最終以提升你的J2EE應(yīng)用的性能。其中一部分技術(shù)使用在開發(fā)過程階段,也就是說,適應(yīng)于在你進(jìn)行系統(tǒng)設(shè)計(jì)或者編寫代碼的時(shí)候。另外一些則是和配置相關(guān)技術(shù)。
調(diào)整方法1:使用 HttpServlet init()方法緩存數(shù)據(jù)
應(yīng)用服務(wù)器在servlet開始構(gòu)造的時(shí)候,接受處理任何請(qǐng)求之前調(diào)用servlet的init()方法。在servlet的生命周期中僅僅調(diào)用一次。Init()方法通過緩存靜態(tài)數(shù)據(jù)或者完成占用大量資源的操作,用來在初始化的過程中提高性能。
舉例說明,通過使用jdbc連接池是一個(gè)最好的實(shí)踐,在調(diào)用javax.sql.DataSource接口的時(shí)候。依靠通過JNDI(java命名和服務(wù)接口)樹獲得DataSource。如果在每一次SQL調(diào)用時(shí)候都進(jìn)行JNDI查找DataSource ,將會(huì)嚴(yán)重的影響應(yīng)用服務(wù)的應(yīng)能。Servlet的init()方法將用來取得DataSource,并且將其進(jìn)行緩存以備以后使用。
public class ControllerServlet extends HttpServlet{
private javax.sql.DataSource testDS = null;
public void init(ServletConfig config) throws ServletException{
super.init(config);
Context ctx = null;
try{
ctx = new InitialContext();
testDS = (javax.sql.DataSource)ctx.lookup("jdbc/testDS");
}
catch(NamingException ne){
ne.printStackTrace();
}
catch(Exception e){
e.printStackTrace();
}
}
public javax.sql.DataSource getTestDS(){
return testDS;
}
...
...
}
調(diào)整方法2:禁止servlet和jsp的自動(dòng)重載
為了節(jié)約開發(fā)時(shí)間,在開發(fā)階段Servlet/JSP容器提供自動(dòng)重載功能,方便你在修改Servlet/JSP后不用重新啟動(dòng)服務(wù)?墒,在生產(chǎn)環(huán)境下面,卻是占用大量開銷,因?yàn)檫M(jìn)行了沒有必要的重新載入的操作,所以帶來了很懷的性能影響。同時(shí),在部分類載入,部分為載入的時(shí)候也可能帶來各種奇怪的沖突。因此在J2EE的生產(chǎn)環(huán)境下關(guān)閉自動(dòng)載入功能可以得到更好的性能。
譯者注:
這點(diǎn)被我深深的體會(huì)到。
一、在一個(gè)大型的J2EE項(xiàng)目中進(jìn)行大量壓力測(cè)試下,在開發(fā)模式下莫名其妙的發(fā)生錯(cuò)誤,部分請(qǐng)求任務(wù)失敗,就是因?yàn)檩d入類造成的系統(tǒng)沖突。
二、在另外一個(gè)大型的J2EE項(xiàng)目實(shí)際應(yīng)用過程中,突發(fā)的大量用戶讓足以滿足用戶的系統(tǒng)處于癱瘓邊緣,經(jīng)過了系統(tǒng)、數(shù)據(jù)庫、應(yīng)用服務(wù)器等等調(diào)整后,終沒有解決問題。最后不得不排除人員去現(xiàn)場(chǎng)解決,最后發(fā)現(xiàn)問題竟是這個(gè)原因。這個(gè)事件僅僅發(fā)生在前天。
調(diào)整方法3:控制HttpSession
許多應(yīng)用服務(wù)需要一系列的客戶請(qǐng)求,這些請(qǐng)求之間又相互依存。因?yàn)閔ttp協(xié)議是無狀態(tài)的,所以基于web的應(yīng)用系統(tǒng)必須使用session技術(shù)來維持連接。為了實(shí)現(xiàn)應(yīng)用服務(wù)進(jìn)行狀態(tài)管理,java servlet技術(shù)提供了一套API,通過使用HttpSession對(duì)象進(jìn)行會(huì)話管理,但是在使用這個(gè)功能的同時(shí),不管servlet進(jìn)行任何請(qǐng)求,HttpSession對(duì)象都要進(jìn)行讀寫,服務(wù)器也承擔(dān)了響應(yīng)的系統(tǒng)開銷。你可以使用下列方法提升性能。
在默認(rèn)情況下,不要在jsp頁面中創(chuàng)建HttpSessions對(duì)象,jsp頁面默認(rèn)會(huì)自動(dòng)創(chuàng)建HttpSessions,如果在你的jsp頁面中不需要HttpSessions,為了節(jié)省一些性能,使用下面的頁面指令避免自動(dòng)創(chuàng)建HttpSessions對(duì)象。
不要存儲(chǔ)大型對(duì)象到HttpSession:如果你存儲(chǔ)大型對(duì)象數(shù)據(jù)到HttpSession中,應(yīng)用服務(wù)器不得不在每一次請(qǐng)求中處理整個(gè)的HttpSession,這將會(huì)強(qiáng)迫使用java的串行化操作,占用大量系統(tǒng)資源。應(yīng)用服務(wù)的性能將會(huì)因?yàn)閖ava的串行化操作而減少。
在結(jié)束時(shí)候釋放HttpSessions對(duì)象:在它們不在需要的時(shí)候使用HttpSession.invalidate()方法消除sessions。
設(shè)置session的超時(shí)值:servlet有一個(gè)默認(rèn)的超時(shí)值。如果你在這個(gè)時(shí)間里面,你既沒有移除它,也沒有使用它(進(jìn)行任何服務(wù)請(qǐng)求),servlet服務(wù)將會(huì)自動(dòng)將其銷毀。因?yàn)閷?duì)內(nèi)存和垃圾的回收處理,因此,超時(shí)值越大,對(duì)服務(wù)器的性能影響越大。所以,盡可能的保持session的超時(shí)值最小。
調(diào)整方法4:使用gzip壓縮
壓縮是一個(gè)去處庸余信息的操作,以便可以使用最小的空間存儲(chǔ)。使用gzip(GNU zip)壓縮內(nèi)容可以顯著的減少下載HTML文件的時(shí)間。信息內(nèi)容越小,傳送的速度越快。因此,如果在生成web應(yīng)用的時(shí)候壓縮內(nèi)容,就可以更快的傳送、顯示在用戶的屏幕上面。由于不是每一個(gè)瀏覽器都支持gzip壓縮功能,所以你必須簡(jiǎn)單的檢查瀏覽器是否支持。
下面代碼是演示如何發(fā)送壓縮內(nèi)容的例子:
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException {
OutputStream out = null;
// Check the Accepting-Encoding header from the HTTP request.
// If the header includes gzip, choose GZIP.
// If the header includes compress, choose ZIP.
// Otherwise choose no compression.
String encoding = request.getHeader("Accept-Encoding");
if (encoding != null && encoding.indexOf("gzip") != -1){
response.setHeader("Content-Encoding" , "gzip");
out = new GZIPOutputStream(response.getOutputStream());
}
else if (encoding != null && encoding.indexOf("compress") != -1){
response.setHeader("Content-Encoding" , "compress");
out = new ZIPOutputStream(response.getOutputStream());
}
else
{
out = response.getOutputStream();
}
...
...
}
調(diào)整方法5:不要使用SingleThreadModel
SingleThreadModel接口確保servlet在同一時(shí)間只接受一個(gè)請(qǐng)求。如果servlet實(shí)現(xiàn)這個(gè)接口,servlet將會(huì)為每一個(gè)新的請(qǐng)求創(chuàng)建隔離的servelet實(shí)例,這將造成很大的系統(tǒng)開銷。如果你需要處理線程安全問題,可以使用其他方法代替這種方法。在servlet2.4中SingleThreadModel接口已經(jīng)被反對(duì)使用。
調(diào)整方法6:使用線程池
Servlet引擎為每一個(gè)請(qǐng)求創(chuàng)建一個(gè)隔離的線程,分配這個(gè)線程給service()方法,在它執(zhí)行完后移除這個(gè)線程。默認(rèn)情況下,servlet引擎為每一個(gè)請(qǐng)求創(chuàng)建新的線程。因?yàn)閯?chuàng)建和消除線程是需要系統(tǒng)開銷的,這種行為將會(huì)引起性能問題?梢酝ㄟ^使用線程池來提升性能。依據(jù)所期望的并發(fā)用戶數(shù)量,配置線程池的最大、最小、以及增加數(shù)量。在服務(wù)啟動(dòng)的時(shí)候,servlet引擎使用最小的線程數(shù)量創(chuàng)建一個(gè)線程池。然后servlet引擎會(huì)分配線程給每一個(gè)請(qǐng)求,替換原來的創(chuàng)建新的線程,在處理完成后把線程返回給線程池。使用線程池,性能會(huì)有一個(gè)顯著的提升。如果需要,根據(jù)線程的最大和增加數(shù)量,更多的線程會(huì)被創(chuàng)建,添加到池中以供更多的請(qǐng)求使用。
調(diào)整方法7:選擇正確的包含機(jī)制
在jsp中有兩種方法使用包含文件:
包含指令() 和包含動(dòng)作 ()。
包含指令在轉(zhuǎn)換的過程中包含文件內(nèi)容;也就是說,在一個(gè)頁面轉(zhuǎn)換成一個(gè)servlet的時(shí)候。包含動(dòng)作在請(qǐng)求處理的階段包含文件內(nèi)容;也就是說,在一個(gè)用戶請(qǐng)求頁面的時(shí)候。包含指令快于包含動(dòng)作。因此,除非被包含的內(nèi)容經(jīng)常變化,應(yīng)該使用包含指令()提升性能。
調(diào)整方法8:選擇正確的范圍在使用useBean動(dòng)作
Jsp頁面的一個(gè)強(qiáng)大功能就是在jsp中交互使用JavaBeans組件。通過使用動(dòng)作標(biāo)簽,JavaBeans可以被直接的嵌入jsp頁面中。語法如下:
范圍屬性指定了bean的作用范圍。它的默認(rèn)值是page。你可以根據(jù)你的系統(tǒng)要求選擇正確的范圍。否則它會(huì)影響到應(yīng)用系統(tǒng)的性能。
舉例說明,如果你需要一個(gè)對(duì)象僅僅作為請(qǐng)求使用,但是你的范圍設(shè)置為session,在你完成請(qǐng)求后,這個(gè)對(duì)象將依然會(huì)留在內(nèi)存中。直到你明確的清楚它為止,通過銷毀session,或者session自動(dòng)超時(shí)。假如你沒有選擇正確的范圍,它同樣也會(huì)影響性能因?yàn)檫^度的內(nèi)存和垃圾收集。因此,需要正確的設(shè)置對(duì)象的范圍,同時(shí)當(dāng)你使用完成之后,也應(yīng)該立即去除它們。
其他方法:
避免字符串相加:使用 + 操作會(huì)產(chǎn)生很多臨時(shí)對(duì)象,因?yàn)镾tring是不可變化(immutable)的對(duì)象。越多的 + 操作,越多的臨時(shí)對(duì)象會(huì)被創(chuàng)建,造成很大的系統(tǒng)開銷。使用StringBuffer替換 + 操作,當(dāng)你需要字符串相加的時(shí)候。
避免使用System.out.println:System.out.println是同步處理的在disk i/o操作中,并且會(huì)嚴(yán)重的影響性能。因此最大可能的要避免使用System.out.println。盡管存在強(qiáng)大的調(diào)試工具,有時(shí)候?yàn)榱烁櫮康、錯(cuò)誤處理、調(diào)試程序,還是使用System.out.println。你應(yīng)該配置System.out.println僅僅在開發(fā)和調(diào)試情況下使用。使用一個(gè)靜態(tài)的final boolean變量,在生產(chǎn)模式下,配置成false,避免System.out.println的使用。
ServletOutputStream比較PrintWriter:使用PrintWriter會(huì)占用一些系統(tǒng)開銷,因?yàn)樗菫樘幚碜址鞯妮敵鲚敵龉δ。因此PrintWriter應(yīng)該使用在確保有字符集轉(zhuǎn)換的環(huán)境中。換句話說,在你知道servlet返回的僅僅是二進(jìn)制數(shù)據(jù)時(shí)候,應(yīng)該使用ServletOutputStream,這樣你可以消除字符轉(zhuǎn)換開銷,當(dāng)servlet容器不用處理字符集轉(zhuǎn)換的時(shí)候。
總結(jié)
本文的目的是通過一些實(shí)踐操作,使用性能調(diào)整技術(shù),通過提升servlets和jsp頁面的性能,進(jìn)而提升J2EE應(yīng)用系統(tǒng)的性能。下一次將會(huì)涉及性能調(diào)整關(guān)于EJB (Enterprise JavaBeans), JMS (Java Message Service), and JDBC (Java Database Connectivity)。
本文來自ChinaUnix博客,如果查看原文請(qǐng)點(diǎn):http://blog.chinaunix.net/u3/109937/showart_2144185.html |
|