Java性能調優方法:基於等待的調優

    企業java應用的性能調優是一項艱鉅的、有時甚至是徒勞的任務,這是由現代應用的複雜性和缺少正規的調優方法導致的。現代企業應用與十年前的應用相比差距很大,如今這些應用支持多輸入、多輸出、複雜的框架和業務處理引擎。而十年之前,基於web的企業應用只是通過網絡瀏覽器獲得輸入信息,然後與數據庫或者遺留系統交互進行後臺處理,最後把輸出結果返回給瀏覽器(HTML)。現在,輸入信息可以來自HTML瀏覽器、富客戶端、移動設備或者網絡服務,它可以跨越運行在不同架構下的servlets或者門戶容器,這反過來又可能調用企業bean,外部web服務或者把處理委託給業務規則引擎。每一個這樣的組件都可能與內容管理系統、緩存層、衆多數據庫和遺留系統交互。輸出的信息通常以獨立於展現層的形式保存,隨後轉化爲HTML、XML、WML或者其他任意客戶端需要的格式。現代應用比過去包含更多移動部分和“黑盒子”,這對性能調優提出了巨大的挑戰。

    除了複雜性提高,性能調優技術其藝術性要大於科學性,還因爲大多數性能調優指南都側重於性能指標,有時晦澀難懂,也可能影響用戶體驗。本文嘗試把性能調優活動變成一種“科學”範疇內的行爲,提供了一種可重用的關注用戶體驗的方法,利用“等待點”(也就是應用中引起某請求等待的部分)分析應用架構。總之,基於等待的調優方法允許性能工程師們通過優化用戶體驗快速實現可度量的性能提高。

    性能調優過程

    在詳細介紹基於等待調優和等待點分析方法之前,本節首先對有效的性能調優過程做一個概述。性能調優可以簡單的概括爲四步:

   1、負載測試
   2、容器調優   
   3、應用調優 
   4、迭代

    像大多數計算機科學一樣,性能調優是一個迭代的過程。首先,創建一個合適的負載測試,其中包含了均衡的、具有代表性的服務請求,這都是容器調優實踐可以滿足的。隨着容器被不斷調優和測試壓力的增大,應用程序的瓶頸逐漸顯現出來。隨着應用的瓶頸被定位和解決,應用行爲會發生變化,這就要求容器再次調優。在容器和應用之間的迭代過程會一直進行到性能到達可以接受的條件(或者直到項目已經到期必須發佈時)。

    負載測試方法

    啓動一個性能調優實踐的先決條件是創建一整套合適的負載測試集合。每一個負載測試必須滿足以下兩點:

    ●  代表性,必須體現最終用戶的業務場景(或期望的場景)
    ●  均衡性,必須符合最終用戶不同行爲的比例分配

    也就是說,負載必須能夠按照最終用戶的實際操作比例來模擬用戶動作。爲了說明均衡最終用戶動作的重要性,請看下面這個例子:在保險索賠部門,員工執行以下操作:

    1、用戶上午八點登陸系統。
    2、上午每人平均處理五個索賠請求。
    3、大約80%的用戶忘記在吃飯之前註銷賬號,導致session過期。
    4、午飯後,用戶重新登錄系統。
    5、下午每人平均處理五個索賠申請。
    6、下班之前生成兩個報告。
    7、80%的用戶回家前註銷賬號。

    這個例子是一個真實應用的簡化版,但是它足夠在這些服務請求建立一個平衡。這個場景展現的均衡是:兩次登陸,十次索賠處理,兩次報告和一次註銷。

    如果負載生成器把壓力均勻分佈在不同的服務請求上又會怎麼樣呢?在本例中,用戶登陸和註銷功能會接收與處理與理賠請求相同的負載。如果是1000個併發用戶,登陸功能會很快崩潰,導致企業投資建立一個能夠處理這種負載的登陸組件,而實際上這種負載根本不會發生。更糟糕的是,本例中由於最大的瓶頸看似存在於登陸功能上,所以調優的努力會側重該功能,而忽視索賠處理功能。總之,一個非均衡的負載可能導致調優過程錯誤的關注於支持那些絕不會發生的負載的組件,而不是那些真正需要調優的部分!

    判斷一個負載對於應用是均衡的和代表性的標準,對於測試一個已存在的應用(或者一個新版本)還是一個全新的應用是不同的。


    已存在應用

    已存在應用跟一個全新應用相比,一個明顯的優點是:真實的用戶行爲可以在實際生產環境中觀察獲得。根據請求的本質和它們如何被應用定義,可以通過兩個選擇定義最終用戶行爲:

    ●  訪問日誌
    ●  最終用戶體驗監視器

    對於大多數基於web的應用來說,訪問日誌提供了足夠的資源分析服務請求的本質和它們的均衡關係。Web服務器可以配置成抓取最終用戶請求信息並存儲在一 個日誌文件中,稱之爲“訪問日誌”(因爲該文件通常命名爲“access.log”)。使用訪問日誌定位用戶行爲的關鍵是應用交互需要能夠通過不同的 URI來區分。例如,如果之前例子的動作採用類似“/login.do”、“/processClaim.do”、“/logout.do”的URI,那 麼我們可以非常容易的在訪問日誌中發現這些行爲並確定它們的比例。更進一步,通過最頻繁的URI來排序訪問日誌可以快速發現佔比例前n%的的若干請求,這 個“n”%應該在80%左右。

    訪問日誌是文本文件,可以手動檢查(不是一個很有效率的任務),可以通過編程解析,也可以通過工具來分析。對此有很多商業解決方案,不過Quest Software有一個產品Funnel Web Analyzer,雖然多年以前已經終止開發,但是由於其很受歡迎的命令,公司就作爲將其作爲自由軟件重新發布。Funnel Web Analyzer可以分析大多數訪問日誌並顯示用於創建合適負載測試的信息。

    一些應用不像上面提到的那樣簡單,其用戶交互無法很容易的通過一個URI來定位。例如,考慮一個包含前端控制器servlet的應用,該servlet接受一個XML有效信息——並且其業務處理邏輯就存在於該信息中。在本例中,需要另外的工具來偵測其有效信息以判斷其符合哪個業務場景。這可以通過使用 servlet過濾器或者一個稱爲最終用戶體驗監視器的硬件設備來完成。

    不管用戶行爲是如何獲得的,它都是開始任何性能調優實踐之前的關鍵先決條件。

    全新應用

    由於全新的應用沒有任何最終用戶行爲可以分析,所以對我們提出了一個獨特的挑戰。定位新應用的用戶行爲需要三個步驟,如圖1所示。

