天天看點

Java性能調優實戰(二) | 如何制定性能調優政策

測試 - 分析 - 調優

性能測試攻略

性能測試是提前能發現性能瓶頸,保障系統性能穩定的必要措施。

1.微基準性能測試

微基準性能測試可以精确定位到某個子產品或者某個方法的性能問題,特别适合做一個功能子產品或者一個方法在不同實作方式下的性能對比。例如,對比一個方法使用同步實作和非同步實作的性能。

2.宏基準性能測試

宏基準性能測試是一個綜合測試,需要考慮到測試環境、測試場景和測試目标。

首先看測試環境,我們需要模拟線上的真實環境。

然後看測試場景。我們需要确定在測試某個接口時,是否有其他業務接口同時也在平行運作,造成幹擾。如果有,請重視,因為你一旦忽視了這種幹擾,測試結果就會出現偏差。

最後看測試目标。我們的性能測試是要有目标的,這裡可以通過吞吐量以及響應時間來衡量系統是否達标。不達标,就進行優化;達标就繼續加大測試的并發數,探底接口的 TPS(最大每秒事務處理量),這樣做,可以深入了解到接口的性能。除了測試接口的吞吐量和響應時間以外,我們還需要循環測試可能導緻性能問題的接口,觀察各個伺服器的 CPU 、記憶體以及 I/O 使用率的變化。

以上就是兩種測試方法的詳解。其中值得注意的是,性能測試存在幹擾因子,會使測試結果不準确。是以,我們在做性能測試時,還要注意一些問題。

1、熱身問題

當我們做性能測試時,我們的系統會運作得越來越快,後面的通路速度要比我們第一次通路的速度快上幾倍。這是怎麼回事呢?

在 Java 程式設計語言和環境中,.Java 檔案編譯成為 .class 檔案後,機器還是無法直接運作 .class 檔案中的位元組碼,需要通過解釋器将位元組碼轉換成本地機器碼才能運作。為了節約記憶體和執行效率,代碼最初被執行時,解釋器會率先執行這段代碼。

随着代碼被執行的次數增多,當虛拟機發現某個方法或代碼塊運作得特别頻繁時,就會把這些代碼認定為熱點代碼。為了提高熱點代碼的執行效率,在運作時,虛拟機将會通過即時編譯器,把這些代碼編譯成與本地平台相關的機器碼,并進行各層次的優化,然後儲存在記憶體中,之後每次運作代碼時,直接從記憶體彙總擷取即可。

是以在剛開始運作的階段,虛拟機會花費很長的時間來全面優化代碼,後面就能以最高性能執行了。

這就是熱身過程,如果在進行性能測試時,熱身時間過長,就會導緻第一次通路速度過慢,你就可以考慮先優化,再進行測試

2、性能測試結果不穩定

我們在做性能測試時發現,每次測試處理的資料集都是一樣的,但測試結果卻有差異。這是因為測試時,伴随着很多不穩定因為,比如機器其他程序的影響、網絡波動以及每個階段 JVM 垃圾回收的不同等等。

我們可以通過多次測試,将測試結果求平均,或者統計一個曲線圖,隻要保證我們的平均值是在合理範圍之内,而且波動不是很大,這種情況下,性能測試就是通過的。

3、多 JVM 情況下的影響

如果我們的伺服器有多個 Java 應用服務,部署在不同的 Tomcat 下,這就意味着我們的伺服器會有多個 JVM。任意一個 JVM 都擁有整個系統的資源使用權。如果一台機器上隻部署單獨的一個 JVM ,在做性能測試時,測試結果很好,或者你調優的效果很好,但在一台機器多個 JVM 的情況下就不一定了。是以我們應該盡量避免線上環境中一台機器部署多個 JVM 的情況。

合理分析結果,制定調優政策

我們在完成性能測試之後,需要輸出一份性能測試報告,幫我們分析系統性能測試的情況。其中測試結果需要包含測試接口的平均、最大和最小吞吐量,響應時間,伺服器的 CPU、記憶體、I/O、網絡 IO 使用率,JVM 的 GC 頻率等。

通過觀察這些調優标準,可以發現性能瓶頸,我們再通過自下而上的方式分析查找問題。首先從作業系統層面,檢視系統的 CPU、記憶體、I/O、網絡的使用率是否存在異常,再通過指令查找異常日志,最後通過分析日志,找到導緻瓶頸的原因;還可以從 Java 應用的 JVM 層面,檢視 JVM 的垃圾回收頻率以及記憶體配置設定情況是否存在異常,分析日志,找到導緻瓶頸的原因。

