戰(zhàn):從需求分析到瓶頸調(diào)優(yōu))
1. 需求分析與性能測試的整體設(shè)計(jì)1.1 為什么性能測試必須先咬透需求很多人一上來就打開壓測工具先把接口地址填進(jìn)去再說。我見過太多這種場景最后測出來的結(jié)果除了給自己看根本沒法說服任何人。性能測試的第一步從來不是搭腳本而是把需求文檔翻爛把每個(gè)指標(biāo)摳清楚。做性能測試的真正目的不是“測出一個(gè)TPS數(shù)字”而是回答三個(gè)問題系統(tǒng)現(xiàn)在能扛多少、瓶頸在哪里、距離目標(biāo)還差多少。這三個(gè)問題都繞不開需求分析。拿最常見的電商下單流程舉例。業(yè)務(wù)方說“系統(tǒng)至少要支持一萬用戶同時(shí)在線”這句話看起來很簡單但如果不拆解你根本不知道該拿什么去壓。一萬用戶同時(shí)在線不代表一萬用戶同時(shí)下單。真實(shí)場景可能只有百分之幾的人在下單大部分人在瀏覽。真正需要關(guān)注的并發(fā)下單量可能只有幾百遠(yuǎn)沒有想象中那么大。需求分析階段必須跟產(chǎn)品、開發(fā)、運(yùn)維反復(fù)對齊的核心點(diǎn)包括業(yè)務(wù)量級日活、峰值在線、核心操作頻次這些數(shù)字從哪來是否經(jīng)過業(yè)務(wù)側(cè)確認(rèn)時(shí)間分布是否存在明顯的波峰波谷比如電商大促、節(jié)假日搶購、月底結(jié)算操作比例讀操作和寫操作的比例瀏覽、搜索、提交訂單分別占多少數(shù)據(jù)規(guī)模數(shù)據(jù)庫里存量數(shù)據(jù)多少壓測時(shí)用多少因?yàn)閿?shù)據(jù)量級直接影響查詢性能網(wǎng)絡(luò)環(huán)境用戶是走內(nèi)網(wǎng)還是公網(wǎng)移動(dòng)端占比多少有沒有弱網(wǎng)場景要求這些信息如果需求文檔里沒有就主動(dòng)去找產(chǎn)品和對口同事確認(rèn)。問一次不清楚就問兩次千萬不要自己想當(dāng)然。我踩過最大的坑之一就是按照自己的猜測去定指標(biāo)結(jié)果壓測報(bào)告出來以后業(yè)務(wù)方根本不認(rèn)說這不是他們的真實(shí)場景最后整個(gè)測試周期被拉長了一倍。1.2 指標(biāo)定級與目標(biāo)值的確定邏輯需求對齊之后就要把指標(biāo)目標(biāo)值定下來。這一步做不好后面所有工作都白搭。性能指標(biāo)一般分為三類對應(yīng)的標(biāo)準(zhǔn)完全不同核心指標(biāo)如下單成功率、支付成功率這類指標(biāo)不達(dá)標(biāo)就是事故必須嚴(yán)格卡住關(guān)鍵指標(biāo)如響應(yīng)時(shí)間、吞吐量直接影響用戶體驗(yàn)需要按業(yè)務(wù)場景分級設(shè)定參考指標(biāo)如服務(wù)器CPU使用率、數(shù)據(jù)庫連接數(shù)主要用于定位瓶頸輔助判斷目標(biāo)值的確定不能拍腦袋。最穩(wěn)妥的方式是看歷史數(shù)據(jù)如果系統(tǒng)已經(jīng)上線過一段時(shí)間線上監(jiān)控里積累的指標(biāo)數(shù)據(jù)就是最好的基準(zhǔn)。比如線上TPS日常在500左右大促時(shí)沖到2000那么壓測目標(biāo)就可以定為正常情況1.5倍峰值情況按2到2.5倍去壓。沒有歷史數(shù)據(jù)的新系統(tǒng)可以按業(yè)務(wù)預(yù)估量推算。這里給一個(gè)常用的簡化計(jì)算方法假設(shè)預(yù)估日訂單量是10萬筆核心高峰期集中在2小時(shí)內(nèi)則平均TPS 100000 / (2 × 3600) ≈ 13.9。再考慮峰值系數(shù)至少2倍目標(biāo)TPS可以定為28到30左右。如果是秒殺或搶購類場景峰值系數(shù)要到5到10倍那就是70到140的TPS。這里要特別注意TPS目標(biāo)不是一個(gè)人拍腦袋定的最終一定要以評審的形式跟各方確認(rèn)。把計(jì)算過程、數(shù)據(jù)來源、假設(shè)條件都寫清楚讓大家在評審會(huì)上確認(rèn)簽字后面即便出了問題你也有依據(jù)可查。這是保護(hù)自己也是讓測試工作更專業(yè)的方式。1.3 場景建模從業(yè)務(wù)鏈路到壓測模型需求分析完成后最關(guān)鍵的一步就是把真實(shí)業(yè)務(wù)轉(zhuǎn)化為可執(zhí)行的壓測場景。這一步直接決定了壓測結(jié)果能不能代表生產(chǎn)環(huán)境表現(xiàn)。場景建模的核心有兩個(gè)業(yè)務(wù)模型和請求模型。業(yè)務(wù)模型是什么樣的就是搞清楚一段完整業(yè)務(wù)流程里每個(gè)操作步驟的占比和順序。拿下單場景來說真實(shí)用戶的行為路徑是瀏覽商品、搜索、加入購物車、確認(rèn)訂單、提交訂單、支付。每一步都有響應(yīng)時(shí)間、成功率和并發(fā)量也都有各自的調(diào)用鏈和資源消耗。如果把壓力全部打在提交訂單一個(gè)接口上測出來的結(jié)果跟真實(shí)情況差之千里。在做業(yè)務(wù)建模時(shí)我常用的一種方法是把業(yè)務(wù)操作按“頻率×資源消耗”劃分成三類類型特點(diǎn)典型操作壓測占比高頻低耗調(diào)用頻繁、單個(gè)消耗小瀏覽、搜索、查看詳情60%-70%中頻中耗頻率適中、消耗中等添加購物車、確認(rèn)訂單20%-30%低頻高耗頻率低、資源消耗大提交訂單、支付回調(diào)5%-10%請求模型則是把業(yè)務(wù)模型翻譯成壓測腳本。這里必須考慮到具體參數(shù)怎么構(gòu)造。比如用戶ID不能全用同一個(gè)否則數(shù)據(jù)庫索引、緩存命中率都會(huì)被污染結(jié)果失真商品庫存數(shù)據(jù)要顧及扣減邏輯和超賣問題訂單號要有唯一性約束否則數(shù)據(jù)庫主鍵沖突導(dǎo)致報(bào)錯(cuò)。一個(gè)容易忽略的痛點(diǎn)是壓測過程中的參數(shù)化相關(guān)性處理。比如下單成功后會(huì)生成一個(gè)訂單ID這個(gè)ID要傳給支付接口使用。很多人寫腳本的時(shí)候只盯著下單接口沒有做實(shí)時(shí)的參數(shù)傳遞和關(guān)聯(lián)提取導(dǎo)致壓測到支付階段時(shí)不斷報(bào)錯(cuò)還以為是系統(tǒng)問題。這一類問題在場景建模時(shí)就要提前設(shè)計(jì)好關(guān)聯(lián)規(guī)則而不是等腳本跑不通了再修。1.4 測試環(huán)境與數(shù)據(jù)準(zhǔn)備決定結(jié)果可信度的地基場景模型定好之后接下來要做的是環(huán)境準(zhǔn)備和數(shù)據(jù)準(zhǔn)備。這個(gè)環(huán)節(jié)看著瑣碎實(shí)際上是決定性能測試結(jié)果可信度的關(guān)鍵。首先明確一個(gè)原則性能測試環(huán)境盡可能貼近生產(chǎn)環(huán)境。但這種貼近不是絕對等價(jià)。生產(chǎn)環(huán)境的機(jī)器配置、網(wǎng)絡(luò)拓?fù)洹⒅虚g件版本、數(shù)據(jù)庫數(shù)據(jù)量這些都應(yīng)該盡量一致因?yàn)镃PU核數(shù)不同、內(nèi)存不同、帶寬不同都會(huì)直接影響性能指標(biāo)。能做成跟生產(chǎn)1:1鏡像當(dāng)然最好但在很多公司里這是不現(xiàn)實(shí)的這時(shí)候就必須做好“等比縮放”的規(guī)劃并且把縮放關(guān)系在報(bào)告里寫清楚。比如生產(chǎn)環(huán)境是4臺應(yīng)用服務(wù)器壓測環(huán)境只能給2臺那你測出來的結(jié)果要按這個(gè)差異進(jìn)行說明不能直接拿壓測環(huán)境的絕對TPS去對標(biāo)生產(chǎn)目標(biāo)。數(shù)據(jù)準(zhǔn)備這一塊是很多人翻車的地方。壓測的數(shù)據(jù)量如果遠(yuǎn)小于生產(chǎn)環(huán)境數(shù)據(jù)庫的SQL執(zhí)行計(jì)劃就會(huì)不一樣。生產(chǎn)環(huán)境千萬級數(shù)據(jù)的表查詢走的是索引而你壓測環(huán)境只有十萬條數(shù)據(jù)優(yōu)化器可能直接走全表掃描查出來的響應(yīng)時(shí)間快得離譜。這個(gè)結(jié)果不但不具有參考性反而會(huì)誤導(dǎo)判斷。數(shù)據(jù)準(zhǔn)備至少要做到幾點(diǎn)核心表的數(shù)據(jù)量級與生產(chǎn)一致尤其是訂單表、用戶表、庫存表這些熱點(diǎn)表數(shù)據(jù)分布要均勻比如用戶ID不要連續(xù)集中在某段區(qū)間避免數(shù)據(jù)庫頁緩存命中率虛高造數(shù)要覆蓋邊界情況比如庫存為零的商品、超時(shí)未支付的訂單、狀態(tài)異常的記錄壓測產(chǎn)生的臟數(shù)據(jù)要清理否則多輪壓測的數(shù)據(jù)會(huì)互相干擾有些公司為了省事直接用生產(chǎn)環(huán)境備份的數(shù)據(jù)脫敏后灌入壓測環(huán)境這是很好的做法。如果做不到就需要自己寫數(shù)據(jù)生成腳本或者用開源的數(shù)據(jù)構(gòu)造工具但無論如何都不能用少量數(shù)據(jù)硬湊。我在早期做測試時(shí)就曾經(jīng)因?yàn)閿?shù)據(jù)量不足測出來的下單接口平均響應(yīng)時(shí)間只有50毫秒結(jié)果上了生產(chǎn)之后實(shí)際響應(yīng)時(shí)間要300毫秒被業(yè)務(wù)方直接質(zhì)疑測試專業(yè)性。后來把數(shù)據(jù)量補(bǔ)齊之后才真正找到了問題所在。2. 壓測工具選型與腳本開發(fā)的核心細(xì)節(jié)2.1 工具選型開源的夠用商業(yè)的看需要壓測工具的選擇會(huì)直接影響整個(gè)測試效率和結(jié)果的可信度。工具不在多在于合適。我用過的工具不算少從早期的開源工具到商業(yè)方案都有接觸各自適用場景差別很大。開源工具里JMeter是我最常用的選擇。主要原因是它幾乎覆蓋了所有我能想到的協(xié)議場景HTTP、HTTPS、WebService、JDBC、JMS、TCP甚至Dubbo這類RPC協(xié)議也能通過自定義插件支持。社區(qū)生態(tài)很成熟遇到問題搜一下基本都有答案。對于絕大多數(shù)公司來說JMeter加上配套的監(jiān)控插件已經(jīng)能支撐80%以上的性能測試場景。Locust的高并發(fā)模擬能力和Python腳本編寫方式在某些場景下非常順手但它的斷言和報(bào)告能力相對輕量。Gatling的Scala DSL寫起來優(yōu)雅性能表現(xiàn)和報(bào)告也很漂亮但學(xué)習(xí)門檻高一些。商業(yè)工具像LoadRunner和NeoLoad功能強(qiáng)大從網(wǎng)絡(luò)協(xié)議層面能做到更精細(xì)的模擬但價(jià)格不低。我個(gè)人的建議是團(tuán)隊(duì)里至少有一位熟練使用JMeter的成員。因?yàn)殚_源性讓你沒有授權(quán)限制社區(qū)也足夠強(qiáng)大遇到問題不必等待廠商支持。如果公司預(yù)算充足也可以通過商業(yè)工具獲取更強(qiáng)的網(wǎng)絡(luò)級模擬和專業(yè)支持。但無論選哪種都要基于實(shí)際場景模型來選擇量級大、場景復(fù)雜的用功能強(qiáng)的簡單校驗(yàn)型的用輕量的即可不要過度設(shè)計(jì)。2.2 腳本開發(fā)的規(guī)范與要點(diǎn)不是能跑就行腳本開發(fā)是很多人忽視但實(shí)際非常重要的環(huán)節(jié)。因?yàn)槟_本質(zhì)量直接影響測試結(jié)果的真實(shí)度而很多問題恰恰藏在腳本里。腳本開發(fā)必須遵循的幾個(gè)原則我逐一展開。第一參數(shù)化一定要做徹底。常用的參數(shù)包括用戶ID、商品ID、訂單號、手機(jī)號這些參數(shù)如果寫死壓測根本測不出真實(shí)效果。拿用戶ID來說如果幾百個(gè)并發(fā)全部用同一個(gè)用戶ID則數(shù)據(jù)庫連接池的會(huì)話可能被復(fù)用緩存也會(huì)集中命中同一份數(shù)據(jù)導(dǎo)致響應(yīng)時(shí)間虛低。參數(shù)化方式有很多CSV文件、數(shù)據(jù)庫讀取、函數(shù)生成均可但根據(jù)業(yè)務(wù)規(guī)則選擇適當(dāng)?shù)姆绞讲灰还赡X全用隨機(jī)函數(shù)。第二關(guān)聯(lián)關(guān)系必須處理好。業(yè)務(wù)鏈路中存在大量上下游依賴前面接口返回的訂單ID、Token、交易流水號要能自動(dòng)化提取并傳給后續(xù)接口。這個(gè)做不好壓測跑到后面全是報(bào)錯(cuò)根本壓不到真實(shí)業(yè)務(wù)邏輯。第三斷言要合理。壓測腳本中如果不加斷言接口返回500也算成功那TPS再高也沒意義。但斷言也不能過度復(fù)雜否則會(huì)消耗額外資源。通常的做法是校驗(yàn)Http狀態(tài)碼為200同時(shí)校驗(yàn)核心業(yè)務(wù)字段比如返回碼為0、或包含成功標(biāo)識。既不過度也不缺失。第四腳本的“思考時(shí)間”要精心設(shè)計(jì)。真實(shí)用戶從“瀏覽商品”到“點(diǎn)擊下單”之間是有間隔的這個(gè)間隔就是思考時(shí)間。完全沒有思考時(shí)間的壓測是極限壓力測試不是業(yè)務(wù)真實(shí)性測試兩者目標(biāo)完全不同。在做容量評估和容量規(guī)劃時(shí)往往需要的是結(jié)合思考時(shí)間的混合場景模型極限壓測一般用于評估系統(tǒng)底線。我自己在腳本開發(fā)中都會(huì)做一層規(guī)范化封裝。通用函數(shù)提取到公共模塊參數(shù)文件統(tǒng)一管理腳本結(jié)構(gòu)按業(yè)務(wù)模塊分層。前期可能覺得這是多余功夫但一旦壓測場景多起來規(guī)范的腳本維護(hù)成本遠(yuǎn)低于一鍋粥式的腳本。2.3 執(zhí)行策略壓測節(jié)奏不是一把梭腳本準(zhǔn)備好之后真正進(jìn)入到壓測執(zhí)行階段節(jié)奏控制尤為關(guān)鍵。壓測執(zhí)行要分階段走不要上來就全量并發(fā)猛打。道理很簡單就像人跑馬拉松要先熱身再加速直接沖刺會(huì)導(dǎo)致數(shù)據(jù)失真也容易給系統(tǒng)造成短暫沖擊把故障誤判為容量不足。我常用的執(zhí)行策略是分層加壓單接口基準(zhǔn)測試先對單個(gè)接口單線程持續(xù)壓測摸清基礎(chǔ)響應(yīng)時(shí)間和成功率這一步通常跑5分鐘單接口梯度加壓從50并發(fā)開始逐步加100、200、400、800每個(gè)梯度保持一段時(shí)間觀察各項(xiàng)指標(biāo)變化趨勢混合場景壓測按業(yè)務(wù)模型比例同時(shí)壓測多個(gè)接口模擬真實(shí)用戶行為鏈路穩(wěn)定性測試在70%目標(biāo)負(fù)載下持續(xù)運(yùn)行至少30分鐘觀察是否存在內(nèi)存泄漏、連接池耗盡等問題峰值與突發(fā)測試模擬瞬時(shí)流量沖擊比如秒殺場景下的5分鐘集中請求每一階段之間要保留觀察窗口讓系統(tǒng)恢復(fù)穩(wěn)定狀態(tài)再進(jìn)入下一輪。切忌一個(gè)場景跑完馬上跑下一個(gè)中間不清理數(shù)據(jù)、不檢查系統(tǒng)狀態(tài)那樣得到的指標(biāo)是混在一起的很難定位問題。執(zhí)行過程中監(jiān)控是同時(shí)進(jìn)行的不只是看壓測端的TPS和響應(yīng)時(shí)間還要同步看服務(wù)端指標(biāo)CPU、內(nèi)存、磁盤I/O、網(wǎng)絡(luò)帶寬、GC頻率、數(shù)據(jù)庫連接池狀態(tài)、慢查詢數(shù)。壓測端數(shù)據(jù)和服務(wù)端數(shù)據(jù)只有放在一起看才能形成完整證據(jù)鏈。舉個(gè)例子如果TPS上不去但壓測端響應(yīng)時(shí)間不斷增加同時(shí)數(shù)據(jù)庫慢查詢也在增長那問題大概率在SQL或者索引層面而不在應(yīng)用服務(wù)器。2.4 全鏈路壓測與容量規(guī)劃的一些經(jīng)驗(yàn)近些年的趨勢是做全鏈路壓測也就是從網(wǎng)關(guān)到應(yīng)用、到中間件、再到數(shù)據(jù)庫整個(gè)鏈路全部納入壓測范圍。這種方式的優(yōu)勢是能發(fā)現(xiàn)在模塊層面聯(lián)調(diào)時(shí)才暴露的問題比如某些環(huán)節(jié)的超時(shí)配置不一致、連接池大小不匹配。但全鏈路壓測對環(huán)境和數(shù)據(jù)隔離的要求非常高。壓測流量如果不加區(qū)分地打入生產(chǎn)環(huán)境會(huì)造成臟數(shù)據(jù)落地。常見的做法是在請求入口打上壓測標(biāo)識業(yè)務(wù)代碼在一些關(guān)鍵環(huán)節(jié)識別到標(biāo)識后會(huì)走獨(dú)立的影子庫或影子表壓測數(shù)據(jù)不污染真實(shí)業(yè)務(wù)數(shù)據(jù)。在容量規(guī)劃方面我對團(tuán)隊(duì)的建議是至少保存三輪有效壓測數(shù)據(jù)再做趨勢評估。第一輪是基線第二輪是優(yōu)化后對比第三輪是小幅加壓的邊際效應(yīng)分析。不同輪次的測試結(jié)果要放到同一個(gè)維度下比較比如都統(tǒng)一在相同并發(fā)數(shù)、相同數(shù)據(jù)量下做否則數(shù)據(jù)之間沒有可比性容量規(guī)劃也就無從談起。3. 性能問題定位與調(diào)優(yōu)的完整路徑3.1 瓶頸分析的正確打開方式壓測做完之后真正的重頭戲才剛開始。壓測結(jié)果不理想是常態(tài)可關(guān)鍵是你得能從一堆數(shù)據(jù)指標(biāo)里揪出真正的瓶頸所在。這環(huán)節(jié)極其考驗(yàn)打印經(jīng)驗(yàn)也是最容易走彎路的地方。我的習(xí)慣方法是自上而下逐層排查。先看應(yīng)用層再往下看中間件最后看數(shù)據(jù)庫和操作系統(tǒng)。為什么這樣排因?yàn)樾阅軉栴}的表象通常出現(xiàn)在上層比如用戶側(cè)響應(yīng)慢但根因往往在下層比如數(shù)據(jù)庫索引失效或者連接池阻塞。如果一上來就去翻數(shù)據(jù)庫慢查詢而忽略了應(yīng)用線程阻塞的問題就會(huì)白白浪費(fèi)大量時(shí)間。應(yīng)用層的排查重點(diǎn)是線程狀態(tài)和GC情況。用線程Dump看是否有大量線程處于BLOCKED或WAITING狀態(tài)用GC日志看GC頻率和停頓時(shí)間。如果一次性Full GC的停頓時(shí)間超過幾百毫秒接口響應(yīng)自然會(huì)變慢TPS也會(huì)受到明顯影響。中間件層主要看的是連接池、緩存和隊(duì)列。數(shù)據(jù)庫連接池是否被打滿Redis響應(yīng)時(shí)間是否異常消息隊(duì)列的積壓量有沒有持續(xù)上升這些往往是整套系統(tǒng)中的水桶短板。數(shù)據(jù)庫層的排查是最常見的瓶頸所在。慢查詢?nèi)罩臼鞘滓P(guān)注點(diǎn)??吹揭粭l查詢耗時(shí)兩秒以上就要去看執(zhí)行計(jì)劃確認(rèn)是否走索引、有沒有全表掃描、預(yù)估掃描行數(shù)是多少。操作系統(tǒng)層面主要關(guān)注的是資源水位。比如通過監(jiān)控看到CPU使用率已達(dá)90%以上就可以判斷CPU是瓶頸如果內(nèi)存大量交換到Swap就要考慮內(nèi)存容量問題磁盤I/O等待時(shí)間過長則要重點(diǎn)關(guān)注磁盤性能尤其是數(shù)據(jù)庫所在盤。這種逐層排查的方式能讓你快速圈定大方向然后逐步聚焦到具體問題的代碼層面。不要試圖一次性分析完所有數(shù)據(jù)那是浪費(fèi)時(shí)間抓住主要矛盾先解決最嚴(yán)重的那個(gè)問題往往整個(gè)系統(tǒng)的性能就能獲得顯著提升。3.2 常見的性能瓶頸類型與典型調(diào)優(yōu)手段性能問題雖然表象多樣但歸結(jié)起來不外乎幾種類型。把每種類型對應(yīng)的典型場景和調(diào)優(yōu)手段搞清楚排查效率會(huì)高很多。CPU密集型瓶頸的一個(gè)典型特征是CPU使用率持續(xù)高位但TPS上不去響應(yīng)時(shí)間卻在增長。這種時(shí)候需要重點(diǎn)排查是否存在死循環(huán)、密集計(jì)算、或者是序列化/反序列化開銷過大等問題。比如某些框架的內(nèi)存序列化操作在低并發(fā)下看起來沒事一上高并發(fā)就暴露出嚴(yán)重的CPU消耗。磁盤I/O瓶頸也是高危類型。常見于大量日志寫入、數(shù)據(jù)庫頻繁刷盤、或者文件讀寫密集的業(yè)務(wù)場景。如果是數(shù)據(jù)庫所在磁盤導(dǎo)致I/O等待過高可以考慮讓數(shù)據(jù)庫表和索引分離存儲在不同磁盤或者調(diào)整日志的寫入策略。內(nèi)存與GC問題在高并發(fā)Java應(yīng)用中非常普遍。堆內(nèi)存設(shè)置不合理對象創(chuàng)建過于頻繁或者存在內(nèi)存泄漏都會(huì)導(dǎo)致GC頻繁。解決方向包括調(diào)整堆大小參數(shù)、優(yōu)化對象創(chuàng)建邏輯、使用池化技術(shù)復(fù)用對象。GC調(diào)優(yōu)要基于GC日志分析不要盲目修改參數(shù)。數(shù)據(jù)庫層面慢查詢和索引失效是兩大高頻問題。慢查詢的典型處理方式是分析SQL執(zhí)行計(jì)劃對高頻查詢條件建立合適的聯(lián)合索引避免在WHERE條件字段上使用函數(shù)導(dǎo)致索引失效。數(shù)據(jù)庫連接數(shù)配置過小在高并發(fā)下會(huì)導(dǎo)致大量線程等待獲取連接表現(xiàn)為接口RT急劇上升。這類問題的判斷方法是看連接池是否被打滿以及等待獲取連接的時(shí)間是否異常。鎖競爭問題在高并發(fā)寫場景下很容易暴露。比如數(shù)據(jù)庫行鎖競爭、分布式鎖的頻繁等待、以及應(yīng)用層的同步鎖。這類問題的處理往往需要結(jié)合業(yè)務(wù)邏輯來優(yōu)化甚至可能需要引入異步化或者隊(duì)列不是單純改參數(shù)就能解決的。我整理的常見瓶頸快速判斷參考如下表癥狀可能瓶頸首選排查方向典型手段響應(yīng)時(shí)間漲、TPS不漲應(yīng)用層線程阻塞/鎖線程Dump、鎖等待分析消除鎖競爭、線程池調(diào)優(yōu)CPU居高不下業(yè)務(wù)計(jì)算密集/GC頻繁CPU火焰圖、GC日志代碼優(yōu)化、調(diào)整JVM參數(shù)I/O等待高磁盤能力不足/刷盤頻繁iostat、數(shù)據(jù)庫配置磁盤擴(kuò)容、日志/tmp分離、調(diào)優(yōu)刷盤策略連接數(shù)打滿連接池配置過小/泄漏連接池監(jiān)控、活躍連接數(shù)池大小調(diào)優(yōu)、排查連接泄漏數(shù)據(jù)庫響應(yīng)慢SQL執(zhí)行計(jì)劃問題慢查詢?nèi)罩?、?zhí)行計(jì)劃優(yōu)化索引、改寫SQL內(nèi)存持續(xù)上漲內(nèi)存泄漏/緩存過大堆內(nèi)存監(jiān)控、Dump分析修復(fù)泄漏、控制緩存上限3.3 調(diào)優(yōu)驗(yàn)證的方法改一處測一次調(diào)優(yōu)過程中最有價(jià)值的習(xí)慣是“一次只改一個(gè)變量改完就測”。這個(gè)道理很簡單但實(shí)際操作時(shí)很多人忍不住一次性調(diào)整多個(gè)參數(shù)結(jié)果出了問題根本看不出來是哪一項(xiàng)改動(dòng)帶來的效果。舉例來說你發(fā)現(xiàn)數(shù)據(jù)庫連接池太小影響了TPS同時(shí)又覺得JVM堆參數(shù)不合理。如果同一次跑壓測前把這兩個(gè)都改了測試通過還好一旦性能沒有提升甚至下降你就無法判斷是哪一個(gè)改動(dòng)出了問題只能回頭逐個(gè)還原白白浪費(fèi)時(shí)間。正確的做法是先只調(diào)連接池大小重新壓測對比結(jié)果確認(rèn)有效之后再去調(diào)整JVM參數(shù)再壓測。每一步都留下完整記錄包括改動(dòng)內(nèi)容、改動(dòng)前后的測試結(jié)果。這樣你每做的一次調(diào)優(yōu)到底有沒有用一清二楚。調(diào)優(yōu)后的驗(yàn)證不是只看一個(gè)指標(biāo)至少要從三個(gè)維度對比TPS是否提升、平均響應(yīng)時(shí)間和P95/P99是否下降、服務(wù)器的資源消耗是否更合理。有時(shí)候TPS上去了但P99反而變差說明長尾請求變多了這對用戶體驗(yàn)來說并不是真正的優(yōu)化。舉個(gè)例子某接口平均響應(yīng)時(shí)間從300毫秒降到150毫秒但如果P99從800毫秒漲到1200毫秒那說明少數(shù)用戶的體驗(yàn)變得更差了這在性能優(yōu)化中是不可接受的。另外如果有條件一定要做多次重復(fù)測試取均值尤其是一些對系統(tǒng)影響較大的調(diào)優(yōu)場景。單次測試結(jié)果受網(wǎng)絡(luò)波動(dòng)、系統(tǒng)自身不穩(wěn)定因素影響較大。至少跑三輪取相對穩(wěn)定的一組作為結(jié)果才更可靠。4. 壓測報(bào)告編寫與問題排查工具集4.1 一份有說服力的報(bào)告應(yīng)該包含什么性能測試報(bào)告是整個(gè)性能測試項(xiàng)目的最終交付物也是對外溝通的橋梁。報(bào)告寫得專業(yè)與否決定了你的工作成果能不能被業(yè)務(wù)方和技術(shù)團(tuán)隊(duì)充分認(rèn)可。我見過很多測試工程師把大量精力花在執(zhí)行上卻對報(bào)告敷衍了事最后效果大打折扣。寫報(bào)告時(shí)一個(gè)核心認(rèn)知是你的讀者不全是技術(shù)人員。業(yè)務(wù)方關(guān)心的是能不能支撐業(yè)務(wù)目標(biāo)管理層關(guān)心的是要不要加機(jī)器、需要花多少錢只有開發(fā)團(tuán)隊(duì)才會(huì)關(guān)心具體的瓶頸點(diǎn)和方法論。所以一份合格的報(bào)告必須兼顧兩類讀者的訴求既要有技術(shù)細(xì)節(jié)的深度也要有結(jié)論的清晰表達(dá)。我的報(bào)告結(jié)構(gòu)一般是這樣組織的測試概述項(xiàng)目背景、測試目標(biāo)、測試范圍和時(shí)間窗口需求與指標(biāo)定義本次壓測的核心指標(biāo)和通過標(biāo)準(zhǔn)比如目標(biāo)TPS、目標(biāo)RT、成功率測試環(huán)境說明軟硬件配置、網(wǎng)絡(luò)拓?fù)?、?shù)據(jù)規(guī)模、是否與生產(chǎn)一致測試場景設(shè)計(jì)業(yè)務(wù)模型、并發(fā)模型、壓測規(guī)模和持續(xù)時(shí)間核心結(jié)果數(shù)據(jù)TPS、RT平均/P95/P99、成功率、錯(cuò)誤率等關(guān)鍵指標(biāo)務(wù)必用表格呈現(xiàn)資源監(jiān)控?cái)?shù)據(jù)應(yīng)用服務(wù)器、數(shù)據(jù)庫、中間件的關(guān)鍵監(jiān)控指標(biāo)問題清單與調(diào)優(yōu)進(jìn)展發(fā)現(xiàn)的問題、嚴(yán)重級別、當(dāng)前狀態(tài)、調(diào)優(yōu)前后對比結(jié)論與建議是否達(dá)到目標(biāo)、容量預(yù)估、擴(kuò)容建議、后續(xù)優(yōu)化的優(yōu)先級4.2 數(shù)據(jù)呈現(xiàn)的細(xì)節(jié)技巧寫報(bào)告的時(shí)候數(shù)據(jù)呈現(xiàn)方式直接決定了讀者能不能快速抓住重點(diǎn)。對于技術(shù)人員你需要的是趨勢圖、分布數(shù)據(jù)和原始明細(xì)對于業(yè)務(wù)方和管理層你需要的是一目了然的結(jié)論性數(shù)據(jù)。表格是報(bào)告里最常用的呈現(xiàn)方式。TPS、響應(yīng)時(shí)間、成功率、錯(cuò)誤率這些關(guān)鍵指標(biāo)一定要放表格里不同場景分不同行使對比清晰。比如基準(zhǔn)場景、梯度場景、混合場景、穩(wěn)定性場景分別列出各自的TPS和RT讀者一眼就知道系統(tǒng)在不同負(fù)載下的表現(xiàn)。趨勢圖的作用是展示變化過程反映性能拐點(diǎn)。一個(gè)典型的場景是當(dāng)并發(fā)從200升到400時(shí)TPS從300升到500這是一個(gè)健康的上揚(yáng)但當(dāng)并發(fā)從400升到800時(shí)TPS不升反降同時(shí)RT暴漲這就說明系統(tǒng)已經(jīng)達(dá)到瓶頸。這種拐點(diǎn)用圖呈現(xiàn)的效果最直觀。P95和P99這兩個(gè)分位值非常值得重視。平均響應(yīng)時(shí)間容易被極端值掩蓋比如一條超時(shí)20秒的請求把幾十條50毫秒的請求平均值一下子拉高了。而P95和P99反映的是絕大多數(shù)用戶的真實(shí)體驗(yàn)尤其是P99它代表的是最差的那1%用戶所感知到的性能。給接口調(diào)優(yōu)時(shí)我通常更關(guān)注P99的變化。真正有說服力的報(bào)告是平均RT不高但P99卻異常高這種對比能直觀地表明確實(shí)存在長尾問題具備更大的分析價(jià)值。資源使用情況也是報(bào)告的關(guān)鍵組成部分。應(yīng)用服務(wù)器的CPU峰值、內(nèi)存水位數(shù)據(jù)庫的慢查詢數(shù)量和連接池使用率這些數(shù)據(jù)與TPS、RT結(jié)合在一起才能解釋“為什么系統(tǒng)表現(xiàn)是這個(gè)水平”。比如TPS上不去但數(shù)據(jù)庫慢查詢數(shù)量一直在漲兩者放在同一張報(bào)告里結(jié)論自然浮出水面。4.3 常見問題與排查技巧實(shí)錄壓測執(zhí)行過程中必然會(huì)遇到各種問題有些是系統(tǒng)自帶的有些是腳本自身帶來的。把所有問題都留給開發(fā)去解決是不對的很多問題明明就是壓測方案設(shè)計(jì)不到位造成的。腳本相關(guān)的高頻問題最典型的是壓測結(jié)果被參數(shù)相關(guān)性困擾。比如前面接口提取的Token過了有效期后續(xù)接口不斷報(bào)權(quán)限異常。這類問題的排查方向是看錯(cuò)誤日志中報(bào)錯(cuò)的具體原因而不是一有問題就覺得系統(tǒng)掛了。確認(rèn)問題來源之后再去調(diào)整腳本的關(guān)聯(lián)邏輯。我自己的習(xí)慣是壓測過程中隨手開著后端日志窗口一旦看到報(bào)錯(cuò)就立刻對照請求體和返回信息判斷到底是腳本問題還是系統(tǒng)問題。另一種高頻問題是壓測機(jī)被打滿。很多人忽略了壓測機(jī)自身的性能限制。如果JMeter部署在一臺低配機(jī)器上一旦并發(fā)數(shù)上去壓測機(jī)自己的CPU先打滿了這時(shí)無論系統(tǒng)性能多好測試結(jié)果都不可能準(zhǔn)確。判斷的方法很簡單監(jiān)控壓測機(jī)的CPU和內(nèi)存如果壓測機(jī)CPU持續(xù)100%就需要橫向擴(kuò)容壓測機(jī)了通常用一臺額外機(jī)器做分布式施壓。系統(tǒng)類問題中最常見也最容易被忽視的是壓測前的緩存預(yù)熱。生產(chǎn)環(huán)境中有大量熱點(diǎn)數(shù)據(jù)常駐緩存而測試環(huán)境是冷啟動(dòng)狀態(tài)Redis里什么都沒有所有請求都直接打到數(shù)據(jù)庫導(dǎo)致首次壓測的數(shù)據(jù)庫壓力巨大甚至出現(xiàn)大量連接超時(shí)。這種問題不解決測出來的結(jié)果毫無參考價(jià)值。我的做法是在壓測前做一次“預(yù)跑”先用低并發(fā)跑上一段時(shí)間讓緩存和數(shù)據(jù)頁填滿等系統(tǒng)穩(wěn)定下來再進(jìn)入正式壓測階段。另一個(gè)實(shí)用的排查小技巧是當(dāng)TPS出現(xiàn)斷崖式下跌時(shí)立刻看兩個(gè)地方數(shù)據(jù)庫連接池和中間件線程池。這兩個(gè)池子一旦被打滿系統(tǒng)會(huì)表現(xiàn)為“突然降速但并沒有崩潰”。原因通常是池的排隊(duì)等待時(shí)間暴增超時(shí)丟棄大量請求。遇到這種情況可先做一個(gè)快速的定位測試降低并發(fā)數(shù)到原來的1/4如果系統(tǒng)迅速恢復(fù)正?;究梢源_認(rèn)是某個(gè)池子容量不夠了。4.4 一個(gè)值得一用的小工具集工欲善其事必先利其器。性能測試期間用對工具能節(jié)省大把時(shí)間。這里分享一些我每次壓測都會(huì)用到的工具組合全部基于開源方案沒有授權(quán)問題。壓測執(zhí)行JMeter作為主壓測工具配好參數(shù)化與斷言監(jiān)控采集Node Exporter配合時(shí)序數(shù)據(jù)庫存儲用可視化面板展示服務(wù)端指標(biāo)一臺機(jī)器即可搭建起來JVM監(jiān)控JDK自帶的Java Flight Recorder排查GC和線程問題相當(dāng)趁手?jǐn)?shù)據(jù)庫排查MySQL慢查詢?nèi)罩九浜献詭У男阅芊治龉ぞ呒由螮XPLAIN查看執(zhí)行計(jì)劃日志分析先用手頭常用的日志檢索工具或者一條awk命令提取關(guān)鍵信息快速定位異常集中點(diǎn)這套組合完全免費(fèi)但覆蓋了從施壓到監(jiān)控、再到定位問題的完整鏈路。團(tuán)隊(duì)小、預(yù)算不多的情況下這套方案足夠支撐絕大部分性能測試需求。用這些工具時(shí)有個(gè)經(jīng)驗(yàn)想分享。監(jiān)控面板不要只看眼前那幾分鐘的數(shù)據(jù)。壓測過程中要定時(shí)回看整個(gè)時(shí)間窗口的趨勢圖因?yàn)楹芏嘈阅軉栴}有一個(gè)緩慢演化的過程比如內(nèi)存會(huì)隨著測試時(shí)間推移不斷增長。只盯著實(shí)時(shí)數(shù)值看很難發(fā)現(xiàn)這種漸進(jìn)的趨勢而趨勢恰恰是定位內(nèi)存泄漏類問題的最重要線索。5. 收尾前的一些真心話性能測試做到最后真正拉開差距的地方其實(shí)已經(jīng)不是工具用得熟不熟、腳本寫得好不好而是對整個(gè)測試過程閉環(huán)的把控能力。從需求到執(zhí)行從定位到報(bào)告每一環(huán)都要經(jīng)得起推敲。我個(gè)人的體會(huì)是性能測試報(bào)告的結(jié)尾不應(yīng)該只是羅列問題更要有明確的問題優(yōu)先級和整改建議。建議要能落地比如“連接池參數(shù)建議從20調(diào)整到50預(yù)期可提升多少”這樣的表述而不是籠統(tǒng)說“建議優(yōu)化系統(tǒng)性能”。報(bào)告只有做到這個(gè)顆粒度才會(huì)被開發(fā)和運(yùn)維真正重視測試工作也才能真正推動(dòng)系統(tǒng)進(jìn)步。最后再分享一個(gè)小技巧。無論項(xiàng)目多急壓測執(zhí)行完了一定要留出時(shí)間回看一遍全部腳本和配置特別是參數(shù)化文件里的數(shù)據(jù)是否被耗盡、關(guān)聯(lián)提取的正則表達(dá)式是否涵蓋邊界情況。很多壓測數(shù)據(jù)的異常最后追根溯源都是這些“小細(xì)節(jié)”的問題。性能測試沒有驚心動(dòng)魄的高光時(shí)刻靠的恰恰是細(xì)節(jié)處一點(diǎn)一滴的累積。把這些基礎(chǔ)功夫做扎實(shí)了你寫的每一份報(bào)告都會(huì)站得住腳。