圖 1 評估新應用的最終用戶行爲

    第一步,評估最終用戶應該會做什麼。這步是“猜一猜”的正式說法,但應該是一個經過培訓的猜測過程。評估結果應該來自於以下雙方的討論:應用業務負責人和應用技術負責人。應用業務負責人,通常是一個產品經理,負責細化最終用戶應該如何使用該應用程序——例如,他可能報告說最終用戶會登陸、處理五個索賠請求、過期、處理五個索賠請求、生成兩個報告、然後註銷。應用技術負責人,一般是架構師或是技術lead,負責把業務交互的抽象列表翻譯成用於生成負載測試的技術步驟——例如,他可能報告說登陸通過“/login.do” URI完成而處理一個索賠請求需要五個URI.這些人(或者小組或者一些大型項目裏的委員會)應該一起提供足夠的信息來建立一個基準負載測試。

    我們建立負載測試,用其調整應用和容器,應用程序部署到生產環境中,這一切做完之後,調優工作並沒有結束。下一步是驗證負載測試集。這通常是一個多階段的活動:

    ●  冒煙測試驗證:在實際運行的一兩週之內驗證原先的評估值是否符合真正生產環境下最終用戶的行爲。這步驗證是爲了確認在評估過程中沒有明顯的錯誤。
    ●  生產驗證:一些應用需要用戶花時間才能形成統一的使用方式。這個適應的時間長短因應用而異,可能是一個月或者一個季度,不過一旦用戶的行爲穩定下來,就需要驗證最終用戶行爲是否與評估一致。
    ●  迴歸驗證:最好在應用的生產週期中階段性的驗證用戶行爲,以防止用戶行爲改變或者引入新的功能或工作流導致用戶行爲改變。

    最後一步,也是經常被忽視的一步,就是反思。根據實際用戶行爲來反思評估的精確性是很重要的,因爲只有通過反思,用戶行爲才能更便於理解,評估在以後的應用中才能得到提高。沒有反思,相同的錯誤會一犯再犯,最終會增加調優的工作量。


    基於等待的調優方法

    建好了負載測試,接下來就是決定把調優精力放在何處。大多數調優指南都會提到“性能比率”或者度量之間的關係。例如,某調優指南可能強調說緩存命中率應該達到80%或者更高,因此負載測試應用時調整緩存大小直到命中率達到80%.然後處理列表上的下一個度量值,但是不要忘記驗證調整新的參數不會影響之前已經調好的其他參數。

    這項工作不僅困難而且效率很低。例如,調整緩存命中率到80%可能是件好事,但是存在一些更重要的問題:

    ●  該應用如何依賴於緩存(與該緩存交互的請求比例是多少)?
    ●  這些請求對應用中的其他請求有多大影響力?
    ●  被緩存的條目的本質是什麼?它們真的需要緩存嗎?

    基於等待的調優方法提出了一個新的思想,即分析應用的業務交互和實現這些交互的底層結構,然後優化這些業務交互的處理。第一步是分析應用的架構以定位實現業務請求的相關技術。每一個技術代表一個“等待點”,或者說在應用的這個地方,請求可能需要等待一些事情才能繼續處理。例如,如果一個請求執行了一次數據庫查詢,則它必須從連接池中獲取一個數據庫連接—如果連接池裏沒有可用的連接,則該請求必須等待直到有連接可用。同樣,如果某請求調用了一個web服務,而那個web服務維護了一個請求隊列(對應一個線程池處理請求),這會潛在的導致請求等待直到一個處理線程可用。

    從以上稱之爲等待點分析的方法中,可以定位兩種類型的等待點:

    ●  基於層次的等待點
    ●  基於技術的等待點

    本節首先概述了基於等待點的架構分析方法,然後分別研究了不同類型的等待點。

    等待點架構分析

    從上面討論中得出的最重要的結論就是性能調優必須在應用架構的環境中執行。這也是調優性能比率爲何如此低效的一個原因:主觀的調整一個性能參數到一個最佳值,對應用可能是好事也可能是壞事——因爲這可能會也可能不會有利於最終用戶體驗。

    基於等待點的分析是一個分析應用中主要請求處理路徑的過程,藉此定位潛在影響該請求可能會等待的資源。在等待點分析實踐中最有效的辦法是定位並標出應用中核心處理路徑。這包括一個請求可能訪問的所有層次、請求交互的所有外部服務、被做成池的所有對象和全部緩存對象。

    基於層次的等待點

    一個請求穿過一個物理層,比如在web層和業務層之間切換,或者調用外部服務器,比如web服務,每當這種時候,都意味着伴隨着轉換,這裏存在一個隱式等待點。如果服務器在某一時刻只處理單個請求是沒有效率的,因此他們使用了多線程方法。典型的,一個服務器在某個socket端口監聽訪問的請求——每當收到一個請求它就會把請求放在隊列中,然後返回監聽其他請求。請求隊列對應着一個線程池,負責從隊列中提取請求並處理。圖2描述了對於三層結構(web服務器、動態web層和業務層)的執行過程。

