控制與腳本排錯)
1. 邏輯控制器在JMeter里的真實身份先別急著點(diǎn)進(jìn)去做性能測試有些年頭的人應(yīng)該都有過這么一段經(jīng)歷腳本跑到一半發(fā)現(xiàn)某個接口的請求數(shù)跟預(yù)期差了十萬八千里要么多發(fā)了要么壓根沒發(fā)。翻了半天腳本最后發(fā)現(xiàn)是某個邏輯控制器在暗中作梗。而剛?cè)腴TJMeter的同學(xué)則經(jīng)常把邏輯控制器當(dāng)成發(fā)請求的按鈕以為它能像線程組那樣直接產(chǎn)生流量——這個誤解會導(dǎo)致后面一連串的腳本設(shè)計跑偏。JMeter邏輯控制器從名字上看容易讓人誤解。它本身不發(fā)送任何請求也不產(chǎn)生任何壓力它做的事情是管理它下面掛著的Sampler取樣器怎么執(zhí)行、執(zhí)行幾次、在什么條件下執(zhí)行。你可以把線程組理解成流水線上的工人把Sampler理解成工人手上的動作邏輯控制器就是工人手里的那張工藝流程圖先做什么、什么情況下做、做幾遍、什么時候跳過。它不干活但它決定活怎么干。那為什么一個性能測試腳本里非要這種東西我自己的體會是三個原因。第一業(yè)務(wù)場景需要。真實用戶訪問系統(tǒng)時不可能每個人都走同一條路徑——有的人上來就搜索有的人直接打開商品頁有的人下單前會反復(fù)改購物車。你要用一個線程組模擬出這種分化就得靠邏輯控制器去分流。第二流量節(jié)奏需要。壓測不是把TPS拉滿就完事很多時候要在同一輪測試?yán)锬M高峰期和平穩(wěn)期的流量差異或者按比例讓一部分請求走A接口、一部分走B接口這也是邏輯控制器的活兒。第三腳本可維護(hù)性需要。如果沒有邏輯控制器你要模擬100種用戶行為就得建100個線程組或者復(fù)制100份Sampler腳本根本沒法看。用控制器把分支收攏到同一個線程組里后續(xù)調(diào)比例、改邏輯都方便得多。搞清楚它的定位之后后面的內(nèi)容才有意義。接下來我會按常用控制器的真實用法 - 組合場景 - 踩坑排錯 - 可維護(hù)性設(shè)計這條線來講全程都是以實際壓測腳本能落地為目標(biāo)不繞彎。2. 最常用的幾類控制器逐個拆解它們到底怎么用、坑在哪2.1 If控制器條件判斷的兩個版本差異能卡死一票人If控制器是做條件分流最直接的工具。它的界面里有一個Condition (evaluated)輸入框但這里的水非常深因為JMeter在不同版本里對條件的處理方式不一樣。在JMeter 4.0之前If控制器有兩個選項Interpret Condition as Variable Expression和Evaluate for all children。那時候很多人習(xí)慣直接寫${VAR}這種形式選中第一個選項后如果${VAR}的值是字符串true就執(zhí)行子節(jié)點(diǎn)。后來版本迭代默認(rèn)行為變成了Expression (must evaluate to true)要求你寫一個能求出布爾值的表達(dá)式比如${__jexl3(${count} 10,)}或者${__groovy(vars.get(count).toInteger() 10)}。這里的坑在于如果你在舊版腳本里寫的是${VAR} 1這種形式在新版本里它會被當(dāng)成一個字符串表達(dá)式而字符串本身是非空即真還是直接報錯取決于具體版本。結(jié)果就是條件永遠(yuǎn)為true所有分支全部執(zhí)行。我見過最典型的翻車現(xiàn)場是一個壓測腳本里開發(fā)者想按用戶類型分流條件寫成了${userType} 1結(jié)果所有用戶都走了第一個分支后面接口的TPS分布完全不符合預(yù)期排查了大半天才發(fā)現(xiàn)是If控制器的條件解析方式變了。我的建議是不管你用的哪個版本一律用__jexl3表達(dá)式包裹最穩(wěn)妥。寫法是這樣的${__jexl3(${VAR} 1,)}或者用Groovy${__groovy(vars.get(VAR) 1)}等號、類型、引號這些細(xì)節(jié)都要注意。另一個容易踩的點(diǎn)是條件里引用的變量如果還沒被定義JMeter會把空值代入導(dǎo)致表達(dá)式求值異?;蛘咧苯觙alse分支就不走了。所以你在設(shè)計腳本時If控制器引用的變量最好在它前面的Sampler或配置元件里明確賦過初值。2.2 循環(huán)控制器與While控制器控制次數(shù)和持續(xù)的根本區(qū)別Loop Controller循環(huán)控制器的邏輯最簡單讓子節(jié)點(diǎn)跑固定的次數(shù)。比如設(shè)置Loop Count為10那么下面的Sampler就執(zhí)行10遍。它適合做重復(fù)性操作比如每個用戶登錄之后要連續(xù)瀏覽10個商品頁或者一個接口要輪詢幾次固定重試。While Controller條件循環(huán)控制器則是當(dāng)條件為true就一直跑。它最大的價值在于處理不確定次數(shù)的循環(huán)最經(jīng)典的場景是下單后輪詢訂單狀態(tài)發(fā)完下單請求不斷查訂單狀態(tài)接口直到返回已完成或者超過超時時間才退出。While控制器的坑在哪里如果你在Condition里直接填${STATUS}而不做任何函數(shù)包裹那么JMeter在循環(huán)開始前會把這個變量的值解析成固定字符串然后在每一次迭代里都用這個固定值去判斷。如果第一次判斷是true那它就永遠(yuǎn)循環(huán)下去腳本直接卡死如果第一次是false那么這個循環(huán)就一次都不執(zhí)行。正確寫法依然是函數(shù)包裹${__jexl3(${STATUS} ! SUCCESS,)}這樣才能在每次迭代時重新讀取變量當(dāng)前值。另外While控制器本身不限制最大循環(huán)次數(shù)所以設(shè)計時最好在循環(huán)內(nèi)部加一個計數(shù)器或者用${__jexl3(${count} 100 ${STATUS} ! SUCCESS,)}這種復(fù)合條件避免無限循環(huán)把施壓機(jī)跑崩。順便說一句循環(huán)控制器嵌套使用時注意循環(huán)變量作用域。比如外層循環(huán)次數(shù)是10內(nèi)層循環(huán)次數(shù)是5內(nèi)層循環(huán)里用了計數(shù)器你需要確定計數(shù)器是每個外層迭代都從0開始還是全局遞增。這個細(xì)節(jié)在后面的排錯部分我會專門提。2.3 事務(wù)控制器它其實不是控制器是統(tǒng)計學(xué)放大鏡從嚴(yán)格意義上講Transaction Controller事務(wù)控制器不是用來控制執(zhí)行邏輯的它會生成一個額外的聚合樣本來匯總它范圍內(nèi)所有Sampler的總耗時。壓測報告里的事務(wù)響應(yīng)時間很多時候就是這個控制器產(chǎn)出的。它有兩種配置模式需要理解清楚。一種是Generate Parent Sample勾選后子Sampler的樣本會被隱藏所有統(tǒng)計信息都以事務(wù)控制器本身的名義上報你在聚合報告里只會看到一條事務(wù)記錄。另一種是不勾選那么子Sampler的每一條樣本照常上報事務(wù)控制器會額外再生成一條匯總樣本。做全鏈路壓測、一個下單操作包含好多個接口時我一般推薦勾選Generate Parent Sample這樣報告里看的就是用戶感知的下單總時長而不是一堆零散接口。這里有個常見的誤解有人以為事務(wù)控制器能讓子Sampler并行執(zhí)行其實不能。它下面的Sampler依然是順序執(zhí)行的它只是把它們的時間框在一起統(tǒng)計。還有如果你把事務(wù)控制器放在循環(huán)控制器里面那么每一次循環(huán)都會生成一條事務(wù)樣本最終報告里的事務(wù)平均耗時可能是多次循環(huán)的混合平均值場景設(shè)計時要注意。2.4 吞吐量控制器按比例和按次數(shù)分發(fā)流量的低調(diào)利器Throughput Controller吞吐量控制器名字聽著高級實際做的事很簡單控制子節(jié)點(diǎn)在整個線程組執(zhí)行周期內(nèi)的執(zhí)行次數(shù)或百分比。它有兩種模式Percent模式比如填30表示該控制器下的Sampler在被遍歷到時有30%的概率會被執(zhí)行。Total Executions模式填一個總次數(shù)比如100表示該控制器下的Sampler在整個腳本生命周期里最多執(zhí)行100次就停。Percent模式很適合做A/B分流。比如你想讓20%的流量打到新版本接口80%走老接口用兩個Throughput Controller分別設(shè)置20和80掛在同一個線程組下就能實現(xiàn)。但是注意這個百分比是每次遍歷時的獨(dú)立概率不是嚴(yán)格精確的配額。如果線程數(shù)很少、迭代次數(shù)也很少實際比例會和設(shè)定值有明顯偏差。線程數(shù)越多、迭代越多才越接近設(shè)定比例。所以它是統(tǒng)計意義上的比例控制不是精確到個位數(shù)的分流開關(guān)。Total Executions模式則適合做引流結(jié)束就停的場景。比如整輪壓測中某個接口只想被觸發(fā)50次做冒煙驗證后面的壓力測試階段不再執(zhí)行就可以用這個模式。下表把這四類控制器的用途梳理一下方便對照記憶控制器核心作用典型場景是否產(chǎn)生請求If控制器條件判斷分支執(zhí)行用戶類型分流、開關(guān)控制否Loop控制器固定次數(shù)循環(huán)重復(fù)瀏覽、固定次數(shù)重試否While控制器條件持續(xù)循環(huán)輪詢狀態(tài)直到成功/超時否Transaction控制器統(tǒng)計子節(jié)點(diǎn)聚合耗時全鏈路事務(wù)響應(yīng)時間統(tǒng)計是額外樣本Throughput控制器按比例/次數(shù)限制子節(jié)點(diǎn)執(zhí)行A/B流量分配、限量觸發(fā)否3. 一個完整業(yè)務(wù)場景里控制器是怎么搭配著用的光說單個控制器沒什么感覺我拿一個電商類系統(tǒng)的常見壓測場景來演示一下組合方式。假設(shè)需求是這樣的模擬真實用戶訪問首頁后30%的用戶會去搜索70%的用戶直接看商品詳情搜索之后有一半人會翻到第二頁最終所有用戶都會把商品加購物車并嘗試下單下單前系統(tǒng)需要檢查庫存如果庫存還沒準(zhǔn)備好要輪詢等它直到庫存接口返回可用。拿到這種需求新手最容易的做法是拆成三四個線程組分別跑然后祈禱比例湊得對。但問題在于一個真實用戶在全鏈路里的行為是連貫的拆成多個線程組后每個組里的用戶是獨(dú)立虛擬出來的你沒法保證同一個用戶先搜索、再詳情、再下單這個完整鏈路是通的。所以更好做法是在一個線程組里用邏輯控制器串出整條路徑。腳本的樹形結(jié)構(gòu)大致長這樣線程組 ├── 隨機(jī)變量userType范圍0~9 ├── 首頁請求 ├── If控制器 ${__jexl3(${userType} 3,)} │ ├── 搜索請求 │ └── If控制器 ${__jexl3(${__Random(0,9,)} 5,)} │ └── 搜索第二頁請求 ├── 商品詳情請求 ├── While控制器 ${__jexl3(${stockReady} ! true ${loopCount} 20,)} │ ├── 庫存查詢請求 │ ├── 后置處理器判斷庫存設(shè)置stockReady │ └── 固定定時器等待1秒 └── 事務(wù)控制器下單全鏈路Generate Parent Sample ├── 加入購物車請求 ├── 提交訂單請求 └── 支付請求這么設(shè)計的思路我逐個說清楚。第一個關(guān)鍵點(diǎn)用隨機(jī)變量 If控制器做用戶分流而不是開多個線程組。隨機(jī)變量userType在0到9之間取一個整數(shù)每次線程迭代重新生成然后第一個If控制器判斷它是否小于3是就走搜索分支否則直接走詳情分支。這樣30%的請求會進(jìn)搜索邏輯70%不進(jìn)。之所以用隨機(jī)變量而不是Throughput Controller是因為這個分流要貫穿整個用戶鏈路——用戶進(jìn)來之后需要知道自己是搜索型用戶還是詳情型用戶后面可能還要根據(jù)這個身份做不同的斷言和數(shù)據(jù)處理。用隨機(jī)變量把身份值固化下來比每次都靠概率去猜要穩(wěn)定。第二個關(guān)鍵點(diǎn)搜索分支里的翻頁判斷用的是__Random函數(shù)配合If控制器。每次進(jìn)到搜索邏輯時隨機(jī)生成一個0~9的數(shù)字小于5就翻頁等于給搜索用戶翻頁這個行為加了50%的條件概率。這里要注意__Random函數(shù)每次被引用時都會重新取隨機(jī)數(shù)所以如果你在If條件里寫它每輪迭代都會重新生成一個隨機(jī)值這就相當(dāng)于每次遍歷到這個控制器時獨(dú)立拋硬幣。第三個關(guān)鍵點(diǎn)庫存輪詢用While控制器包住查詢庫存 設(shè)置變量 等待這三個節(jié)點(diǎn)。每次循環(huán)先查一次庫存后置處理器里判斷返回結(jié)果如果庫存可用就把stockReady設(shè)為true下次循環(huán)一開始條件就變成false自然退出。等待1秒的定時器是為了避免輪詢太頻繁把庫存服務(wù)打垮也算是一種對被測系統(tǒng)的保護(hù)。第四個關(guān)鍵點(diǎn)下單鏈路整體包在一個Transaction Controller里并且勾選了Generate Parent Sample。這樣聚合報告里就能直接看到從加購物車到支付成功這一整條事務(wù)的平均耗時、90線、95線、誤差率而不是分別看三個接口的指標(biāo)。做性能分析時這個整體事務(wù)數(shù)據(jù)比零散接口數(shù)據(jù)更有決策價值。這個組合用下來整個腳本的流量分布就非常接近真實用戶的行為了。而且后面如果要調(diào)整比例只需要改隨機(jī)變量的取值范圍或者If條件里的閾值完全不用動Sampler結(jié)構(gòu)維護(hù)成本很低。4. 排錯專章邏輯控制器引發(fā)的靈異事件排查鏈路復(fù)盤邏輯控制器用多了一定會遇到幾種你翻半天代碼都找不到原因的情況。我把這幾類高頻問題整理出來并把排查過程完整寫出來讓大家以后遇到類似問題能直接對照。4.1 作用域問題你以為它管了其實它管不著最容易被忽視的坑是作用域。JMeter里的控制器只對它下面的子節(jié)點(diǎn)有效跟它平級的Sampler完全不受它影響。比如線程組 ├── If控制器條件false │ └── 請求A ├── 請求B你期望的是條件false時A和B都不執(zhí)行實際上B照樣執(zhí)行。因為If控制器管不到兄弟節(jié)點(diǎn)。這個現(xiàn)象在腳本樹層級比較復(fù)雜時特別容易發(fā)生尤其是有人把Sampler拖來拖去拖到了控制器外面還以為是控制器出了問題。排查方法是在腳本樹里先看每個Sampler縮進(jìn)幾格上面有沒有控制器節(jié)點(diǎn)。如果Sampler和控制器是平級關(guān)系那它理直氣壯地不受控制。注意邏輯控制器的管束范圍永遠(yuǎn)是它的直接子節(jié)點(diǎn)加上所有后代節(jié)點(diǎn)不包含它的兄弟節(jié)點(diǎn)。另外還有一種常見誤解把If控制器放在線程組下以為是線程級開關(guān)想讓整個線程組暫停。其實If控制器只作用于它下面的樹節(jié)點(diǎn)線程組內(nèi)其他平級分支照樣跑。4.2 變量取值時機(jī)條件判斷賭的是那一刻的值我遇到過這樣一個問題場景里有一個JSR223前置處理器在每次請求前動態(tài)計算一個標(biāo)記寫入變量然后下面的If控制器根據(jù)這個標(biāo)記決定是否執(zhí)行某個子請求。腳本結(jié)構(gòu)大概是這樣線程組 ├── JSR223前置處理器計算標(biāo)記寫入nextAction ├── If控制器${nextAction} submit │ └── 提交訂單請求 ├── 查詢訂單狀態(tài)請求看起來邏輯沒問題但跑起來發(fā)現(xiàn)If控制器經(jīng)常判斷失敗。原因是JMeter的變量在樹中的執(zhí)行順序是從上到下、逐節(jié)點(diǎn)執(zhí)行的前置處理器先執(zhí)行并把值寫入變量If控制器再判斷時是能拿到新值的。但如果你把If控制器放在前置處理器的上面或者放在另一個會影響變量的線程組里那變量取值就完全是另一回事了。更隱蔽的一種情況是在循環(huán)里。比如While循環(huán)體內(nèi)有一個BeanShell后置處理器它每次執(zhí)行都會更新某個變量然后下一輪循環(huán)里If控制器要用這個變量做判斷。看起來每輪都更新了但實際上JMeter在某些版本中變量在循環(huán)迭代之間傳遞時存在快照問題——你更新的變量可能沒來得及在當(dāng)前迭代的If判斷中使用要到下一輪甚至更晚才生效。排查思路是在If控制器前面放一個Debug Sampler調(diào)試取樣器運(yùn)行一次腳本看變量在那一刻到底是多少。這種問題靠肉眼審腳本很難發(fā)現(xiàn)必須用運(yùn)行時觀測的手段確認(rèn)變量快照。Debug Sampler會輸出當(dāng)前線程所有變量的值是排查變量類問題最直接的利器。4.3 循環(huán)里的計數(shù)器你以為重置了其實它是全局的如果你在循環(huán)控制器里使用了${__counter}函數(shù)或者放了一個計數(shù)器配置元件要特別注意它的計數(shù)范圍默認(rèn)是每個線程獨(dú)立計數(shù)還是全局計數(shù)。__counter函數(shù)有兩個參數(shù)第二個參數(shù)填TRUE表示每個線程獨(dú)立計數(shù)填FALSE表示全局共享計數(shù)。很多人默認(rèn)不填結(jié)果多個線程的計數(shù)器串在一起循環(huán)次數(shù)控制完全亂套。舉一個真實會翻車的例子你想讓每個用戶循環(huán)5次瀏覽商品在循環(huán)內(nèi)用了${__counter(TRUE,)}作為商品ID編號。按理說每個用戶第1次循環(huán)取到的都應(yīng)該是1但如果你寫成了${__counter(FALSE,)}那么第一個用戶取到1~5第二個用戶取到6~10商品ID完全錯位業(yè)務(wù)上下文的關(guān)聯(lián)數(shù)據(jù)就全亂了。排查方法先確認(rèn)你的計數(shù)器函數(shù)參數(shù)是否跟預(yù)期一致再確認(rèn)計數(shù)器元件擺放的位置是線程組下還是循環(huán)體內(nèi)。如果放在循環(huán)體外它不會隨循環(huán)重置。如果想要每個用戶都是從1開始的獨(dú)立計數(shù)就必須把計數(shù)器放在線程組級別并勾選Track counter independently for each user如果要跨線程的全局計數(shù)就不要勾選。4.4 施壓端資源開銷控制器不是免費(fèi)的最后說一個容易被忽略的性能問題邏輯控制器本身也有開銷。如果一個腳本里堆了幾十個If控制器每個If條件里都是一長串字符串拼接和正則匹配那么壓測機(jī)施壓端在判斷邏輯上消耗的CPU時間會非??捎^。在高并發(fā)下這部分開銷會拉低施壓端自身能產(chǎn)生的最大TPS讓你誤以為是被測系統(tǒng)性能不行其實是施壓端榨不出更多流量了。我在一次壓測中見過一個腳本一個線程組里嵌了六七層If控制器和兩個While循環(huán)里面大量使用${VAR}拼接字符串。單看邏輯沒問題但跑到8000TPS時怎么都上不去了最后排查發(fā)現(xiàn)施壓機(jī)CPU已經(jīng)100%而采樣器真正發(fā)請求的時間占比很低大部分時間都耗在條件判斷和變量解析上。解決思路有兩個方向。一是能用Sampler本身的次數(shù)設(shè)置比如HTTP請求里的循環(huán)次數(shù)、線程組的調(diào)度時長實現(xiàn)的就別用控制器包一層。二是復(fù)雜的條件判斷盡量用JSR223腳本實現(xiàn)Groovy的字符串處理和邏輯判斷速度遠(yuǎn)比JMeter內(nèi)嵌的那些${}表達(dá)式快這個在可維護(hù)性部分我會展開講。下面把這幾類問題的根因和解法整理成一個速查表現(xiàn)象根因排查思路解決方法控制器下的Sampler不執(zhí)行條件表達(dá)式解析異常或變量為空加Debug Sampler看變量實際值統(tǒng)一用__jexl3或__groovy包裹條件條件為true卻走了false分支變量更新時機(jī)不對檢查變量賦值節(jié)點(diǎn)與If控制器的相對位置調(diào)整執(zhí)行順序或在If前強(qiáng)制更新變量循環(huán)次數(shù)完全不對計數(shù)器范圍設(shè)置錯誤確認(rèn)__counter參數(shù)和配置元件場景明確TRUE/FALSE參數(shù)獨(dú)立計數(shù)放線程組級TPS上不去且CPU跑滿控制器層數(shù)過多、表達(dá)式開銷大查看施壓機(jī)CPU占用與采樣器耗時占比減少控制器層數(shù)復(fù)雜邏輯改JSR2235. 讓腳本能跑之外更要好維護(hù)幾個實用設(shè)計習(xí)慣壓測腳本跟業(yè)務(wù)代碼一樣寫著爽很容易維護(hù)起來才見真章。尤其是邏輯控制器這種藏在樹結(jié)構(gòu)里的邏輯三個月后再打開腳本連你自己都可能看不懂當(dāng)初為什么這么拉分支。所以我總結(jié)了幾個在實際項目中沉淀下來的設(shè)計習(xí)慣。第一個習(xí)慣是給控制器命名直接帶意圖。不要叫什么If控制器1循環(huán)控制器2要寫成If_用戶已登錄_去結(jié)算While_查詢物流狀態(tài)直到完成Transaction_全鏈路下單統(tǒng)計。這么做的好處是腳本樹層次一展開整個業(yè)務(wù)流就一目了然像看目錄一樣。JMeter里樹節(jié)點(diǎn)的名字完全由你定命名規(guī)范從第一天就建立起來后面接手腳本的人會非常感謝你。第二個習(xí)慣是復(fù)雜分支用JSR223 變量收斂而不是在GUI上堆一大片If。舉個典型場景下單接口返回的業(yè)務(wù)狀態(tài)碼可能有幾十種不同狀態(tài)碼要走不同的后續(xù)處理。如果你用If控制器寫幾十個分支腳本直接變成一團(tuán)亂麻。更好的做法是寫一個JSR223采樣器或后置處理器用Groovy的switch語句解析業(yè)務(wù)狀態(tài)碼把下一步動作塞進(jìn)一個變量里后面再用一個If控制器判斷這個變量def bizCode vars.get(bizCode) String nextAction switch (bizCode) { case A001: nextAction order break case A002: nextAction retry break case A003: nextAction refund break default: nextAction done } vars.put(nextAction, nextAction)然后在樹里只保留一個If控制器If控制器${__jexl3(${nextAction} retry,)} └── 重新下單請求這樣主要的分支邏輯從控制器堆疊變成了代碼邏輯后續(xù)想增加一個狀態(tài)碼的處理路徑只需要改JSR223里的switch不用動樹結(jié)構(gòu)。需要注意JSR223里用的腳本語言如果是Groovy請確認(rèn)JMeter版本是3.1以上的因為那個時候才內(nèi)置了Groovy引擎。第三個習(xí)慣是善用JMeter屬性來做全局開關(guān)。__P函數(shù)可以讀取JMeter屬性和__V配合能實現(xiàn)不改腳本就切換分支的效果。比如你的腳本里有一個是否執(zhí)行清理數(shù)據(jù)的控制分支正常的寫法是If控制器判斷一個用戶自定義變量但每次要手動改。更好的做法是${__P(doClean, true)}然后在命令行里用-JdoCleanfalse傳入就能在不打開GUI的情況下決定是否執(zhí)行清理邏輯。性能測試經(jīng)常要在不同環(huán)境重復(fù)跑同一套腳本這種屬性開關(guān)能省下大量改腳本的時間。第四個習(xí)慣是保留必要的調(diào)試輔助節(jié)點(diǎn)。在腳本聯(lián)調(diào)階段我會在每個關(guān)鍵的If控制器或While控制器前面臨時掛一個Debug Sampler確認(rèn)變量狀態(tài)。等腳本穩(wěn)定了再刪掉。很多人在腳本跑不通時喜歡一遍遍盯日志其實Debug Sampler輸出變量快照的效率要高得多。加上JMeter的Run校驗功能不實際發(fā)請求只做配置驗證大部分配置層面的問題在真正壓測前就能暴露。但要記住校驗通過不代表邏輯正確該跑的試運(yùn)行比如單線程小循環(huán)還是不能省。排查邏輯控制器問題時還有一個小技巧值得分享陷入死循環(huán)的While控制器會導(dǎo)致線程一直不結(jié)束看起來像腳本卡住了。遇到這種情況先在查看結(jié)果樹里看是不是有某類請求在瘋狂刷屏如果是基本就是While條件永遠(yuǎn)為true。此時強(qiáng)制停止測試把While控制器的最大循環(huán)次數(shù)加上或者用固定定時器降低輪詢頻率就能避免把施壓機(jī)資源耗盡。我自己用JMeter做壓測這么多年最大的體會是邏輯控制器是把雙刃劍。用得好一個線程組能模擬出極其復(fù)雜的真實用戶行為模型腳本清晰、比例好調(diào)、維護(hù)省心用得不好層級嵌套堆成山條件判斷亂七八糟跑出來的數(shù)據(jù)根本不敢信。每個控制器在加入腳本之前你都應(yīng)該先問一句這個分支邏輯能不能用更簡單的方式表達(dá)。如果答案是能那就別加。性能測試腳本的本質(zhì)是可靠復(fù)現(xiàn)業(yè)務(wù)流量模型越簡單的東西在關(guān)鍵時刻越不容易出錯。