如果系統和 JVM 層面都沒有出現異常情況,我們可以檢視應用服務業務層是否存在性能瓶頸,例如 Java 程式設計的問題、讀寫資料瓶頸等等。

分析查找問題是一個複雜而又細緻的過程,某個性能問題可能是一個原因導緻的,也可能是幾個原因共同導緻的結果。我們分析查找問題可以采用自下而上的方式,而我們解決系統性能問題,則可以采用自上而下的方式逐級優化。下面我來介紹下從應用層到作業系統層的幾種調優政策。

1、優化代碼

應用層的問題代碼往往會因為耗盡系統資源而暴露出來。例如,我們某段代碼導緻記憶體溢出,往往是将 JVM 中的記憶體用完了,這個時候系統的記憶體資源消耗殆盡了,同時也會引發JVM 頻繁地發生垃圾回收,導緻 CPU 100% 以上居高不下,這個時候又消耗了系統的CPU 資源。

還有一些是非問題代碼導緻的性能問題,這種往往是比較難發現的,需要依靠我們的經驗來優化。例如,我們經常使用的 LinkedList 集合,如果使用 for 循環周遊該容器,将大大降低讀的效率,但這種效率的降低很難導緻系統性能參數異常。

這時有經驗的同學,就會改用 Iterator (疊代器)疊代循環該集合,這是因為 LinkedList是連結清單實作的,如果使用 for 循環擷取元素,在每次循環擷取元素時,都會去周遊一次List,這樣會降低讀的效率。

2、優化設計

面向對象有很多設計模式,可以幫助我們優化業務層以及中間件層的代碼設計。優化後,不僅可以精簡代碼,還能提高整體性能。例如,單例模式在頻繁調用建立對象的場景中,可以共享一個建立對象,這樣可以減少頻繁地建立和銷毀對象所帶來的性能消耗。

3、優化算法

好的算法可以幫助我們大大地提升系統性能。例如,在不同的場景中,使用合适的查找算法可以降低時間複雜度。

4、時間換空間

有時候系統對查詢時的速度并沒有很高的要求,反而對存儲空間要求苛刻,這個時候我們可以考慮用時間來換取空間。

例如,我在 03 講就會詳解的用 String 對象的 intern 方法,可以将重複率比較高的資料集存儲在常量池,重複使用一個相同的對象,這樣可以大大節省記憶體存儲空間。但由于常量池使用的是 HashMap 資料結構類型,如果我們存儲資料過多,查詢的性能就會下降。是以在這種對存儲容量要求比較苛刻,而對查詢速度不作要求的場景,我們就可以考慮用時間換空

5、空間換時間

這種方法是使用存儲空間來提升通路速度。現在很多系統都是使用的 MySQL 資料庫,較為常見的分表分庫是典型的使用空間換時間的案例。

因為 MySQL 單表在存儲千萬資料以上時,讀寫性能會明顯下降,這個時候我們需要将表資料通過某個字段 Hash 值或者其他方式分拆,系統查詢資料時,會根據條件的 Hash 值判斷找到對應的表,因為表資料量減小了,查詢性能也就提升了。

6、參數調優

以上都是業務層代碼的優化,除此之外,JVM、Web 容器以及作業系統的優化也是非常關鍵的。

根據自己的業務場景,合理地設定 JVM 的記憶體空間以及垃圾回收算法可以提升系統性能。例如,如果我們業務中會建立大量的大對象,我們可以通過設定,将這些大對象直接放進老年代。這樣可以減少年輕代頻繁發生小的垃圾回收(Minor GC),減少 CPU 占用時間,提升系統性能。

Web 容器線程池的設定以及 Linux 作業系統的核心參數設定不合理也有可能導緻系統性能瓶頸,根據自己的業務場景優化這兩部分,可以提升系統性能。

兜底政策,確定系統穩定性

第一,限流,對系統的入口設定最大通路限制。這裡可以參考性能測試中探底接口的 TPS。同時采取熔斷措施,友好地傳回沒有成功的請求。

第二,實作智能化橫向擴容。智能化橫向擴容可以保證當通路量超過某一個門檻值時,系統可以根據需求自動橫向新增服務。

第三,提前擴容。這種方法通常應用于高并發系統,例如,瞬時搶購業務系統。這是因為橫向擴容無法滿足大量發生在瞬間的請求,即使成功了,搶購也結束了。

目前很多公司使用 Docker 容器來部署應用服務。這是因為 Docker 容器是使用 Kubernetes 作為容器管理系統,而 Kubernetes 可以實作智能化橫向擴容和提前擴容Docker 服務。

總結

Java性能調優實戰(二) | 如何制定性能調優政策

繼續閱讀