上線那天早上十點,行銷把活動連結丟進各大群組。前五分鐘一切正常,你甚至有點得意。第八分鐘,監控面板那條原本貼著底的回應時間曲線開始往上爬,慢慢翹起來,然後直接戳到天花板。使用者的截圖進來了:轉圈圈、504、一片白。

你打開伺服器看,CPU 沒滿、記憶體還有、資料庫連線數也沒爆,但服務就是卡在那裡不動。你心裡只有一個問題:到底是多少人同時進來,把它壓垮的?

問題在於,這個問題你現在才問,已經太晚了。它應該在上線前的兩個禮拜就被問過一遍。

憑感覺加機器,跟 curl 一發很快,都在騙自己

第一個直覺通常是加機器。服務會掛,那就多開兩台、把 instance 拉到四個,總行了吧。這招有時候真的有效,但你其實不知道為什麼有效、也不知道加到第幾台會失效。更慘的情況是加了機器一樣掛,因為瓶頸根本不在應用層,而是在後面那個所有 instance 共用的資料庫,或某個第三方 API 的連線上限。加機器只是把錢燒得更快,問題原封不動。

第二個直覺是自己測一下。你打開終端機,curl 打一發 API,200 毫秒回來,漂亮。再打一發,還是很快。於是你安心了。

但你剛剛測的東西,跟上線那天發生的事情完全是兩回事。你一個人、一次一發,測的是「系統閒著沒事時,服務一個人的速度」。上線那天是幾千個人同時擠進同一道門,彼此搶連線、搶執行緒、搶那個資料庫連線池裡有限的幾十條連線。這兩種情況的差別,就像量一個人走過空無一人的走廊,跟量他在跨年 101 散場的人潮裡走過同一條走廊——距離一樣,體感是兩個世界。

你缺的不是更快的單次請求,是「很多人同時來」這個變數。而這個變數,你在正式環境上線那天才第一次遇到。

JMeter 切進來的地方:把「很多人同時來」搬到上線前

JMeter 是 Apache 家的開源壓測工具,用 Java 寫的(已驗證,官方使用手冊)。它幫你解的就是上面那個缺口:在上線前,先在你的機器上生出一群假使用者,同時對你的服務發動請求,看它撐到哪裡開始崩。

它的核心是 Thread Group(執行緒群組)。你可以把一個 thread 想成一位使用者,Thread Group 就是「這次要放幾個人進來」的旋鈕。官方文件講得很直白:每個 thread 會完整、且各自獨立地跑完整份測試計畫,多個 thread 就是用來模擬對伺服器的併發連線(已驗證,Test Plan 元件說明)。你設 500 個 thread,就是同時放 500 個人衝進來。

這裡有個很多人第一次踩的細節:Ramp-Up Period(爬升時間)。假設你設 500 個 thread、ramp-up 給 100 秒,JMeter 不會在第 0 秒把 500 人一次塞進去,而是花 100 秒慢慢放,平均每 0.2 秒進一個(已驗證,官方 Thread Group 說明)。這比較像真實流量——客人是陸續走進店裡,不是同一個瞬間從天而降。ramp-up 給太短,你測到的其實是一記瞬間衝擊;給太長,又可能永遠達不到你想要的併發峰值。

Thread Group 底下掛的是 Sampler(取樣器),真正幹活的就是它。最常用的是 HTTP Request,也就是「這 500 個人各自要去打哪支 API」。Sampler 每跑一次就吐出一筆結果,帶著成功或失敗、花了幾毫秒、回傳多大等資訊(已驗證,官方 Component Reference)。

光會發請求還不夠,你得確認「快」的同時有沒有「對」。這是 Assertion(斷言) 的位置。壓力一上來,服務為了自保有時候會很快回你一個 500,或者回一頁半殘的錯誤畫面——速度看起來超讚,其實整條路是斷的。加一個 Response Assertion 去檢查回傳內容或狀態碼,才不會被「很快但是錯的」騙過去。

還有兩個元件會讓你的壓測更像真人。一個是 Timer(定時器),在請求之間塞進等待時間,模擬使用者不是機關槍一樣狂點,中間會停頓、會思考(官方叫 think time)。另一個是 CSV Data Set Config,讓每個 thread 從一份 CSV 讀不同的帳號密碼,把 sampler 裡的登入欄位換成 ${USER}${PASS}(已驗證,官方最佳實務)。這步比它看起來重要:如果 500 個假使用者全都拿同一組帳號、查同一筆商品,資料庫的快取會爽爽地全部命中,你量到的是「快取很猛」,不是「系統扛得住」。真上線時每個人查的東西都不一樣,那時快取幫不了你。用同一筆資料壓測,等於自己給自己灌迷湯。

看懂結果:平均值是最會騙人的那個數字

跑完之後,數字從 Listener(監聽器) 出來。但這裡藏著一個新手最容易犯的錯:只看 Average(平均回應時間),然後鬆一口氣。

