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