Java性能優(yōu)化實戰(zhàn):談一談服務(wù)性能衡量指標(biāo)有哪些?
初衷:
隨著互聯(lián)網(wǎng)的發(fā)展,高可靠、高并發(fā)以及降本增效,已成為各大公司面臨的現(xiàn)實挑戰(zhàn),性能優(yōu)化需求愈發(fā)迫切,大到分布式系統(tǒng),小到代碼塊的算法優(yōu)化,都已經(jīng)成為你日常工作中必須要面對的事情。對于開發(fā)者而言,性能優(yōu)化也從加分項變?yōu)橐粋€熱門技能,缺乏相關(guān)知識將很難在面試或工作中脫穎而出。
該篇主要從理論分析入手來介紹性能優(yōu)化的衡量指標(biāo),及其理論方法和注意點。
指標(biāo)是我們衡量很多事物,以及做出行為決策的重要參考。例如在生活中,當(dāng)你打算買汽車時,會關(guān)注很多指標(biāo),比如動力性、燃油經(jīng)濟(jì)性、制動性、操縱穩(wěn)定性、平順性、通過性、排放與噪聲等,而這些指標(biāo)也都有相關(guān)的測試和參數(shù),同時也會對這些指標(biāo)進(jìn)行一一參考。
這個道理大家都懂,但一旦到了性能優(yōu)化上,卻往往因為缺乏理論依據(jù)而選擇了錯誤的優(yōu)化方向,陷入了盲猜的窘境。在衡量一項優(yōu)化是否能達(dá)到目的之時,不能僅靠感覺,它同樣有一系列的指標(biāo)來衡量你的改進(jìn)。如果在改動之后,性能不升反降,那就不能叫性能優(yōu)化了。
所謂性能,就是使用有限的資源在有限的時間內(nèi)完成工作。最主要的衡量因素就是時間,所以很多衡量指標(biāo),都可以把時間作為橫軸。
加載緩慢的網(wǎng)站,會受到搜索排名算法的懲罰,從而導(dǎo)致網(wǎng)站排名下降。 因此加載的快慢是性能優(yōu)化是否合理的一個非常直觀的判斷因素,但性能指標(biāo)不僅僅包括單次請求的速度,它還包含更多因素。
接下來看一下,都有哪些衡量指標(biāo)能夠幫我們進(jìn)行決策。
衡量指標(biāo)有哪些?
1. 吞吐量和響應(yīng)速度
分布式的高并發(fā)應(yīng)用并不能把單次請求作為判斷依據(jù),它往往是一個統(tǒng)計結(jié)果。其中最常用的衡量指標(biāo)就是吞吐量和響應(yīng)速度,而這兩者也是考慮性能時非常重要的概念。要理解這兩個指標(biāo)的意義,我們可以類比為交通環(huán)境中的十字路口。
在交通非常繁忙的情況下,十字路口是典型的瓶頸點,當(dāng)紅綠燈放行時間非常長時,后面往往會排起長隊。
從我們開車開始排隊,到車經(jīng)過紅綠燈,這個過程所花費(fèi)的時間,就是響應(yīng)時間。
當(dāng)然,我們可以適當(dāng)?shù)卣{(diào)低紅綠燈的間隔時間,這樣對于某些車輛來說,通過時間可能會短一些。但是,如果信號燈頻繁切換,反而會導(dǎo)致單位時間內(nèi)通過的車輛減少,換一個角度,我們也可以認(rèn)為這個十字路口的車輛吞吐量減少了。
像我們平常開發(fā)中經(jīng)常提到的,QPS 代表每秒查詢的數(shù)量,TPS 代表每秒事務(wù)的數(shù)量,HPS 代表每秒的 HTTP 請求數(shù)量等,這都是常用的與吞吐量相關(guān)的量化指標(biāo)。
在性能優(yōu)化的時候,我們要搞清楚優(yōu)化的目標(biāo),到底是吞吐量還是響應(yīng)速度。 有些時候,雖然響應(yīng)速度比較慢,但整個吞吐量卻非常高,比如一些數(shù)據(jù)庫的批量操作、一些緩沖區(qū)的合并等。雖然信息的延遲增加了,但如果我們的目標(biāo)就是吞吐量,那么這顯然也可以算是比較大的性能提升。
一般情況下,我們認(rèn)為:
- 響應(yīng)速度是串行執(zhí)行的優(yōu)化,通過優(yōu)化執(zhí)行步驟解決問題;
- 吞吐量是并行執(zhí)行的優(yōu)化,通過合理利用計算資源達(dá)到目標(biāo)。
我們平常的優(yōu)化主要側(cè)重于響應(yīng)速度,因為一旦響應(yīng)速度提升了,那么整個吞吐量自然也會跟著提升。
但對于高并發(fā)的互聯(lián)網(wǎng)應(yīng)用來說,響應(yīng)速度和吞吐量兩者都需要。這些應(yīng)用都會標(biāo)榜為高吞吐、高并發(fā)的場景,用戶對系統(tǒng)的延遲忍耐度很差,我們需要使用有限的硬件資源,從中找到一個平衡點。
2. 響應(yīng)時間衡量
既然響應(yīng)時間這么重要,我們就著重看一下響應(yīng)時間的衡量方法。
(1)平均響應(yīng)時間
我們最常用的指標(biāo),即平均響應(yīng)時間(AVG),該指標(biāo)能夠體現(xiàn)服務(wù)接口的平均處理能力。它的本質(zhì)是把所有的請求耗時加起來,然后除以請求的次數(shù)。舉個最簡單的例子,有 10 個請求,其中有 2 個 1ms、3 個 5ms、5 個 10ms,那么它的平均耗時就是(21+35+5*10)/10=6.7ms。
除非服務(wù)在一段時間內(nèi)出現(xiàn)了嚴(yán)重的問題,否則平均響應(yīng)時間都會比較平緩。因為高并發(fā)應(yīng)用請求量都特別大,所以長尾請求的影響會被很快平均,導(dǎo)致很多用戶的請求變慢,但這不能體現(xiàn)在平均耗時指標(biāo)中。
為了解決這個問題,另外一個比較常用的指標(biāo),就是百分位數(shù)(Percentile)。
(2)百分位數(shù)
這個也比較好理解。我們?nèi)Χㄒ粋€時間范圍,把每次請求的耗時加入一個列表中,然后按照從小到大的順序?qū)⑦@些時間進(jìn)行排序。這樣,我們?nèi)〕鎏囟ò俜治坏暮臅r,這個數(shù)字就是 TP 值。可以看到,TP 值(Top Percentile)和中位數(shù)、平均數(shù)等是類似的,都是一個統(tǒng)計學(xué)里的術(shù)語。
它的意義是,超過 N% 的請求都在 X 時間內(nèi)返回。比如 TP90 = 50ms,意思是超過 90th 的請求,都在 50ms 內(nèi)返回。
這個指標(biāo)也是非常重要的,它能夠反映出應(yīng)用接口的整體響應(yīng)情況。比如,某段時間若發(fā)生了長時間的 GC,那它的某個時間段之上的指標(biāo)就會產(chǎn)生嚴(yán)重的抖動,但一些低百分位的數(shù)值卻很少有變化。
我們一般分為 TP50、TP90、TP95、TP99、TP99.9 等多個段,對高百分位的值要求越高,對系統(tǒng)響應(yīng)能力的穩(wěn)定性要求越高。
在這些高穩(wěn)定性系統(tǒng)中,目標(biāo)就是要干掉嚴(yán)重影響系統(tǒng)的長尾請求。這部分接口性能數(shù)據(jù)的收集,我們會采用更加詳細(xì)的日志記錄方式,而不僅僅靠指標(biāo)。比如,我們將某個接口,耗時超過 1s 的入?yún)⒓皥?zhí)行步驟,詳細(xì)地輸出在日志系統(tǒng)中。
3. 并發(fā)量
并發(fā)量是指系統(tǒng)同時能處理的請求數(shù)量,這個指標(biāo)反映了系統(tǒng)的負(fù)載能力。
在高并發(fā)應(yīng)用中,僅僅高吞吐是不夠的,它還必須同時能為多個用戶提供服務(wù)。并發(fā)高時,會導(dǎo)致很嚴(yán)重的共享資源爭用問題,我們需要減少資源沖突,以及長時間占用資源的行為。
針對響應(yīng)時間進(jìn)行設(shè)計,一般來說是萬能的。因為響應(yīng)時間減少,同一時間能夠處理的請求必然會增加。值得注意的是,即使是一個秒殺系統(tǒng),經(jīng)過層層過濾處理,最終到達(dá)某個節(jié)點的并發(fā)數(shù),大概也就五六十左右。我們在平常的設(shè)計中,除非并發(fā)量特別低,否則都不需要太過度關(guān)注這個指標(biāo)。
4. 秒開率
在移動互聯(lián)網(wǎng)時代,尤其對于 App 中的頁面,秒開是一種極佳的用戶體驗。如果能在 1 秒內(nèi)加載完成頁面,那用戶可以獲得流暢的體驗,并且不會產(chǎn)生更多的焦慮感。
通常而言,可以根據(jù)業(yè)務(wù)情況設(shè)定不同的頁面打開標(biāo)準(zhǔn),比如低于 1 秒內(nèi)的數(shù)據(jù)占比是秒開率。業(yè)界優(yōu)秀的公司,比如手淘,其頁面的秒開率基本可達(dá)到 80% 以上。
5. 正確性
說一個比較有意思的事情。我們有個技術(shù)團(tuán)隊,在進(jìn)行測試的時候,發(fā)現(xiàn)接口響應(yīng)非常流暢,把并發(fā)數(shù)增加到 20 以后,應(yīng)用接口響應(yīng)依舊非常迅速。
但等應(yīng)用真正上線時,卻發(fā)生了重大事故,這是因為接口返回的都是無法使用的數(shù)據(jù)。
其問題原因也比較好定位,就是項目中使用了熔斷。在壓測的時候,接口直接超出服務(wù)能力,觸發(fā)熔斷了,但是壓測并沒有對接口響應(yīng)的正確性做判斷,造成了非常低級的錯誤。
所以在進(jìn)行性能評估的時候,不要忘記正確性這一關(guān)鍵要素。
有哪些理論方法?
性能優(yōu)化有很多理論方法,比如木桶理論、基礎(chǔ)測試、Amdahl 定律等。下面我們簡單地講解一下最常用的兩個理論。
1. 木桶理論
一只木桶若想要裝最多的水,則需要每塊木板都一樣長而且沒有破損才行。如果有一塊木板不滿足條件,那么這只桶就無法裝最多的水。
能夠裝多少水,取決于最短的那塊木板,而不是最長的那一塊。
木桶效應(yīng)在解釋系統(tǒng)性能上,也非常適合。組成系統(tǒng)的組件,在速度上是良莠不齊的。系統(tǒng)的整體性能,就取決于系統(tǒng)中最慢的組件。
比如,在數(shù)據(jù)庫應(yīng)用中,制約性能最嚴(yán)重的是落盤的 I/O 問題,也就是說,硬盤是這個場景下的短板,我們首要的任務(wù)就是補(bǔ)齊這個短板。
2. 基準(zhǔn)測試、預(yù)熱
基準(zhǔn)測試(Benchmark)并不是簡單的性能測試,是用來測試某個程序的最佳性能。
應(yīng)用接口往往在剛啟動后都有短暫的超時。在測試之前,我們需要對應(yīng)用進(jìn)行預(yù)熱,消除 JIT 編譯器等因素的影響。而在 Java 里就有一個組件,即 JMH,就可以消除這些差異。
注意點
1. 依據(jù)數(shù)字而不是猜想
有些同學(xué)對編程有很好的感覺,能夠靠猜測列出系統(tǒng)的瓶頸點,這種情況固然存在,但卻非常不可取。復(fù)雜的系統(tǒng)往往有多個影響因素,我們應(yīng)將性能分析放在第一位,把性能優(yōu)化放在次要位置,直覺只是我們的輔助,但不能作為下結(jié)論的工具。
進(jìn)行性能優(yōu)化時,我們一般會把分析后的結(jié)果排一個優(yōu)先級(根據(jù)難度和影響程度),從大處著手,首先擊破影響最大的點,然后將其他影響因素逐一擊破。
有些優(yōu)化會引入新的性能問題,有時候這些新問題會引起更嚴(yán)重的性能下降,你需要評估這個連鎖反應(yīng),確保這種優(yōu)化確實需要,同時需要使用數(shù)字去衡量這個過程,而不是靠感覺猜想。
2. 個體數(shù)據(jù)不足信
你是否有這樣的經(jīng)歷:某個知名網(wǎng)站的訪問速度真慢,光加載就花費(fèi)了 x 秒。其實,僅憑一個人的一次請求,就下了“慢”這個結(jié)論,是不合適的,而在我們進(jìn)行性能評估的時候,也往往會陷入這樣的誤區(qū)。
這是因為個體請求的小批量數(shù)據(jù),可參考價值并不是非常大。響應(yīng)時間可能因用戶的數(shù)據(jù)而異,也可能取決于設(shè)備和網(wǎng)絡(luò)條件。
合理的做法,是從統(tǒng)計數(shù)據(jù)中找到一些規(guī)律,比如上面所提到的平均響應(yīng)時間、TP 值等,甚至是響應(yīng)時間分布的直方圖,這些都能夠幫我們評估性能質(zhì)量。
3. 不要過早優(yōu)化和過度優(yōu)化
雖然性能優(yōu)化有這么多好處,但并不代表我們要把每個地方都做到極致,性能優(yōu)化也是要有限度的。程序要運(yùn)行地正確,要比程序運(yùn)行得更快還要困難。
計算機(jī)科學(xué)的鼻祖"Donald Knuth" 曾說:“過早的優(yōu)化是萬惡之源”,就是這個道理。
如果一項改進(jìn)并不能產(chǎn)生明顯的價值,那我們?yōu)槭裁催€要花大力氣耗在上面呢?比如,某個應(yīng)用已經(jīng)滿足了用戶的吞吐量需求和響應(yīng)需求,但有的同學(xué)熱衷于 JVM 的調(diào)優(yōu),依然花很大力氣在參數(shù)測試上,這種優(yōu)化就屬于過度優(yōu)化。
時間要花在刀刃上,我們需要找到最迫切需要解決的性能點,然后將其擊破。比如,一個系統(tǒng)主要是慢在了數(shù)據(jù)庫查詢上,結(jié)果你卻花了很大的精力去優(yōu)化 Java 編碼規(guī)范,這就是偏離目標(biāo)的典型情況。
一般地,性能優(yōu)化后的代碼,由于太過于追求執(zhí)行速度,讀起來都比較晦澀,在結(jié)構(gòu)上也會有很多讓步。很顯然,過早優(yōu)化會讓這種難以維護(hù)的特性過早介入到你的項目中,等代碼重構(gòu)的時候,就會花更大的力氣去解決它。
正確的做法是,項目開發(fā)和性能優(yōu)化,應(yīng)該作為兩個獨(dú)立的步驟進(jìn)行,要做性能優(yōu)化,要等到整個項目的架構(gòu)和功能大體進(jìn)入穩(wěn)定狀態(tài)時再進(jìn)行。
4. 保持良好的編碼習(xí)慣
我們上面提到,不要過早地優(yōu)化和過度優(yōu)化,但并不代表大家在編碼時就不考慮這些問題。
比如,保持好的編碼規(guī)范,就可以非常方便地進(jìn)行代碼重構(gòu);使用合適的設(shè)計模式,合理的劃分模塊,就可以針對性能問題和結(jié)構(gòu)問題進(jìn)行聚焦、優(yōu)化。
在追求高性能、高質(zhì)量編碼的過程中,一些好的習(xí)慣都會積累下來,形成人生道路上優(yōu)秀的修養(yǎng)和品質(zhì),這對我們是大有裨益的。
小結(jié)
我們簡單地了解了衡量性能的一些指標(biāo),比如常見的吞吐量和響應(yīng)速度,還探討了一些其他的影響因素,比如并發(fā)量、秒開率、容錯率等。
同時,我們也談到了木桶理論和基準(zhǔn)測試等兩種過程方法,并對性能測試中的一些誤區(qū)和注意點進(jìn)行了介紹,現(xiàn)在你應(yīng)該對如何描述性能有了更好的理解。像一些專業(yè)的性能測試軟件,如 JMeter、LoadRunner 等,就是在這些基礎(chǔ)性能指標(biāo)上進(jìn)行的擴(kuò)展。我們在平常的工作中,也應(yīng)該盡量使用專業(yè)術(shù)語,這樣才能對系統(tǒng)性能進(jìn)行正確評估。
了解了優(yōu)化指標(biāo)后,有了行動導(dǎo)向,那接下來該從哪些方面入手呢? Java 性能優(yōu)化是否有可以遵循的規(guī)律呢?