官方文件裡有個經典例子,值得抄下來記住(已驗證,Aggregate Report 說明):假設你發了 10 次登入,前 5 次各花 1 秒,第 6 次卡了 91 秒,剩下 4 次又各 1 秒。平均是 (1×9 + 91) / 10 = 10 秒。看到平均 10 秒你可能覺得慢,但真相是——這服務九成的時候都在 1 秒內回應,只有一個離群值把平均整個拉歪。

反過來也一樣危險:平均漂亮不代表沒事。如果 100 個人裡有 5 個等了 30 秒,平均可能還是很好看,但那 5 個人已經在客訴了。

所以真正要盯的是百分位數p95 是「95% 的請求都在這個時間內完成」,p99 是「99% 的請求都在這個時間內完成」(已驗證,官方 90/95/99 Line 定義)。這兩個數字才貼近真實的使用者體驗——你關心的不是平均那個人,是排在最後面、等到快抓狂的那 1% 到 5%。有個判斷小訣竅:如果平均值和 p99 靠得很近,代表回應時間很穩、沒什麼長尾;如果 p99 比平均高出一大截,就表示尾巴很長,總有一小撮人在受苦。

另外兩個數字要一起看。Throughput(吞吐量) 是伺服器每秒(或每分)處理掉幾個請求,這是壓測真正的產能刻度——你把人加得越多,throughput 到某個點就上不去了,那個轉折點大概就是你的天花板。Error % 是失敗請求的比例,JMeter 預設把 HTTP 狀態碼大於 399 的都算失敗(已驗證,官方定義)。這三個要合起來讀:throughput 卡住不動、同時 error rate 開始往上跳、p99 飆到天花板——這三件事一起發生的那個併發數,就是你上線那天想知道卻太晚才知道的答案。

三個會讓你白測一場的坑

第一個坑:用 GUI 模式跑正式壓測。 JMeter 那個圖形介面很好用,但官方最佳實務講得斬釘截鐵:GUI 模式只拿來開發、除錯測試計畫,真正的壓測一律用 non-GUI 的 CLI 模式跑(已驗證,官方 Best Practices)。因為那個畫面本身就很吃 CPU 和記憶體,你一邊渲染即時圖表一邊發壓力,等於一邊開直播一邊跑百米,量出來的數字會被你自己的介面拖慢。正確做法是:

1
jmeter -n -t test.jmx -l result.jtl

-n 是 non-GUI,-t 指定測試計畫檔,-l 指定結果要寫去哪個檔(已驗證,官方文件)。跑完再把 result.jtl 匯進 Listener 慢慢看。同一份文件也提醒:壓測時把 View Results Tree 這類會吃大量記憶體的 Listener 全部關掉,它們只該在寫腳本、除錯的時候開。

第二個坑:壓測機自己先成了瓶頸。 這個坑最陰,因為它會給你一個看起來合理、其實是假的結論。官方明確講:JMeter 做的事越多,每個 thread 越要排隊搶 CPU,量到的時間就越膨脹、越不準(已驗證,官方 Best Practices)。你以為系統在 800 人的時候變慢,其實是你那台筆電的網卡跟 CPU 先撐不住了,被壓垮的是壓測工具本身,不是被測的服務。真要打大量負載,得把 JMeter 分散到多台機器(distributed mode),或至少確認壓測機的資源沒先見底。壓測的鐵律是——先確定那把尺本身是準的,再去量別人。

第三個坑,其實前面講過了:只看平均、不看長尾。 這是最便宜、也最常犯的錯,不用多花一毛錢就能避開,只要你願意多看兩欄。

還有一塊 JMeter 幫不了你,得誠實面對:壓測環境跟正式環境的差距(推測,取決於各團隊配置)。你在測試環境壓出來的天花板,只有在硬體規格、資料量、網路拓撲、快取狀態都跟正式一致時才可信。測試庫裡躺著一萬筆資料、正式庫躺著一千萬筆,同一支查詢在兩邊是不同的世界。JMeter 給得出數字,但那個數字有多接近真相,是你自己要負責的事。

回到上線那天

如果重來一次,上線前那兩個禮拜你會這樣做:先在測試環境架好一份 Thread Group,掛上真正會被打爆的那幾支 API 當 Sampler,加 Assertion 確認回來的是對的、加 Timer 讓節奏像真人、用 CSV Data Set 餵不同帳號避免快取作弊。然後用 CLI 模式,從 100 個併發開始,一路往上加——200、500、1000——每一輪都盯著 throughput 何時不再往上、error rate 何時開始跳、p99 何時戳到你受不了的線。

那個轉折點出現的地方,就是你的天花板。你會在一間沒有半個真實使用者的辦公室裡,安安靜靜地知道這個答案,而不是在活動開跑第八分鐘、看著群組裡的截圖一張張跳出來的時候。


參考來源:Apache JMeter User’s ManualElements of a Test PlanBest PracticesAggregate Report 指標說明(BlazeMeter)