圖 2 基於層次的等待點

    因爲請求穿越層次的動作會引起請求隊列,由相應的線程池處理,這種線程池也是一個潛在的等待點。每一個線程池的大小的調優必須基於以下考慮:

    ●  池必須足夠大使得訪問的請求無需不必要的等待一個線程。
    ●  池不能大到耗盡服務器。過多的線程會迫使服務器花費大量時間用於在不同的線程上下文中切換,真正處理請求的時間反而更少了。這種情況通常表現爲CPU使用率很高,但是處理請求的吞吐量卻降低了。
    ●  池的大小不能透支與之交互的後臺資源。例如,如果數據庫對於單個服務器只支持50個請求,那麼服務器不應該向數據庫發送超過50個數量的請求。

    服務器線程池的最佳尺寸的標準是:對受限制的依賴資源產生足夠的負載—最大化它們的使用率而不讓其透支。下面的“後退調優”一節有更多調整受限制資源池大小的內容。


    基於技術的等待點

    基於層次的等待點考慮的是在不同服務器之間傳遞請求,而基於技術的等待點關注的則是在單個服務器中如何通過有效地內部工作來傳遞請求。基於層次的調優,類似於IBM的隊列調優,只是調整應用的有效第一步,如果忽略了調優應用服務器的內部工作,則會對應用性能產生巨大的影響。這就類似於調整JDBC連接池以發送最佳數量的負載給數據庫,但是忽略了檢查執行的SQL語句——如果查詢需要連接十個表單,每個表單有一百萬個記錄,則最佳負載可能是兩個連接,但是如果我們優化了查詢,則數據庫可能支持二百個連接。深入研究應用服務器和應用使用的潛在技術,可能存在以下通用的基於技術的等待點:

    ●  池對象(比如無狀態session bean或者其他應用放入池的業務對象)
    ●  緩存設施
    ●  持久化存儲或外部依賴池
    ●  通訊基礎設施
    ●  垃圾收集

    大多數情況下,無狀態session bean池的大小被應用服務器優化,不會是一個明顯的等待點,除非池大小被手工錯誤的配置了。但是也存在一些池對象必須手動配置大小——這些可能成爲有效的等待點。當一個應用需要一個池化的資源,它必須從池裏獲取一個資源實例,使用它,然後釋放給池。如果池太小,所有的對象實例都在被使用,則請求不得不等待一個實例可用。顯而易見,等待一個池化的資源會增加響應時間,如果越來越多的請求被堵塞在等待池化資源,會導致明顯的性能下降。另一方面,如果池很大,它可能消耗過多內存,對總體JVM的性能產生差的影響。池的最佳大小需要權衡,只能在對池的利用情況做徹底的分析才能決定。

    池化對象是無狀態的,這意味着應用從池中得到哪個實例都無所謂——任何實例都行。另一方面,緩存的對象都是有狀態的,也就是說當應用從緩存中請求一個對象時,它的目標是一個特定對象。舉一個很粗糙的類比說明一下區別:考慮人們生活當中兩種常見的活動:超市購物和接孩子放學。在超市中,任何收銀員都可以接待每一位顧客,無論顧客選擇哪位收銀員都可以順利結賬。因此收銀員可以池化。但是在接孩子放學時,每一個父母只想要他們自己的孩子,別的孩子是不行的。因此孩子可以被緩存。

    如前面所述,緩存提出了一個新的調優挑戰。簡單說,緩存的目的是在本地內存中存儲對象,使應用可以隨時讀取它們,而不是在需要的時候才獲取他們。一個合適大小的緩存可以對通過遠程調用加載對象的行爲提供明顯的性能改善。但是,一個不合適大小的緩存,可能產生明顯的性能阻礙。因爲緩存維護有狀態的對象,所以重要的一點是在緩存中存儲最頻繁訪問的對象,同時保留額外的空間來處理非頻繁訪問對象。試想如果緩存太小,請求會怎樣:

    1、請求檢查緩存中是否存在某對象,結果失敗。
    2、請求需要查詢外部資源獲取對象數據。
    3、因爲緩存通常維護最頻繁訪問的數據,所以這個新對象需要添加到緩存中(它正在被訪問)。
    4、但是如果緩存滿了,必須利用“最少最近訪問”算法選擇一個對象移除。
    5、如果緩存對象的狀態與外部資源不一致,則緩存對象移除之前必須更新外部資源。
    6、新的對象此刻添加到緩存中。
    7、新的對象最終返回給請求。

    這是一個低效的過程,如果大多數請求都要執行這些步驟,那麼緩存肯定會降低性能。緩存必須調整到足夠大以最小化緩存的“不命中率”,一次不命中意味着需要執行前面提到的七個步驟,但是也不能太大導致佔用太多JVM內存。如果緩存需要非常非常大才能滿足性能需要,那麼最好是重新考慮被緩存對象的實質和它們到底是否值得緩存。

    類似對象池,外部資源池,比如數據庫連接池,也必須足夠大以滿足請求不會被迫等待池中的一個連接變爲可用狀態,但是也不能太大,導致應用浪費外部資源。“後退調優”一節討論瞭如何決定這些池的最佳大小,但是在本節的上下文中,牢記它們代表了一個明顯的等待點。

    調優通訊基礎設施遠遠超出了本文討論的範圍,因爲其具體實現因產品不同而存在明顯的區別,這包括諸如MSMQ、MQSeries、TIBCO等主流產品。但是請記住,如果一個應用與某消息服務器交互,它必須經過合適的調優或者它也代表了一個等待點。

    最後一個明顯影響JVM性能的等待點是垃圾收集。它不太適用本文中描述的等待點分析過程(檢查一個請求,定位導致該請求等待的技術),但是由於它可以對性能產生顯著的影響,所以把它列在這裏。不同的JVM實現和不同的垃圾收集策略決定了垃圾收集如何執行,但是在很多情況下,一次主垃圾收集(或者說標記—清除—壓縮垃圾收集)可能導致整個JVM暫停直到垃圾收集完成。一個顯著提高JVM性能的辦法就是優化垃圾收集。如果想了解更多垃圾收集的信息,請加入GeekCap討論應用基礎設施調優。


    後退調優

    現在關於基於層次的和基於技術的等待點的一切都介紹完了,最後一步就是優化每一個等待點的配置。這一步有時被稱爲“後退調優”,其思想非常簡單:

    1、開放所有基於層次的等待點和外部依賴池——也就是配置它們允許過多的負載經過服務器。
    2、根據應用生成均衡的和具有代表性的服務請求。
    3、定位首先透支的等待點,通常是外部依賴,比如數據庫。
    4、減小配置以控制等待點允許足夠的負載經過外部依賴而不透支。
    5、調整所有其他基於層次的等待點,發送足夠的負載經過服務器,最大化受限制的等待點,但是也不讓請求等待。
    6、允許所有其他請求在業務邏輯層之上等待,比如web服務器端。

    此處的原則就是應用應該發送一定數量的負載給它的外部依賴資源以最大化它們的使用率又不導致透支—並且所有其他等待點應該合理配置以發送足夠的負載給這些受限制的等待點。例如,如果數據庫對每一個應用服務器最多支持50個連接(例如,配置池容納40或45個連接)。接下來,如果80個線程產生40個數據庫連接,則應用的線程池應配置爲80.最後,web服務器在任意時刻應該發送不超過80個請求給每一個應用服務器。

    所有基於技術的等待點,比如對象池、緩存和垃圾回收,應該調整到最大化請求的吞吐量使得儘可能快的穿越服務器或者基於層析的等待點之間。

    總結

    性能調優曾經是“藝術性”多於“科學性”,但是通過結合抽象分析和嘗試併產生錯誤,基於等待的調優方法已經證明能夠使該過程更具科學性和更有效率。基於等待的調優首先執行一個應用架構的等待點分析,以此定位有可能導致請求等待的某個技術。等待點來自兩方面:基於層次的等待點,代表着跨越應用層次的轉換;基於技術的等待點,代表着可能提高或降低性能的技術,比如緩存、池和通訊基礎設施。一旦定位好了一系列等待點,調優過程就此開始:開放所有基於層次的等待點和外部依賴池,產生均衡的和具有代表性的負載,然後後退調優,收緊等待點以最大化該請求最薄弱的一環的性能,但是不要透支。基於等待的調優方法在生產環境中已經一次又一次得到了證明,不僅僅是高效的,而且允許性能工程師快速實現可度量的性能優化。

    關於作者

    Steven Haines是GeekCap公司的創立者和CEO,該公司致力於向全球的開發社區提供電子學習解決方案,尤其側重於諸如性能測試和性能調優這樣生僻的領域。除了電子學習方案,GeekCap還提供了免費的技術論壇、學習路線圖和其他資源以幫助開發人員發展自己的技術事業和學習新技術。GeekCap提供的服務包括:市場營銷資料,比如白皮書和技術概要;業務服務,比如市場分析和戰略定位。Steven的著作包括:許多白皮書、Java EE 5性能管和優化(Apress,2006),Java 2 Primer Plus (SAMS, 2002)和從零學習Java 2(QUE,1999)。他是InformIT.com的Java版主,同時也是InfoQ.com的Java社區編輯。平時他作爲一名承包商在Disney團隊工作,負責實現下一代Disney網站的架構。他之前在Quest Software公司工作了七年,職責是作爲Java領域專家設計性能監控和分析軟件。他先後在California大學Irvine分校和 Learning Tree大學接受過全面的Java開發培訓。您可以通過[email protected]聯繫他。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章