試實(shí)戰(zhàn):用 wrk 從零搭建性能基線)
很多同學(xué)第一次給 PHP 項(xiàng)目做壓力測(cè)試第一反應(yīng)都是找個(gè)工具把接口打爆看看會(huì)不會(huì)掛。我以前也這么想直到有次新功能上線前我用 wrk 隨手壓了一套接口發(fā)現(xiàn) QPS 連 200 都沒到RT 的 P99 卻已經(jīng)飆到 1.5 秒這才意識(shí)到一件事沒有一份可靠的 QPS、RT、CPU、內(nèi)存基線數(shù)據(jù)你根本沒法判斷系統(tǒng)是變好了還是變差了。這篇文章就把我從零開始用 wrk 對(duì) PHP 系統(tǒng)做壓測(cè)、記錄基線的完整過(guò)程寫下來(lái)適合完全沒碰過(guò)壓測(cè)的 PHP 開發(fā)同學(xué)照著操作也能幫想搭一套壓測(cè)流程的人省掉不少?gòu)澛贰?. 壓測(cè)前先想明白你測(cè)的不是極限是一份可用基線1.1 為什么選 wrk輕量、快、不折騰壓測(cè)工具不少常見的有 ab、JMeter、wrk、k6 這幾類。我給 PHP 項(xiàng)目做日常基線測(cè)試首選就是 wrk原因很簡(jiǎn)單它是一個(gè)單文件編譯的 C 語(yǔ)言工具沒有圖形界面沒有插件體系一條命令就能跑完一輪壓測(cè)。對(duì)比下來(lái)大概是這個(gè)樣子工具上手成本適合場(chǎng)景主要限制ab很低臨時(shí)快速看一眼接口吞吐高并發(fā)下線程模型不如 wrk數(shù)值容易偏低JMeter高復(fù)雜業(yè)務(wù)流程、分布式壓測(cè)、報(bào)告美化配置麻煩跑少量線程時(shí)本地資源占用就不小wrk低單接口/多接口的并發(fā)壓測(cè)、基線回歸玩法要靠 Lua 腳本層級(jí)報(bào)告需要自己整理我說(shuō)的不折騰是有實(shí)際體會(huì)的。之前我試過(guò)在兩臺(tái)測(cè)試機(jī)上各跑一套壓測(cè)一邊用 ab一邊用 wrk同樣打到同一個(gè) PHP 接口ab 的 QPS 明顯低于 wrk。原因是 wrk 基于非阻塞事件模型可以用少量線程維持大量并發(fā)連接壓測(cè)機(jī)自身的 CPU 消耗低很多。換句話說(shuō)wrk 更容易讓壓力真正落到被測(cè)的 PHP 服務(wù)上而不是先把自己的進(jìn)程拖垮。1.2 壓測(cè)要記錄的四個(gè)數(shù)字各自代表什么很多人把壓測(cè)等同于測(cè) QPS但一份合格的基線報(bào)告至少要有四個(gè)維度的數(shù)據(jù)QPS每秒請(qǐng)求數(shù)系統(tǒng)在特定并發(fā)下每秒鐘能處理多少個(gè)請(qǐng)求這是吞吐量指標(biāo)。RT響應(yīng)時(shí)間單個(gè)請(qǐng)求從發(fā)出到收到響應(yīng)的時(shí)間通??雌骄岛透叻种槐热?P99。CPU 使用率壓測(cè)期間這臺(tái)機(jī)器到底忙不忙是代碼計(jì)算密集還是空等 IO。內(nèi)存占用PHP-FPM 是常駐內(nèi)存的進(jìn)程池隨著并發(fā)上升worker 數(shù)量增加內(nèi)存曲線非常值得盯。這四個(gè)數(shù)字必須放在一起看缺一個(gè)都容易誤判。比如 QPS 高了但 CPU 已經(jīng) 95% 以上說(shuō)明系統(tǒng)到上限了QPS 沒漲內(nèi)存卻一直在漲可能是 PHP-FPM worker 在不斷創(chuàng)建或者測(cè)試接口本身有泄漏。只有四者對(duì)齊才能說(shuō)這條基線的水位是健康的。1.3 開始之前先確認(rèn)測(cè)試環(huán)境是干凈的這也是我第一次壓測(cè)踩過(guò)的坑。當(dāng)時(shí)我在本地開發(fā)環(huán)境上直接壓機(jī)器上還跑著編譯器、數(shù)據(jù)庫(kù)腳本、一堆開發(fā)工具測(cè)出來(lái)的 QPS 忽高忽低完全沒法當(dāng)參考?,F(xiàn)在我的習(xí)慣是壓測(cè)前必須確認(rèn)幾件事被測(cè)代碼是同一個(gè)穩(wěn)定分支并記錄 commit 號(hào)測(cè)試機(jī)只跑 nginx PHP-FPM 必要的數(shù)據(jù)庫(kù)后臺(tái)監(jiān)控進(jìn)程全部關(guān)掉壓測(cè)機(jī)的網(wǎng)絡(luò)和被測(cè)機(jī)之間沒有額外限速或代理PHP 的 opcache 保持與服務(wù)一致的開關(guān)狀態(tài)。寧可多花十分鐘清理環(huán)境也不要拿一份臟數(shù)據(jù)當(dāng)基線后面做優(yōu)化對(duì)比時(shí)你會(huì)感謝當(dāng)時(shí)的謹(jǐn)慎。2. 從零搭環(huán)境裝好 wrk準(zhǔn)備一個(gè)能壓的 PHP 接口2.1 安裝 wrk 的兩種常規(guī)姿勢(shì)如果你的測(cè)試機(jī)是 Linux 系統(tǒng)最簡(jiǎn)單的辦法是直接用包管理器裝。以常見的 Debian/Ubuntu 系為例sudo apt install -y wrk wrk --version安裝完直接驗(yàn)證版本能輸出 wrk 的版本號(hào)就說(shuō)明裝好了。有些發(fā)行版?zhèn)}庫(kù)里沒有這個(gè)包或者你想用最新版本可以走編譯安裝sudo apt install -y build-essential libssl-dev git clone wrk 官方源碼倉(cāng)庫(kù)地址 cd wrk make sudo cp wrk /usr/local/bin/編譯之前務(wù)必保證有 openssl 的開發(fā)頭文件wrk 默認(rèn)會(huì)編出支持 HTTPS 的版本如果缺了 libssl-devmake 階段會(huì)報(bào)錯(cuò)報(bào)錯(cuò)信息通常會(huì)直接指向 openssl 相關(guān)的頭文件。我第一次編譯時(shí)就在這卡過(guò)所以單獨(dú)提一句。這里多說(shuō)一句很多 PHP 項(xiàng)目部署時(shí)是 Windows 開發(fā)環(huán)境 Linux 生產(chǎn)環(huán)境如果你只在 Windows 上開發(fā)建議直接在 Linux 測(cè)試機(jī)上跑 wrk不要試圖在 Windows 下折騰編譯。wrk 本身的設(shè)計(jì)目標(biāo)就是 Linux/macOSWindows 下的兼容方案既費(fèi)時(shí)間又容易遇到網(wǎng)絡(luò)棧差異。2.2 一個(gè)足夠簡(jiǎn)單又不失真實(shí)的測(cè)試接口壓測(cè)接口選什么很有講究。我的做法是準(zhǔn)備兩層目標(biāo)第一層是一個(gè)最簡(jiǎn)單、不查數(shù)據(jù)庫(kù)、不調(diào)第三方接口的 JSON 返回接口用來(lái)測(cè)純 PHP 運(yùn)行時(shí) PHP-FPM 網(wǎng)絡(luò)棧的底子第二層再測(cè)真實(shí)業(yè)務(wù)接口比如帶一個(gè)簡(jiǎn)單的數(shù)據(jù)庫(kù)查詢觀察額外損耗。第一層接口可以長(zhǎng)這樣?php // test_api.php $data [ code 0, msg ok, data [ user_id 10086, time time(), ], ]; echo json_encode($data);為什么先拿這種接口打底因?yàn)閴簻y(cè)是為了定位瓶頸。如果連一個(gè)不查庫(kù)的接口 QPS 都很低那問(wèn)題大概率出在 PHP-FPM 配置、opcache、CPU 頻率或中間件上沒必要上來(lái)就懷疑 SQL。把這個(gè)底子摸清楚之后再去壓真實(shí)接口兩者對(duì)比就能算出業(yè)務(wù)邏輯本身消耗了多少吞吐量。部署上我建議走 nginx PHP-FPM 的標(biāo)準(zhǔn)組合監(jiān)聽 127.0.0.1:8080 這樣的內(nèi)網(wǎng)地址避免在壓測(cè)過(guò)程中被防火墻或路由影響。測(cè)試機(jī) IP 用 127.0.0.1 還是內(nèi)網(wǎng) IP 都行但如果你想讓壓測(cè)機(jī)和被測(cè)機(jī)分離就要用被測(cè)機(jī)的內(nèi)網(wǎng) IP 訪問(wèn)。2.3 第一條壓測(cè)命令和輸出解讀把接口部署好之后先用 curl 手動(dòng)確認(rèn)返回正常然后再上 wrk。第一條命令建議放低并發(fā)跑通流程wrk -t2 -c10 -d10s --latency http://127.0.0.1:8080/test_api.php其中-t2表示用 2 個(gè)線程-c10表示維持 10 個(gè)并發(fā)連接-d10s表示持續(xù) 10 秒--latency會(huì)在結(jié)束時(shí)輸出更完整的延遲分布。正常跑完會(huì)看到類似這樣的輸出Running 10s test http://127.0.0.1:8080/test_api.php 2 threads and 10 connections Thread Stats Avg Stdev Max /- Stdev Latency 4.82ms 2.31ms 45.12ms 86.18% Req/Sec 1.05k 102.34 1.41k 70.31% Latency Distribution 50% 4.50ms 75% 5.20ms 90% 6.80ms 99% 14.20ms 10520 requests in 10.01s, 2.03MB read Requests/sec: 1051.12 Transfer/sec: 207.43KB這里最容易被新手誤讀的地方是 Thread Stats 里的 Req/Sec 和最后的 Requests/sec。Thread Stats 的 Req/Sec 是每個(gè)線程自己的平均吞吐如果你的線程數(shù)是 4那么這個(gè)數(shù)值大致是總 QPS 分割后的水平而最后的Requests/sec才是整體 QPS也就是我們基線里要記的那個(gè)數(shù)字。把這兩行搞混的人不少我第一次看輸出時(shí)也愣了半天。3. 正式壓測(cè)并發(fā)要加梯度數(shù)據(jù)要取多輪3.1 從低并發(fā)起步找到吞吐量的拐點(diǎn)壓測(cè)不是一上來(lái)就-c500那樣你只能得到一個(gè)打爆了的結(jié)果卻不知道系統(tǒng)是從哪個(gè)并發(fā)開始扛不住的。我的標(biāo)準(zhǔn)操作是用一組并發(fā)梯度比如-c10、-c50、-c100、-c200、-c500每個(gè)梯度跑 30 秒記錄對(duì)應(yīng)的 QPS 和 RT。這個(gè)過(guò)程本質(zhì)上是尋找拐點(diǎn)一開始并發(fā)上升QPS 會(huì)跟著漲因?yàn)橥瑫r(shí)能處理的請(qǐng)求變多了到某個(gè)點(diǎn)之后再往上加并發(fā)QPS 不再明顯增長(zhǎng)甚至開始回落同時(shí) RT 快速拉高。這個(gè)拐點(diǎn)對(duì)應(yīng)的并發(fā)量就是系統(tǒng)當(dāng)前配置下的合理服務(wù)水位。舉個(gè)實(shí)際例子我之前壓一個(gè) PHP 8.2 opcache 的簡(jiǎn)單 JSON 接口-c10時(shí) QPS 約 1800-c50時(shí)漲到 4200-c100時(shí)是 5100-c200的時(shí)候只有 4600 了RT 的 P99 從 18ms 漲到 120ms。這就說(shuō)明 100 左右并發(fā)已經(jīng)接近這臺(tái)機(jī)器的服務(wù)上限繼續(xù)壓只會(huì)讓用戶體驗(yàn)變差。3.2 平均 RT 會(huì)欺騙你P99 才是真實(shí)用戶體驗(yàn)wrk 輸出里的 Latency 一欄Avg是平均響應(yīng)時(shí)間但平均值的迷惑性很大如果 90% 的請(qǐng)求都是 5ms剩下 10% 是 500ms平均值會(huì)被拉到一個(gè)看起來(lái)很溫和的數(shù)字。這也是為什么壓測(cè)一定要加--latency參數(shù)看延遲分布里的 50%、75%、90%、99% 分位。P99 的含義是99% 的請(qǐng)求都低于這個(gè)值剩下的 1% 是最慢的尾部請(qǐng)求。對(duì)線上服務(wù)來(lái)說(shuō)用戶體驗(yàn)往往由尾部延遲決定。我在做基線記錄時(shí)RT 這一欄至少記兩個(gè)數(shù)平均值和 P99。后續(xù)做優(yōu)化時(shí)如果平均值下降了但 P99 在上漲我會(huì)立刻警惕——這通常意味著某些慢請(qǐng)求變得更極端了可能存在于鎖競(jìng)爭(zhēng)或進(jìn)程池打滿的問(wèn)題。3.3 記錄多輪結(jié)果取穩(wěn)定而不是最高的一輪壓測(cè)數(shù)值天然有波動(dòng)尤其是 PHP-FPM 場(chǎng)景下進(jìn)程復(fù)用、opcache 冷熱、系統(tǒng)定時(shí)任務(wù)都可能讓某一輪結(jié)果偏高或偏低。我的做法是同一個(gè)并發(fā)梯度至少跑三輪每輪之間停 5 秒把三輪結(jié)果都記下來(lái)然后取中位數(shù)或者波動(dòng)最小的那一輪作為基線值。這里有個(gè)反直覺的點(diǎn)最高值往往不值得記錄。比如某輪跑到 5000 QPS下一輪只有 4100我會(huì)直接懷疑第一輪是不是正好命中了 opcache 預(yù)熱完成的窗口或者系統(tǒng) GC 還沒觸發(fā)。真正的基線應(yīng)該是可復(fù)現(xiàn)的那一組數(shù)字不是偶爾出現(xiàn)的峰值。如果接口需要 POST 請(qǐng)求wrk 也能通過(guò) Lua 腳本解決?;A(chǔ)寫法大概是這樣-- post.lua wrk.method POST wrk.headers[Content-Type] application/json wrk.body {user_id:10086,page:1}然后用wrk -t4 -c100 -d30s -s post.lua http://...啟動(dòng)即可。這個(gè)用法不難但注意腳本里的 body 是所有并發(fā)連接共用同一個(gè)請(qǐng)求體如果業(yè)務(wù)要求每個(gè)請(qǐng)求參數(shù)不同就得用 Lua 腳本在請(qǐng)求時(shí)動(dòng)態(tài)生成復(fù)雜度會(huì)高不少一般做基線時(shí)不需要一步到位。4. 壓測(cè)時(shí)盯服務(wù)器CPU 和內(nèi)存基線的采集方法4.1 壓測(cè)期間要開的幾個(gè)監(jiān)控命令wrk 跑起來(lái)只是整個(gè)壓測(cè)的一半另一半是被測(cè)服務(wù)器的資源狀態(tài)。我通常會(huì)在壓測(cè)開始前先在被測(cè)機(jī)上開一個(gè)后臺(tái)記錄腳本把壓測(cè)期間的數(shù)據(jù)落盤這樣壓測(cè)結(jié)束后可以回放當(dāng)時(shí)的資源曲線。最常用的命令有這幾個(gè)# 每2秒輸出一次系統(tǒng)概況共記錄30次 vmstat 2 30 # 每2秒輸出一次每個(gè)CPU核心的使用率 mpstat -P ALL 2 30 # 查看每個(gè)進(jìn)程的CPU和內(nèi)存占用 pidstat -u -r 2 30 # 內(nèi)存概況 free -m如果不想同時(shí)開這么多終端可以用一個(gè)簡(jiǎn)單的循環(huán)腳本把關(guān)鍵數(shù)據(jù)拼進(jìn)同一個(gè)日志文件#!/bin/bash # load_monitor.sh interval2 end$((SECONDS60)) while [ $SECONDS -lt $end ]; do echo $(date %F_%T) /tmp/load_test.log top -bn1 | head -15 /tmp/load_test.log free -m /tmp/load_test.log sleep $interval done注意pidstat這個(gè)命令來(lái)自 sysstat 工具包如果系統(tǒng)里沒有需要先apt install sysstat。mpstat 也一樣。另外這些監(jiān)控命令本身也會(huì)消耗少量 CPU在低配機(jī)器上可能影響測(cè)試結(jié)果所以記錄完后記得把這些進(jìn)程都?xì)⒌粼偃∽罱K數(shù)據(jù)。4.2 從監(jiān)控?cái)?shù)據(jù)反推瓶頸在哪個(gè)環(huán)節(jié)拿到資源數(shù)據(jù)后重點(diǎn)看三個(gè)地方一是 CPU 的 us/sy 比例。us高說(shuō)明用戶態(tài)代碼在忙比如 PHP 腳本邏輯復(fù)雜、循環(huán)密集sy高說(shuō)明系統(tǒng)態(tài)開銷大比如頻繁上下文切換、網(wǎng)絡(luò)包處理、系統(tǒng)調(diào)用太多。如果 sy 高于 us往往不是 PHP 代碼本身的問(wèn)題而是并發(fā)模型或網(wǎng)絡(luò)配置有瓶頸。二是 CPU idle 和等待 IO 的時(shí)間。vmstat里有個(gè) wa 列代表 IO 等待。wa 明顯偏高的時(shí)候QPS 上不去多半是磁盤拖后腿比如 nginx 訪問(wèn)日志瘋狂寫盤或者 PHP slowlog 開著。我之前確實(shí)遇到過(guò) CPU 只有 20%、QPS 卻卡在 800 的情況檢查后發(fā)現(xiàn)是訪問(wèn)日志寫到了普通機(jī)械盤上IO 等待直接打滿。三是內(nèi)存里 PHP-FPM 的占用。用ps aux --sort-%mem | head -20看看是否有 worker 進(jìn)程內(nèi)存漲得異常。PHP-FPM 的每個(gè) worker 在默認(rèn)配置下可能占用幾十到一兩百 MB如果并發(fā)開得高幾十個(gè) worker 同時(shí)駐留內(nèi)存很容易超預(yù)期。內(nèi)存基線就是用來(lái)記錄這個(gè)并發(fā)水位下FPM 進(jìn)程池到底吃掉了多少內(nèi)存。4.3 把服務(wù)端數(shù)據(jù)和 wrk 輸出對(duì)齊到同一時(shí)間線壓測(cè)結(jié)束后你會(huì)得到兩堆數(shù)據(jù)一堆來(lái)自 wrk 的匯總值一堆來(lái)自監(jiān)控日志的每秒采樣。很多人直接把這兩堆數(shù)字拼在一起寫報(bào)告實(shí)際上不對(duì)齊時(shí)間線的基線是沒法定位問(wèn)題的。我的做法是壓測(cè)開始前在監(jiān)控日志里手動(dòng)打一條標(biāo)記 start wrk 結(jié)束后再打一條 done 這樣壓測(cè)窗口內(nèi)的資源采樣就有明確的起止邊界。后續(xù)做對(duì)比時(shí)只看窗口內(nèi)的采樣平均值和峰值窗口外的數(shù)據(jù)直接丟棄。然后我習(xí)慣建一張映射表把每次壓測(cè)的命令參數(shù)、wrk 結(jié)果、資源監(jiān)控結(jié)果放在同一行這樣一旦某次基線異常可以立刻倒推是哪一層的變量發(fā)生了變化。5. 把基線寫成表格一份能長(zhǎng)期復(fù)用的記錄模板5.1 基線表該有哪些字段跑完一堆壓測(cè)之后如果只是把數(shù)字記在聊天軟件里過(guò)兩周就找不到了。我推薦用一張表格管理所有基線字段包括日期代碼版本測(cè)試接口并發(fā)QPSRT 均值RT P99CPU%內(nèi)存占用環(huán)境說(shuō)明2026-06-01某提交號(hào)test_api.php100510018ms46ms68%1.8Gopcache開啟FPM動(dòng)態(tài)10-802026-06-02某提交號(hào)test_api.php200460088ms120ms66%2.4G同上超負(fù)荷區(qū)間2026-06-03某提交號(hào)user_info.php5062076ms210ms35%2.1G帶一次DB查詢這張表里每一行就是一條基線。環(huán)境說(shuō)明這欄特別重要因?yàn)?opcache 開關(guān)、PHP 版本、FPM 參數(shù)都會(huì)直接影響結(jié)果不寫清楚三周后再看數(shù)據(jù)等于瞎猜。5.2 什么時(shí)機(jī)需要重新測(cè)基線基線不是測(cè)一次就永久有效的。按我的經(jīng)驗(yàn)以下變化發(fā)生時(shí)必須重新采集PHP 版本升級(jí)、框架或核心依賴有大版本變更、php-fpm 池配置調(diào)整pm.max_children、pm.start_servers、nginx 的 keepalive 或 gzip 策略變化、測(cè)試接口本身的代碼邏輯改動(dòng)、測(cè)試機(jī)硬件規(guī)格變化。如果出現(xiàn)同一接口、同一代碼版本QPS 卻和上周記錄差很多的情況我也會(huì)重新測(cè)一遍基線而且會(huì)特意檢查是不是監(jiān)控腳本在后臺(tái)偷跑、是否有人改了系統(tǒng)參數(shù)。這個(gè)習(xí)慣幫我排查過(guò)好幾次假象——有一次 QPS 突然掉了一半最后發(fā)現(xiàn)是某臺(tái)機(jī)器上被同事裝了定時(shí)任務(wù)壓測(cè)窗口正好撞上了大規(guī)模日志清理。5.3 用基線做容量預(yù)估的簡(jiǎn)單算法基線最有價(jià)值的用法不是看數(shù)字好看而是用來(lái)做容量預(yù)估。公式不復(fù)雜需要實(shí)例數(shù) (預(yù)估峰值 QPS × (1 冗余系數(shù))) / 單機(jī)基線 QPS舉個(gè)例子基線表里單機(jī)在 100 并發(fā)下 QPS 是 5000如果業(yè)務(wù)方預(yù)測(cè)大促峰值請(qǐng)求是 15000 QPS目標(biāo) CPU 水位控制在 70% 左右那么單機(jī)預(yù)留 30% 冗余最終需要的實(shí)例數(shù)就是15000 × 1.3 / 5000 ≈ 4臺(tái)。如果只按 QPS 峰值算而不留冗余一旦流量抖動(dòng)所有實(shí)例都會(huì)沖到 90% 以上的 CPU尾部延遲會(huì)很難看。需要注意的是這種線性預(yù)估只適用于無(wú)狀態(tài)、可水平擴(kuò)展的 PHP 接口。如果接口嚴(yán)重依賴數(shù)據(jù)庫(kù)或第三方服務(wù)那么數(shù)據(jù)庫(kù)側(cè)的連接數(shù)、負(fù)載也是容量邊界單純按單機(jī) QPS 算會(huì)低估風(fēng)險(xiǎn)。遇到這種情況我會(huì)同時(shí)把數(shù)據(jù)庫(kù)的 CPU、連接數(shù)也拉進(jìn)基線表。6. 實(shí)操中最容易翻車的五個(gè)坑6.1 壓測(cè)機(jī)先崩了測(cè)出來(lái)的數(shù)就不可信wrk 雖然很輕量但也不是無(wú)限跑。如果你用一臺(tái) 2 核小機(jī)器去壓一個(gè) 8 核的服務(wù)端很可能會(huì)出現(xiàn)壓測(cè)機(jī) CPU 先到 100%而服務(wù)端還很從容的情況。這時(shí)的 QPS 數(shù)字反映的是壓測(cè)機(jī)的上限不是被測(cè)系統(tǒng)的上限。判斷方法很簡(jiǎn)單壓完看壓測(cè)機(jī)的top如果 wrk 進(jìn)程吃滿了 CPU 或者系統(tǒng)出現(xiàn)大量 TCP 連接排隊(duì)就說(shuō)明壓力工具先到極限了。解決方式一是減少 wrk 線程數(shù)通常-t設(shè)置為壓測(cè)機(jī) CPU 核心數(shù)左右即可沒必要開太多二是把 wrk 放到另一臺(tái)閑置機(jī)器上跑讓壓測(cè)機(jī)和被測(cè)機(jī)分離。6.2 PHP-FPM 配置和 opcache 偷偷影響成績(jī)PHP 場(chǎng)景下最容易被忽略的兩個(gè)變量是 opcache 和 FPM 進(jìn)程池參數(shù)。opcache 關(guān)閉時(shí)每個(gè)請(qǐng)求都要重新編譯一遍 PHP 腳本CPU 消耗會(huì)明顯上升QPS 直接打折opcache 開啟后前幾個(gè)請(qǐng)求還會(huì)觸發(fā)編譯緩存所以正式壓測(cè)前一定要先打幾發(fā)請(qǐng)求做預(yù)熱。FPM 這邊pm.max_children如果設(shè)置太小并發(fā)一旦超過(guò)進(jìn)程池上限請(qǐng)求就開始排隊(duì)QPS 停滯、RT 暴漲如果設(shè)置太大內(nèi)存會(huì)被 worker 吃滿。我做基線時(shí)會(huì)把 FPM 配置快照寫進(jìn)環(huán)境說(shuō)明這樣后續(xù)調(diào)整參數(shù)后對(duì)比才公平。另外注意pm.max_requests它是讓 worker 處理多少請(qǐng)求后自動(dòng)回收的機(jī)制設(shè)得太小會(huì)導(dǎo)致進(jìn)程頻繁創(chuàng)建銷毀CPU 忽高忽低。6.3 長(zhǎng)連接與并發(fā)數(shù)設(shè)置會(huì)帶來(lái)假數(shù)據(jù)wrk 默認(rèn)使用 HTTP/1.1 keep-alive 長(zhǎng)連接這能大幅提高 QPS因?yàn)槭∪チ舜罅?TCP 握手開銷。但真實(shí)用戶往往不是這種訪問(wèn)模式連接會(huì)不斷建立和斷開。所以壓測(cè)結(jié)果必須注明是否 keep-alive。如果你想知道斷開連接場(chǎng)景下的表現(xiàn)可以在 wrk 里加一個(gè)請(qǐng)求頭wrk -t4 -c100 -d30s -H Connection: close http://127.0.0.1:8080/test_api.php你會(huì)看到 QPS 明顯下降這是正常的別慌。關(guān)鍵是基線表里要寫清楚當(dāng)時(shí)是哪種連接模式否則拿 keep-alive 的基線去對(duì)比短連接場(chǎng)景你會(huì)以為系統(tǒng)退化了。6.4 日志和磁盤 IO 拖垮整個(gè)壓測(cè)壓測(cè)期間nginx 的 access log 默認(rèn)每接收一個(gè)請(qǐng)求就寫一行。QPS 高的時(shí)候日志寫入本身就是巨大的磁盤壓力。更隱蔽的是 PHP-FPM 的 slowlog一旦觸發(fā)慢日志就會(huì)在請(qǐng)求處理路徑上產(chǎn)生額外 IORT 反而被日志拖得更慢。我的建議是做純性能基線時(shí)先把 access_log 臨時(shí)關(guān)閉或在配置里指向內(nèi)存盤壓測(cè)完再改回來(lái)。這個(gè)操作不算什么高級(jí)技巧但特別實(shí)用。我第一次壓測(cè) 2000 QPS 時(shí)就發(fā)現(xiàn)磁盤 wait 占到 40%所有并發(fā)一上去 CPU 就在等 IO關(guān)掉訪問(wèn)日志后 QPS 直接翻了一倍?;€測(cè)試測(cè)的是系統(tǒng)處理能力不應(yīng)該讓日志寫入變成隱形成本。6.5 壓完不收拾現(xiàn)場(chǎng)下一次基線直接失真壓測(cè)結(jié)束后的收尾工作同樣重要。需要做的包括停掉壓測(cè)期間的監(jiān)控腳本確認(rèn)沒有殘留的壓測(cè)進(jìn)程在跑清掉壓測(cè)產(chǎn)生的日志文件和臨時(shí)腳本如果臨時(shí)改過(guò) FPM 參數(shù)或 nginx 日志開關(guān)記得恢復(fù)原狀把壓測(cè)時(shí)記錄的代碼 commit 號(hào)和配置快照歸檔。這些看起來(lái)瑣碎但直接影響下一次壓測(cè)是否可信。我之前有一回壓完就撤了第二天再測(cè)時(shí)發(fā)現(xiàn)系統(tǒng)后臺(tái)還掛著監(jiān)控腳本測(cè)出來(lái)的 CPU 基線高了十幾個(gè)百分點(diǎn)白白排查了一下午。從那之后我就養(yǎng)成了壓測(cè)結(jié)束三分鐘收尾的習(xí)慣寧可慢一點(diǎn)也不能讓基線數(shù)據(jù)帶傷入庫(kù)。最后再分享一個(gè)經(jīng)驗(yàn)壓測(cè)基線不是給上級(jí)看的一張表它是你自己理解系統(tǒng)的一把尺子。同一個(gè)接口在不同并發(fā)下的表現(xiàn)、不同配置下的差異、代碼提交前后的吞吐變化都藏在基線的對(duì)比里。把這套流程跑順之后你會(huì)發(fā)現(xiàn)自己對(duì)系統(tǒng)到底能扛多少量這件事終于有了基于數(shù)據(jù)的底而不是靠猜。