一、概述
本文檔主要介紹了Tomcat的性能調優的原理和方法。可作為公司技術人員為客戶Tomcat系統調優的技術指南,也可以提供給客戶的技術人員作為他們性能調優的指導手冊。
二、調優分類
由于Tomcat的運行依賴于JVM,從虛擬機的角度我們把Tomcat的調整分為外部環境調優和自身調優兩類來描述。
1. 外部環境調優
調整Tomcat運行環境的操作系統參數和運行Tomcat的java虛擬機參數。
(a) JAVA虛擬機性能優化
Tomcat需要依賴Java虛擬機運行。根據客戶選用的主機的操作系統選擇對應的 JDK的版本。無論哪個廠商的JDK,都建議使用最新的版本。 虛擬機可通過命令行的方式改變虛擬機使用內存的大小。如下表所示有兩個參數用來設置虛擬機使用內存的大小。
-Xms<size> :JVM初始化堆的大小
-Xmx<size> :JVM堆的最大值
Tomcat默認可以使用的內存較小,在較大型的應用項目中,這點內存是不夠的,需要調大。
Windows下,在文件tomcat_home/bin/catalina.bat,Unix下,在文件tomcat_home/bin/catalina.sh的前面,增加如下設置:
JAVA_OPTS=‘-Xms【初始化內存大小】 -Xmx【可以使用的最大內存】
需要把這個兩個參數值調大。例如:JAVA_OPTS='-Xms256m -Xmx512m' ,表示初始化內存為256MB,可以使用的最大內存為512MB。
另外需要考慮的是Java提供的垃圾回收機制。虛擬機的堆大小決定了虛擬機花費在收集垃圾上的時間和頻度。收集垃圾可以接受的速度與應用有關,應該通過分析實際的垃圾收集的時間和頻率來調整。
如果堆的空間很大,那么完全垃圾收集(FULL GC)就會很慢,但是頻度會降低。如果在客戶系統中把堆的大小和內存的需要一致,完全收集就很快,但是會更加頻繁。調整堆大小的的目的是最小化垃圾收集的時間,以在特定的時間內最大化處理客戶的請求。對于SUN和HP等虛擬機,推薦將最小堆大小和最大堆大小設置為同一值,因為這樣可以避免浪費用于時常調整堆大小所需的 VM 資源。
當然,客戶系統如果用到IBM虛擬機,要特別的注意設置-Xms和-Xmx一樣大小會耽誤垃圾回收的開始直到堆滿,這樣第一次垃圾回收就會變成非常昂貴的操作。推薦把-Xms設置為應用所需的最小值,這樣會產生高效的垃圾回收。
(b) 操作系統性能優化
以客戶系統為HP-UX為例。
HP系統中對Tomcat有影響的參數:
max_thread_proc: 一個進程所能創建的線程的最大數
nkthread: 在系統上同時允許的核心線程的最大數
如果在輸出里看到消息:java.lang.OutOfMemoryError: unable to create new native thread,則說明名為 max_thread_proc 的 Unix 內核設置過小。max_thread_proc 是單個進程中的最大線程數。 它必須大到能夠容納 Java 應用程序中的所有線程以及虛擬機本身中的部分額外線程。
查看核心參數:$ulimit -a
顯示輸出中的 nofiles 是指用戶的進程能同時打開的最大文件句柄數。如果日志中出現”two many open files”的異常,需要重點檢查這個參數。coredump 參數是 core 文件最大值的,限制當進程 coredump 時將產生 core文件的大小不能超過這個最大值。如果在日志文件檢查時,發現 core文件不完整,需要增大這個參數值。執行 ulimit -n 命令可以設置 nofiles 參數,執行ulimit -c命令設置 core 文件最大值。
如果是在Windows操作系統上使用Tomcat,那么最好選擇服務器版本。因為在非服務器版本上,最終用戶授權數或者操作系統本身所能承受的用戶數、可用的網絡連接數或其它方面的一些方面都是有限制的。并且基于安全性的考慮,必須經常給操作系統打上最新的補丁。
(c) Tomcat與其它Web服務器整合使用
雖然tomcat也可以作web服務器,但其處理靜態html的速度比不上apache,且其作為web服務器的功能遠不如apache,因此我們想把 apache和tomcat集成起來,將html與jsp的功能部分進行明確分工,讓tomcat只處理jsp部分,其它的由apache,IIS等這些 web服務器處理,由此大大節省了tomcat有限的工作線程。
2. 自身調優
本節將說明Tomcat性能調優的技巧和方法,這些技巧和方法與操作系統或Java虛擬機的種類無關。以下方法都是針對Tomcat 性能自身調整的最佳方式。
(a) 禁用DNS查詢
當web應用程序要記錄客戶端的信息時,它也會記錄客戶端的IP地址或者通過域名服務器查找機器名轉換為IP地址。DNS查詢需要占用網絡,并且包括可能從很多很遠的服務器或者不起作用的服務器上去獲取對應的IP的過程,這樣會消耗一定的時間。為了消除DNS查詢對性能的影響我們可以關閉DNS查詢,方式是修改server.xml 文件中的enableLookups參數值:
Tomcat4
<Connector className=“org.apache.coyote.tomcat4.CoyoteConnector”port=“80” minProcessors=“5” maxProcessors=“75” enableLookups=“false” redirectPort=“8443” acceptCount=“100” debug=“0” connectionTimeout=“20000” useURIValidationHack=“false” disableUploadTimeout=“true” />
Tomcat5
<Connector port=“80” maxThreads=“150” minSpareThreads=“25” maxSpareThreads=“75” enableLookups=“false” redirectPort=“8443” acceptCount=“100” debug=“0” connectionTimeout=“20000” disableUploadTimeout=“true”/>
除非客戶需要連接到站點的每個HTTP客戶端的機器名,否則我們建議在生產環境上關閉DNS查詢功能。可以通過Tomcat以外的方式來獲取機器名。這樣不僅節省了網絡帶寬、查詢時間和內存,而且更小的流量會使日志數據也會變得更少,顯而易見也節省了硬盤空間。對流量較小的站點來說禁用DNS查詢可能沒有大流量站點的效果明顯。
(b) 調整線程數
另外一個可通過應用程序的連接器(Connector)進行性能控制的參數是創建的處理請求的線程數。Tomcat使用線程池加速響應速度來處理請求。在Java中線程是程序運行時的路徑,是在一個程序中與其它控制線程無關的、能夠獨立運行的代碼段。它們共享相同的地址空間。多線程幫助程序員寫出CPU最大利用率的高效程序,使空閑時間保持最低,從而接受更多的請求。
Tomcat4中可以通過修改minProcessors和maxProcessors的值來控制線程數。這些值在安裝后就已經設定為默認值并且是足夠使用的,但是隨著站點的擴容而改大這些值。minProcessors服務器啟動時創建的處理請求的線程數應該足夠處理一個小量的負載。也就是說,如果一天內每秒僅發生5次單擊事件,并且每個請求任務處理需要1秒鐘,那么預先設置線程數為5就足夠了。但在你的站點訪問量較大時就需要設置更大的線程數,指定為參數maxProcessors的值。maxProcessors的值也是有上限的,應防止流量不可控制(或者惡意的服務攻擊),從而導致超出了虛擬機使用內存的大小。如果要加大并發連接數,應同時加大這兩個參數。web server允許的最大連接數還受制于操作系統的內核參數設置,通常Windows是2000個左右,Linux是1000個左右。
在Tomcat5對這些參數進行了調整:
maxThreads :Tomcat使用線程來處理接收的每個請求。這個值表示Tomcat可創建的最大的線程數。
acceptCount :指定當所有可以使用的處理請求的線程數都被使用時,可以放到處理隊列中的請求數,超過這個數的請求將不予處理。
connnectionTimeout :網絡連接超時,單位:毫秒。設置為0表示永不超時,這樣設置有隱患的。通常可設置為30000毫秒。
minSpareThreads :Tomcat初始化時創建的線程數。
maxSpareThreads :一旦創建的線程超過這個值,Tomcat就會關閉不再需要的socket線程。
最好的方式是多設置幾次并且進行測試,觀察響應時間和內存使用情況。在不同的機器、操作系統或虛擬機組合的情況下可能會不同,而且并不是所有的web站點的流量都是一樣的,因此沒有一刀切的方案來確定線程數的值。
(c) 加速JSP編譯速度
當第一次訪問一個JSP文件時,它會被轉換為Java servlet源碼,接著被編譯成Java字節碼。客戶工程師可以控制使用哪個編譯器,默認情況下,Tomcat使用命令行javac進行使用的編譯器。也可以使用更快的編譯器,這里將介紹如何優化它們。
另外一種方法是不要把所有的實現都使用JSP頁面,而是使用一些不同的java模板引擎變量。
在Tomcat 4.0中可以使用流行而且免費的Jikes編譯器。Jikes編譯器的速度要高于Sun的Java編譯器。首先要安裝Jikes(可訪問http://oss.software.ibm.com/pub/jikes獲得更多的信息),接著需要在環境變量中設置JIKESPATH包含系統運行時所需的JAR文件。裝好Jikes以后還需要設置讓JSP編譯servlet使用Jikes,需要修改web.xml文件中jspCompilerPlugin的值:
<servlet>
<servlet-name>jsp</servlet-name>
<servlet-class>
org.apache.jasper.servlet.JspServlet
</servlet-class>
<init-param>
<param-name>logVerbosityLevel</param-name>
<param-value>WARNING</param-value>
</init-param>
<init-param>
<param-name>jspCompilerPlugin</param-name>
<param-value>org.apache.jasper.compiler.JikesJavaCompiler</param-value>
</init-param>
<init-param>
<!-- <param-name>org.apache.catalina.jsp_classpath</param-name> -->
<param-name>classpath</param-name>
<param-value>
/usr/local/jdk1.3.1-linux/jre/lib/rt.jar:
/usr/local/lib/java/servletapi/servlet.jar
</param-value>
</init-param>
<load-on-startup>3</load-on-startup>
</servlet>
在Tomcat 4.1(或更高版本),JSP的編譯由包含在Tomcat里面的Ant程序控制器直接執行。客戶開發人員需要在元素中定義一個名字叫”compiler”,并且在value中有一個支持編譯的編譯器名字,示例如下:
<servlet>
<servlet-name>jsp</servlet-name>
<servlet-class>
org.apache.jasper.servlet.JspServlet
</servlet-class>
<init-param>
<param-name>logVerbosityLevel</param-name>
<param-value>WARNING</param-value>
</init-param>
<init-param>
<param-name>compiler</param-name>
<param-value>jikes</param-value>
</init-param>
<load-on-startup>3</load-on-startup>
</servlet>
由于JSP頁面在第一次使用時已經被編譯,那么你可能希望在更新新的jsp頁面后馬上對它進行編譯。實際上,這個過程完全可以自動化,因為可以確認的是新的JSP頁面在生產服務器和在測試服務器上的運行效果是一樣的。
在Tomcat4的bin目錄下有一個名為jspc的腳本。它僅僅是運行翻譯階段,而不是編譯階段,使用它可以在當前目錄生成Java源文件。它是調試JSP頁面的一種有力的手段。
可以通過瀏覽器訪問再確認一下編譯的結果。這樣就確保了文件被轉換成servlet,被編譯了可直接執行。這樣也準確地模仿了真實用戶訪問JSP頁面,可以看到給用戶提供的功能。也抓緊這最后一刻修改出現的bug并且修改它。
Tomcat提供了一種通過請求來編譯JSP頁面的功能。客戶可以在瀏覽器地址欄中輸入http://localhost: 8080/examples/jsp/dates/date.jsp?jsp_precompile=true,這樣Tomcat就會編譯 data.jsp而不是執行它。此舉唾手可得,不失為一種檢驗頁面正確性的捷徑。
(d) NIO 配置
NIO (No-blocking I/O)從JDK 1.4起,NIO API作為一個基于緩沖區,并能提供非阻塞I/O操作的API被引入 。
TOMCAT可以支持高并發的企業級應用。其中有個很大的原因就是,配置良好的tomcat都會使用APR(Apache Portable Runtime),APR是Apache HTTP Server2.x的核心,它是高度可移植的本地庫,它使用高性能的UXIN I/O操作,低性能的java io操作,但是APR對客戶開發人員而言可能稍稍有點難度,在很多OS平臺上,可能需要重新編譯APR。但是從Tomcat6.0以后, 客戶開發人員很容易就可以用NIO的技術來提升tomcat的并發處理能力。但是為什么NIO可以提升tomcat的并發處理能力呢,我們先來看一下java 傳統io與 java NIO的差別。
Java 傳統的IO操作都是阻塞式的(blocking I/O), 如果有socket的編程基礎,你會接觸過堵塞socket和非堵塞socket,堵塞socket就是在accept、read、write等IO操作的時候,如果沒有可用符合條件的資源,不馬上返回,一直等待直到有資源為止。而非堵塞socket則是在執行select的時候,當沒有資源的時候堵塞,當有符合資源的時候,返回一個信號,然后程序就可以執行accept、read、write等操作,一般來說,如果使用堵塞socket,通常我們通常開一個線程accept socket,當讀完這次socket請求的時候,開一個單獨的線程處理這個socket請求;如果使用非堵塞socket,通常是只有一個線程,一開始是select狀,當有信號的時候可以通過多路復用(Multiplexing)技術傳遞給一個指定的線程池來處理請求,然后原來的線程繼續select狀態。 最簡單的多路復用技術可以通過java管道(Pipe)來實現。換句話說,如果客戶端的并發請求很大的時候,客戶系統可以使用少于客戶端并發請求的線程數來處理這些請求,而這些來不及立即處理的請求會被阻塞在java管道或者隊列里面,等待線程池的處理。
在web服務器上阻塞IO(BIO)與NIO一個比較重要的不同是,客戶系統使用BIO的時候往往會為每一個web請求引入多線程,每個web請求一個單獨的線程,所以并發量一旦上去了,線程數就上去了,CPU就忙著線程切換,所以BIO不合適高吞吐量、高可伸縮的web服務器;而NIO則是使用單線程(單個CPU)或者只使用少量的多線程(多CPU)來接受Socket,而由線程池來處理堵塞在pipe或者隊列里的請求.這樣的話,只要OS可以接受TCP的連接,web服務器就可以處理該請求。大大提高了web服務器的可伸縮性。
客戶只需要在server.xml里把 HTTP Connector做如下更改,
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
改為
<Connector port="8080" protocol="org.apache.coyote.http11.Http11NioProtocol"
connectionTimeout="20000"
redirectPort="8443" />
然后啟動服務器,如果出現org.apache.coyote.http11.Http11NioProtocol start的提示信息,表示NIO已經啟動。其他的配置請參考官方配置文檔。
(e) 其他
前面我們提到過操作系統通過一些限制手段來防止惡意的服務攻擊,同樣Tomcat也提供了防止惡意攻擊或禁止某些機器訪問的設置。
Tomcat提供了兩個參數供你配置:RemoteHostValve 和RemoteAddrValve。 通過配置這兩個參數,可以讓你過濾來自請求的主機或IP地址,并允許或拒絕哪些主機/IP。與之類似的,在Apache的httpd文件里有對每個目錄的允許/拒絕指定。
例如你可以把Admin Web application設置成只允許本地訪問,設置如下:
<Context path=“/path/to/secret_files” >
<Valve className=“org.apache.catalina.valves.RemoteAddrValve”
allow=“127.0.0.1”deny=““/>
</Context>
如果沒有給出允許主機的指定,那么與拒絕主機匹配的主機就會被拒絕,除此之外的都是允許的。與之類似,如果沒有給出拒絕主機的指定,那么與允許主機匹配的主機就會被允許,除此之外的都是拒絕的。
本文轉自:點擊打開鏈接