調優背景
HBCZT 信息中心使用IBM X366服務器Windows2003運行其基於J2EE1.4技術的應用系統。另外運行一個基於COM技術的數據採集應用程序。該程序客戶端讀入用戶填 寫的xls格式表格文件信息,並通過該程序將XLS內容封裝成爲XML並打包ZIP後發送到數據採集程序的服務器端,服務器端接受到文件後,對該ZIP包 進行解包、並對解包後的XML信息進行解析、使用SQL逐條將記錄插入到Oracle數據庫中。數據庫連接池已經設置爲20,但批量數據插入數據庫的時候 (數據量至少500000條記錄,一般情況5000000條記錄)導致數據庫異常緩慢。客戶希望找到系統瓶頸,並提出相應性能調優建議。
1、總體思路
硬件調優、操作系統調優,數據庫調優 略!我們假設都已經是最佳狀態。由於本人負責WebLogic部分的調優,所以以下思路與內容均爲WebLogic方面。特此說明
J2EE 應用架構環境下的系統調優,首先我們一般會從應用程序出發,去審覈代碼,做到代碼級的優化,然後再調整應用服務器(BEA WebLogic8.1)和數據庫 (Oracle9i)的參數,最後當然是調整操作系統和網絡的性能(包括硬件升級)。這是一種MDA的先進做法。誠然,在這樣一個政務項目中,不可能完全 按照這個思路來做,我們把目標首先定位在應用系統所在的應用服務器(BEA WebLogic8.1)上,通過對BEA WebLogic8.1的參數進行設置,使WebLogic8.1能夠在最優化的環境中去運行其系統,然後對ORACLE數據的參數進行優化設置,最後進 行性能測試再找出導致性能瓶頸所在的SQL代碼或JAVA程序,考量其修改的可行性,並進行最終問題優先級認定,與瓶頸模塊進行協商解決性能問題。當然, 一般情況下我見過的案例都是出現了性能問題後纔想到調優,而且一般都是先進行系統參數調整,實在解決不了纔會對代碼進行檢查.實際上,我們應當將代碼級的 調優放在應用設計時來做,測試生產時修改代碼將是一件極其痛苦的事情。
下表爲一般性J2EE性能調優的參照情況一覽表,供參考。
毛病 |
描述 |
症狀 |
原因或治法 |
線性內存泄漏
|
每單位(每事務、每用戶等)泄漏造成內存隨着時間或負載線性增長。這會隨着時間或負載增長降低系統性能。只有重啓纔有可能恢復。 |
隨着時間越來越慢 隨着負載越來越慢 |
雖然可能有多種外部原因,但最典型的是與資源泄漏有關(例如,每單位數據的鏈表存儲,或者沒有回收的回收/增長緩衝區)。 |
指數方式內存泄漏
|
雙倍增長策略的泄漏造成系統內存消耗表現爲時間的指數曲線 |
隨着時間越來越慢 隨着負載越來越慢 |
通常是由於向集合(Vector,HashMap) 中加入永遠不刪除的元素造成的。 |
糟糕的編碼:無限循環
|
線程在 while(true) 語句以及類似的語句裏阻塞。 | 可以預見的鎖定 | 您需要對循環進行大刀闊斧的刪剪。 |
資源泄漏
|
JDBC 語句,CICS 事務網關連接,以及類似的東西被泄漏了,造成對 Java 橋接層和後端系統的影響。 |
隨着時間越來越慢 可以預見的鎖定 突然混亂 |
通常情況下,這是由於遺漏了 finally 塊,或者更簡單點,就是忘記用 close() 關閉代表外部資源的對象所造成的。 |
外部瓶頸問題
|
後端或者其他外部系統(如鑑權)越來越慢,同樣減緩了 J2EE 應用服務器和應用程序 |
持續緩慢 隨着負載越來越慢 |
諮詢專家(負責的第三方或者系統管理員),獲取解決外部瓶頸問題的方法。 |
外部系統
|
J2EE 應用程序通過太大或太多的請求濫用後端系統。 |
持續緩慢 隨着負載越來越慢 |
清除冗餘的工作請求 ,成批處理相似的工作請求,把大的請求分解成若干個更小的請求,調整工作請求或後端系統(例如,公共查詢關鍵字的索引)等。 |
糟糕的編碼:CPU密集的組件
|
這是 J2EE 世界中常見的感冒。一些糟糕的代碼或大量代碼之間一次糟糕的交互,就掛起了 CPU,把吞吐速度減慢到爬行的速度。 |
持續緩慢 隨着負載越來越慢 |
典型的解決方案就是數據高速緩存或者性能計數。 |
中間層問題
|
實現得很糟糕的橋接層(JDBC 驅動程序,到傳統系統的 CORBA 連接),由於對數據和請求不斷的排列、解除排列,從而把所有通過它的流量減慢到爬行速度。這個毛病在早期階段很容易與外部瓶頸混淆。 |
持續緩慢 隨着負載越來越慢 |
檢查橋接層和外部系統的版本兼容性。如果有可能,評估不同的橋接供應商。如果重新規劃架構,有可能完全不需要橋接。 |
內部資源瓶頸:過度使用或分配不足 | 內部資源(線程、放入池的對象)變得稀缺。是在正確使用的情況下加大負載時出現過度使用還是因爲泄漏? |
隨着負載越來越慢 零星的掛起或異常錯誤 |
分配不足:根據預期的最大負載提高池的最大尺寸。過度使用:請參閱外部系統的過度使用。 |
不停止的重試 | 這包括對失敗請求連續的(或者在極端情況下無休止的)重試。 |
可以預見的鎖定 突然混亂 |
可能就是後端系統完全宕機。在這裏,可用性監控會有幫助,或者就是把嘗試與成功分開。 |
線程:阻塞點 | 線程在過於積極的同步點上備份,造成交通阻塞。 |
隨着負載越來越慢 零星的掛起或異常錯誤 可以預見的鎖定 突然混亂 |
可能同步是不必要的(只要重新設計),或者比較外在的鎖定策略(例如,讀/寫鎖)也許會有幫助。 |
線程:死鎖/活動鎖 | 最普遍,這是您基本的“獲得順序”的問題。 | 突然混亂 | 處理選項包括:主鎖,確定的獲得順序,以及銀行家算法。 |
2、調優建議
通過分析其配置。我們發現JDBC連接池存在性能問題。
在 WebLogic中就大量使用了池:JDBC Connection Pool、Socket Pool、Object Pool和Thread Pool。I/O操作中,buffer是必須的,特別是對大文件的操作,不然容易造成內存溢出。字節操作最快,所以儘可能採用 write(byte[]),Buffered FileOutputStream比Buffered FileWriter要快,因爲FileWriter需要Unicode到Byte的轉換。JDBC建議使用buffer和cache。爲 HttpServletResponse設置buffersize,使用wl-cache,緩存在JNDI樹上獲取的對象等等。
此外,使用JDK 1.4的非阻塞I/O對性能也有很大提高。
JDBC 代碼調優最大的原則就是使用WebLogic的連接池,而不是自己直連數據庫。在我接觸的很多自己實現連接池的項目中,大部分遇到死鎖和連接泄漏的問題, 最後得不得修改代碼。而WebLogic提供了功能強大,性能良好的數據庫連接池,我們要做的只是封裝一個連接管理類,從JNDI樹上獲取數據源並緩存, 得到連接,並提供一系列關閉數據庫資源的方法。
對任何資源使用的原則是 用完即關,不管是數據庫資源、上下文環境,還是文件。數據庫資源的泄漏極易造成內存泄漏,乃至系統崩潰。在使用完數據庫資源後依次關閉 ResultSet,Statement和Connection,而在一個數據庫連接多次進行數據庫操作時要特別注意ResultSet和 Statement依次關閉。
由於獲取連接時默認自動提交方式,使用 connection.setAutoCommit(false)關閉自動提交,使用PreparedStatement,批量更新,業務複雜或者大數據 量操作時使用存儲過程,儘量使用RowSet,此外設置記錄集讀取緩存FetchSize和設置記錄集讀取方向FetchDirection對性能也有一 定的提高。
Servlet代碼調優比較簡單:在Servlet之間跳轉 時,forward比sendRedirect更有效;設置HttpServletResponse 緩衝區,如:response.setBufferSize(20000);在init()方法裏緩存靜態數據,而在destroy()中釋放它;建議在 Servlet裏使用ServletOutputStream輸出圖片等對象;避免在Servlet和Jsp中定界事務等。
JDBC Connection Pool的調優受制於WebLogic Server線程數的設置和數據庫進程數,遊標的大小。通常我們在一個線程中使用一個連接,所以連接數並不是越多越好,爲避免兩邊的資源消耗,建議設置連 接池的最大值等於或者略小於線程數。同時爲了減少新建連接的開銷,將最小值和最大值設爲一致。增加Statement Cache Size對於大量使用PreparedStatement對象的應用程序很有幫助,WebLogic能夠爲每一個連接緩存這些對象,此值默認爲10。在保 證數據庫遊標大小足夠的前提下,可以根據需要提高Statement Cache Size。比如當你設置連接數爲25,Cache Size爲10時,數據庫可能需要打開25*10=250個遊標。不幸的是,當遇到與PreparedStatement Cache有關的應用程序錯誤時,你需要將Cache Size設置爲0。儘管JDBC Connection Pool提供了很多高級參數,在開發模式下比較有用,但大部分在生產環境下不需調整。這裏建議最好不要設置測試表, 同時Test Reserved Connections和Test Released Connections也無需勾上。 當然如果你的數據庫不穩定,時斷時續,你就可能需要上述的參數打開。
最 後分析一下JDBC驅動程序類型的選擇,Oracle提供thin驅動和oci驅動,從性能上來講,oci驅動強於thin驅動,特別是大數據量的操作。 但在簡單的數據庫操作中,性能相差不大。所以我建議對數據量至少500000條記錄,一般情況5000000條記錄的狀況使用oci驅動。
通過分析其日誌並使用GC資源查看。我們發現存在內存泄露的垃圾回收失敗問題。
垃 圾收集(GC)是指JVM釋放Java堆中不再使用的對象所佔用的內存的過程,而Java堆(Heap)是指Java應用程序對象生存的空間。堆大小決定 了GC的頻度和時間。堆越大,GC頻度低,速度慢。堆越小,GC頻度高,速度快。所以GC和堆大小是一組矛盾。爲了獲取理想的Heap堆大小,需要使用 -verbosegc參數(Sun jdk: -Xloggc:)以打開詳細的GC輸出。分析GC的頻度和時間,結合應用最大負載所需內存情況,得出堆的大小。通常情況下,我們建議使用可用內存(除操 作系統和其他應用程序佔用之外的內存)70-80%,爲避免堆大小調整引起的開銷,設置內存堆的最小值等於最大值即:-Xms=-Xmx。而爲了防止內存 溢出,建議在生產環境堆大小至少爲256M(Platform至少512M),實際環境中512M~1G左右性能最佳,2G以上是不可取的,在調整內存時 可能需要調整核心參數進程的允許最大內存數。對於sun和hp的jvm,永久域太小(默認4M)也可能造成內存溢出,應增加參 -XX:MaxPermSize=128m。建議設置臨時域-Xmn的大小爲-Xmx的1/4~1/3, SurvivorRatio爲8。
爲了獲得更好的性能,建議在啓動文件設置WebLogic爲產品模式,此時sun和hp jvm JIT引擎爲-server,默認情況下打開JIT編譯模式對性能也有幫助。調整Chunk Size和Chunk Pool Size也可能對系統的吞吐量有提高。此外還需關閉顯示GC: -XX:+DisableExplicitGC。
建 議Intel平臺上使用jRockit(使用參數-jrockit)無疑大大提高WebLogic性能。本系統使用SUN JDK1.4.2_08。jRockit支持四種垃圾收集器:分代複製收集器、單空間併發收集器、分代併發收集器和並行收集器。默認狀態 下,JRockit使用分代併發收集器。要改變收集器,可使用-Xgc:,對應四個收集器分其他爲gencopy, singlecom, gencon以及parallel。爲得到更好的響應性能,應該使用併發垃圾回收器:-Xgc:gencon,可使用-Xms和-Xmx設置堆棧的初始大 小和最大值,要設置護理域-Xns爲-Xmx的10%。而如果要得到更好的性能,應該選用並行垃圾回收器:-Xgc: parallel,由於並行垃圾回收器不使用nursery,不必設置-Xns。jRockit 還提供了強大的圖形化監控工具Jrockit Management Console。可以查看GC性能問題。
通過實時查看並分析操作系統與ORACLE系統中的I/O信息。我們發現存在I/O讀寫佔用資源率高且頻繁問題。
WebLogic Server有兩套套接字複用器:Java版和本地庫。採用小型本地庫更有效,儘量激活Enable Native IO(默認),此時UNIX默認使用CPUs+1個線程,Window下爲雙倍CPU。如果系統不能加載本地庫,將會拋出一個異 常:java.lang.UnsatisfiedLinkException,此時只能使用Java套接字複用器,可以調整socket readers 百分比,默認爲33%。該參數可以在Console Server Tuning Configuration配置欄裏設置。