存壓力測試實(shí)戰(zhàn)指南:從工具選型到容器避坑全解析)
簡介一份內(nèi)存壓力測試工具memtester 4.1.2的源碼壓縮包面向Linux系統(tǒng)管理員、運(yùn)維人員及底層開發(fā)者用于檢測服務(wù)器或PC內(nèi)存的穩(wěn)定性與潛在錯(cuò)誤解決因內(nèi)存位翻轉(zhuǎn)、數(shù)據(jù)丟失或內(nèi)存泄漏導(dǎo)致的系統(tǒng)崩潰、數(shù)據(jù)損壞等隱患。壓縮包僅20KB共22個(gè)文件包含6個(gè)shell腳本負(fù)責(zé)自動(dòng)編譯、系統(tǒng)類型探測與動(dòng)態(tài)庫鏈接、4個(gè)頭文件和3個(gè)C源文件實(shí)現(xiàn)核心測試邏輯另有README、CHANGELOG、BUGS、Makefile及conf-ld、conf-cc等編譯配置文檔目錄結(jié)構(gòu)典型便于二次開發(fā)。memtester通過向內(nèi)存寫入、讀取和擦除特定模式數(shù)據(jù)來暴露錯(cuò)誤并支持讀寫測試、地址校驗(yàn)、奇偶校驗(yàn)及交叉測試等多種模式適合在服務(wù)器上線前、硬件故障排查或高負(fù)載模擬環(huán)境中進(jìn)行壓力驗(yàn)證可配合top、htop、vmstat等監(jiān)控工具全面評估內(nèi)存狀況。目前已有2551人學(xué)習(xí)下載包含完整源碼與構(gòu)建腳本用戶可快速編譯安裝或交叉移植到其他平臺是深入理解內(nèi)存測試原理并搭建自用檢測工具的理想?yún)⒖肌?. 內(nèi)存壓力測試工具別等服務(wù)被 OOM 抬走才想起來做壓測作為一線運(yùn)維和 SRE我見過太多「服務(wù)下午兩點(diǎn)準(zhǔn)時(shí)卡死」的案例其實(shí)結(jié)論很簡單JVM 堆外內(nèi)存緩慢增長年輕時(shí)一直沒到水位線等到業(yè)務(wù)高峰正好觸到 memory limit內(nèi)核 OOM Killer 一刀把容器殺了然后 K8s 又把它拉起來循環(huán)往復(fù)。事后復(fù)盤往往都會(huì)感慨如果當(dāng)初用內(nèi)存壓力測試工具把整機(jī)或容器的 cgroup 水位預(yù)先頂?shù)介撝狄陨虾芏鄦栴}是能提前暴露的。這里說的“內(nèi)存壓力測試工具”不是某個(gè)單一命令而是一類慣用方案通過工具快速制造用戶態(tài)內(nèi)存分配、物理頁填充、內(nèi)存帶寬飽和或 swap 競爭讓系統(tǒng)在可控范圍進(jìn)入高水位再觀察業(yè)務(wù)進(jìn)程和內(nèi)核的行為變化。它解決的核心問題有三個(gè)評估一臺物理機(jī)或容器可承載的內(nèi)存水位上限驗(yàn)證應(yīng)用在低內(nèi)存、OOM 邊緣時(shí)是否保持穩(wěn)定以及測試引入新版內(nèi)核、調(diào)優(yōu)參數(shù)、開啟 swap 后系統(tǒng)是變好了還是崩得更快。這篇文章會(huì)按「場景選型 → 最小可復(fù)現(xiàn)命令 → 容器與 NUMA 環(huán)境 → 踩坑排查 → 把壓測結(jié)果落成監(jiān)控閾值」這一條線拆開講。適合剛把內(nèi)存壓力測試寫進(jìn)工作列表的初級運(yùn)維也適合已經(jīng)會(huì)跑幾條命令、但總在壓測結(jié)果里看到玄學(xué)波動(dòng)的熟手。2. 拆場景再選型內(nèi)存壓力測試之前先想清楚要壓哪一層搜“內(nèi)存壓力測試”時(shí)網(wǎng)上大部分教程只會(huì)甩給你一條stress命令然后讓你盯著free -h看。這種做法不是沒用而是太粗糙。內(nèi)存系統(tǒng)不是一塊鐵板壓用戶態(tài)堆、壓內(nèi)核 page cache、壓內(nèi)存帶寬觸發(fā)的問題完全不是同一類。選型順序應(yīng)該是先定義要驗(yàn)證的對象再挑工具。2.1 用戶態(tài)分配、page cache、還是帶寬飽和三類壓力要分開壓最常見的壓測對象是用戶態(tài)進(jìn)程的堆內(nèi)存。進(jìn)程通過malloc或mmap申請?zhí)摂M內(nèi)存只有真正寫數(shù)據(jù)時(shí)才觸發(fā)按需調(diào)頁缺頁異常把物理頁分配進(jìn)來。對這種場景stress-ng --vm、memtester都能做關(guān)鍵是“數(shù)據(jù)要寫進(jìn)去”不能只malloc了不碰。第二類對象是內(nèi)核 page cache。文件讀、寫、fopen 為什么會(huì)吃內(nèi)存因?yàn)閮?nèi)核把讀寫過的頁緩存留在了 page cache 里。要驗(yàn)證一個(gè)服務(wù)在 page cache 漲到占滿可用內(nèi)存時(shí)會(huì)發(fā)生什么常用的工具反而不是stress-ng而是fio配合大文件讀或?qū)?。fio 讓整機(jī)內(nèi)存中大部分變成不可回收或需要 IO 刷出的臟頁這時(shí)候觀察業(yè)務(wù)延遲會(huì)很有意思。第三類對象是內(nèi)存帶寬飽和。多路服務(wù)器最容易遇到的場景是內(nèi)存通道被某些高吞吐線程吃滿導(dǎo)致其他線程 memory latency 飆升。stream、stress-ng --memrate這類命令會(huì)發(fā)起不同讀寫比例的內(nèi)存訪問足以讓內(nèi)存控制器成為瓶頸。這一層與容量無關(guān)即使free顯示內(nèi)存還剩 70%業(yè)務(wù)可能已經(jīng)延遲翻倍。選型前先問自己三句話我要測試的是進(jìn)程能分配多少內(nèi)存還是系統(tǒng)在內(nèi)存滿了之后怎么回收我要看的是 OOM還是性能劣化我壓的是本機(jī)業(yè)務(wù)還是要提前發(fā)現(xiàn) DIMM 硬件故障三個(gè)答案對應(yīng)三套完全不同的工具和方法。2.2 memtester、stress-ng、sysbench、fio邊界在哪里下面把最常用的幾個(gè)工具拉出來對比這也是我選型時(shí)最常翻的手邊表。這里不推薦唯一答案只給邊界條件。工具主要用途驗(yàn)證目標(biāo)短板stress-ng通用系統(tǒng)壓力制造駐留內(nèi)存、swap、CPU、IO、內(nèi)存帶寬等偏向壓力源不做逐位數(shù)據(jù)校驗(yàn)memtester物理內(nèi)存健康測試DIMM 位翻轉(zhuǎn)、地址線故障、讀寫一致性占用大塊連續(xù)內(nèi)存不適合壓業(yè)務(wù)高水位sysbench memory用戶態(tài)內(nèi)存吞吐測試內(nèi)存分配/拷貝速度、多線程并發(fā)擴(kuò)展性場景相對單純不做長時(shí)間駐留fio文件 IO page cache緩存、臟頁回收、寫回壓力配置復(fù)雜跑偏了會(huì)變成磁盤壓測memtester 偏“體檢”新購置服務(wù)器、機(jī)房節(jié)點(diǎn)替換后我會(huì)先跑它跑 5 輪位翻轉(zhuǎn)測試能排除多數(shù)顆粒級故障。stress-ng 偏“破壞性壓力”故意把內(nèi)存水位頂?shù)竭吔缟嫌^察業(yè)務(wù)是不是還能穩(wěn)住。sysbench memory 偏“性能基線”適合回歸測試比如同樣 8 線程分配 1M 塊內(nèi)核從 4.18 升到 5.10 之后吞吐指標(biāo)差幾個(gè)點(diǎn)。fio 偏“緩存路徑”注意它的--direct0和--direct1差很多只有direct0才走 page cache否則直接繞過緩存測裸盤那就跑題了。還有一個(gè)誤用要提前說stress命令不帶-ng的--vm選項(xiàng)很老很有限它只會(huì)做單一的 mmap 分配釋放線程數(shù)多也壓不出真實(shí)業(yè)務(wù)場景現(xiàn)在新項(xiàng)目我基本直接上stress-ng老命令只保留在極簡容器環(huán)境里用。2.3 線程數(shù)、堆大小和負(fù)載的換算先把燃料算好再點(diǎn)火“內(nèi)存壓力測試工具”最容易被忽略的是前置計(jì)算。我見過有人直接在 64C 256G 的機(jī)器上跑stress-ng --vm 128 --vm-bytes 512M結(jié)果 128 個(gè)進(jìn)程同時(shí)往 swap 里塞Linux 內(nèi)核卡在 direct reclaim 里幾分鐘不響應(yīng)。這不是壓測是事故演練。經(jīng)驗(yàn)上先看目標(biāo)水位例如整機(jī) 256G我希望把 Used 推到 90%同時(shí)留下 10% 給 page cache 和 kernel reachable??梢杂胿m_bytes 總內(nèi)存 * 目標(biāo)百分比 - 當(dāng)前已使用內(nèi)存 - 安全余量來反推單進(jìn)程分配量。比如當(dāng)前 free 顯示 used 只有 40G目標(biāo)水位 85%安全余量 8G那么要額外壓的量就是 256 * 0.85 - 40 - 8 169.6G。如果單進(jìn)程分 2G至少需要 85 個(gè)進(jìn)程。線程數(shù)不需要等于 CPU 核心數(shù)內(nèi)存壓力工具的主要資源是頁表和內(nèi)存帶寬線程過多反而會(huì)因?yàn)?CPU 調(diào)度抖動(dòng)影響觀測。計(jì)算完再考慮到 swap如果機(jī)器開啟了 swap--vm-bytes超過物理內(nèi)存剩余后進(jìn)程不會(huì)立刻被 OOM而是先進(jìn)入 swap 累積。這本身是有效測試但要注意耗時(shí)可能數(shù)倍膨脹。我的做法是先跑一輪不開 swap 的高水位基線再單獨(dú)跑一輪“swap 競爭”專項(xiàng)兩輪數(shù)據(jù)分開解讀不要混在一起。3. 最小可復(fù)現(xiàn)的命令組我上線前必跑的幾組內(nèi)存壓力測試這里直接給一套我在 x86_64 Linux 上慣用的命令組按順序跑。每一條命令后面都寫參數(shù)含義和失敗時(shí)先看什么方便你復(fù)制之后自己改。3.1 stress-ng 制造駐留內(nèi)存與 swap 競爭第一步先把內(nèi)存頂?shù)侥繕?biāo)水位。下面的命令用 8 個(gè)子進(jìn)程申請 80% 的可用內(nèi)存每輪做寫分配與校驗(yàn)。# 先記錄壓測前的內(nèi)存基線作為后面對照 free -h mem_before.txt # 8個(gè)vm壓力子進(jìn)程每個(gè)最多分配總內(nèi)存的10%總計(jì)可達(dá)80%左右 # --vm-populate 讓分配出的虛擬頁立即觸發(fā)缺頁并占用物理內(nèi)存 # --vm-method all 輪換寫策略能覆蓋寫入、讀寫、翻轉(zhuǎn)等常見一致性場景 # --metrics-brief 打印每個(gè)壓力項(xiàng)的吞吐和耗時(shí) stress-ng --vm 8 --vm-bytes 80% --vm-method all \ --vm-populate --timeout 360s --metrics-brief stress_report.log 21先看free -h確認(rèn) Used 是否達(dá)到預(yù)期。--vm-bytes 80%這里 80% 是相對于“可用物理內(nèi)存”的百分比不是總內(nèi)存換句話說系統(tǒng)已有的 page cache 會(huì)先被自動(dòng)回收一部分實(shí)際看到的水位可能比預(yù)期低。如果機(jī)器上跑著業(yè)務(wù)我不建議直接給 80%最好先看業(yè)務(wù)預(yù)留水位否則壓測期間業(yè)務(wù)可能直接被 OOM。--vm-populate很多人不加這是踩坑點(diǎn)。不加這個(gè)參數(shù)子進(jìn)程只mmap了地址空間但不主動(dòng)寫頁物理內(nèi)存可能只有 1-2 個(gè)頁面被實(shí)際占用free里 used 一點(diǎn)沒漲整場壓測就變成了地址空間分配測試。加上它之后頁表展開和物理頁分配一起發(fā)生這才是真實(shí)業(yè)務(wù)“突然吃掉大量內(nèi)存”時(shí)的樣子。再加 swap 競爭參數(shù)# --vm-keep 讓子進(jìn)程分配后不釋放持續(xù)駐留模擬常駐內(nèi)存業(yè)務(wù) # --vm-madvise random 提示內(nèi)核用 MADV_RANDOM弱化預(yù)讀/預(yù)分配行為 stress-ng --vm 8 --vm-bytes 80% --vm-keep --vm-madvise random \ --timeout 600s --sync-start加--sync-start是為了讓所有子進(jìn)程對齊啟動(dòng)避免 8 個(gè)進(jìn)程錯(cuò)峰分配導(dǎo)致峰值被拉平。我在壓測一個(gè) Redis 緩存節(jié)點(diǎn)時(shí)用這套命令能穩(wěn)定復(fù)現(xiàn)“部分 key 訪問變慢”的問題因?yàn)榭臻e內(nèi)存被消耗后內(nèi)核對文件讀寫的回彈空間變小。3.2 memtester 把位翻轉(zhuǎn)測試做進(jìn)運(yùn)維巡檢腳本業(yè)務(wù)壓測之后有必要確認(rèn)硬件本身是不是可靠。memtester 是個(gè)老牌工具思路直球分配一段內(nèi)存填已知模式讀回來對比。發(fā)現(xiàn)不一致就認(rèn)為是內(nèi)存顆?;虻刂肪€故障。命令很樸素# 分配 1G 內(nèi)存跑 5 輪完整測試 # 輪數(shù)不是越多越好ECC 內(nèi)存一般 2-5 輪足夠非 ECC 內(nèi)存建議至少 10 輪 memtester 1G 5 memtester_result.log 21 # 檢查退出碼和報(bào)錯(cuò)摘要 # 0 表示全部通過非 0 表示出現(xiàn)錯(cuò)誤繼續(xù)用 dmesg | tail 看硬件報(bào)錯(cuò) memtester 1G 5 /dev/null 21 if [ $? -eq 0 ]; then echo memory test PASS else echo memory test FAIL fi參數(shù)上1G表示申請 1GiB 的連續(xù)內(nèi)存塊建議不要超過單機(jī)可用物理內(nèi)存的一半否則 memtester 還沒開始測試系統(tǒng)就開始 swap測出來的全是 swap 盤 IO不是內(nèi)存故障。輪數(shù)5表示對同一塊地址區(qū)重復(fù)測 5 輪。位翻轉(zhuǎn)是概率性事件輪數(shù)太少抓不到輪數(shù)太多耗時(shí)指數(shù)增長生產(chǎn)環(huán)境停機(jī)窗口撐不住。運(yùn)行邏輯說明memtester 內(nèi)部會(huì)把大塊拆成若干不同大小的 chunk 分別執(zhí)行 32 位、8 位、隨機(jī)數(shù)等測試包括寫全 1、寫全 0、Walking 1 等模式。這類測試不只是驗(yàn)證“能寫入”還會(huì)檢查相鄰地址之間有沒有短路。新機(jī)器開箱時(shí)我會(huì)先跑一次memtester 512M 5如果機(jī)房節(jié)點(diǎn)出現(xiàn)非規(guī)律性程序 segfault也會(huì)用同一條命令排雷。它治不了業(yè)務(wù)水位問題但能少走很多硬件背鍋的彎路。3.3 sysbench 模擬高并發(fā)線程的分配-讀寫-釋放主循環(huán)業(yè)務(wù)代碼里的內(nèi)存模式多數(shù)是“短期分配→寫入→釋放”循環(huán)比如網(wǎng)關(guān)解析報(bào)文、日志批量刷盤。用 sysbench memory 可以模擬多線程同時(shí)做這件事更像一個(gè)高并發(fā)應(yīng)用。# 1M 的分配塊每線程連續(xù)跑 100G 總傳輸量 # threads 對總吞吐影響很大從 4 開始線性加到 16看擴(kuò)展性 sysbench memory --memory-block-size1M --memory-total-size100G \ --memory-access-modeseq --threads8 run--memory-block-size1M改成 4K 會(huì)帶來完全不一樣的結(jié)論塊越小越容易命中 TLB 和 cache吞吐會(huì)虛高塊越大越容易吃內(nèi)存帶寬和物理頁分配。測業(yè)務(wù)真實(shí)負(fù)載時(shí)先查代碼里典型 chunk 是多少比如消息隊(duì)列 4K、對象樹 1M沒查而不拍腦袋。--memory-total-size不是一次性申請 100G而是多個(gè)線程循環(huán)累積傳輸 100G瞬時(shí)峰值并不高。所以要觀察高水位不要用 sysbench它更多是看 CPU 與內(nèi)存帶寬配合后的吞吐變化。我會(huì)把它放在每次內(nèi)核參數(shù)調(diào)優(yōu)后的回歸列表里比如調(diào)整vm.vfs_cache_pressure、vm.swappiness后把前后兩輪 sysbench 數(shù)字對比比看業(yè)務(wù)延遲更穩(wěn)定。4. 在容器和 NUMA 環(huán)境下壓測限定內(nèi)存邊界才叫測到點(diǎn)子上容器普及后內(nèi)存壓力測試的最大變化是“內(nèi)存”有了邊界。裸機(jī)上free -h看到的是整機(jī)容器里應(yīng)用只能拿到 cgroup 限額壓測工具如果不感知這個(gè)邊界就可能把宿主機(jī)或同租戶的其他進(jìn)程一起打爆生產(chǎn)事故分分鐘變成你的事故。4.1 用 cgroup v2 把壓測進(jìn)程關(guān)進(jìn)“2G 的小黑屋”現(xiàn)代 Linux 發(fā)行版基本都在用 cgroup v2/sys/fs/cgroup/memory.max直接寫入字節(jié)數(shù)即可限制。最常見的做法是用systemd-run臨時(shí)起一個(gè) scope把要壓的進(jìn)程塞進(jìn)指定的 memory 上限。# 限制壓測進(jìn)程的內(nèi)存上限為 2Gswap 上限 1G # MemoryMax 是硬限制超過后觸發(fā) OOM 或 reclaim systemd-run --scope -p MemoryMax2G -p MemorySwapMax1G \ -- stress-ng --vm 2 --vm-bytes 1G --timeout 120s這里有三個(gè)細(xì)節(jié)。第一MemoryMax2G是 cgroup v2 的memory.max不是進(jìn)程的ulimit進(jìn)程組里所有任務(wù)共享這 2G。第二如果只設(shè)MemoryMax不設(shè)MemorySwapMax壓測時(shí)會(huì)發(fā)現(xiàn)實(shí)際可用內(nèi)存變成“2G 很大的 swap”數(shù)字對不上所以要做嚴(yán)格內(nèi)存壓測必須同時(shí)限制 swap。第三壓測結(jié)束后馬上看本 scope 的memory.peak文件能拿到過程峰值比壓測腳本里記錄的free更準(zhǔn)確。# 查 scope 的瞬時(shí)內(nèi)存峰值 cat /sys/fs/cgroup/system.slice/run-*.scope/memory.peakcgroup 里還有個(gè)memory.high參數(shù)它是軟限制超過后不立刻 OOM而是開始強(qiáng)制回收回收不過來才會(huì)向 OOM 發(fā)展。用它壓測最接近業(yè)務(wù)容器帶 CPU throttle 時(shí)“慢半拍”的場景。常見做法是systemd-run --scope -p MemoryHigh1G -p MemoryMax2G \ -- stress-ng --vm 2 --vm-bytes 900M --timeout 120s這組命令的意義在于我們能證明“即使進(jìn)程分配總量沒超過限額內(nèi)存回收依舊會(huì)拖累業(yè)務(wù)”。很多做容器容量規(guī)劃的人只看memory.max忽略了memory.high的軟水位才是日常抖動(dòng)來源。4.2 用 numactl 把壓力釘死在指定 NUMA node多路服務(wù)器上內(nèi)存訪問有本節(jié)點(diǎn)和跨節(jié)點(diǎn)之分。壓測如果放任內(nèi)核自動(dòng)分配兩個(gè)進(jìn)程可能被分到不同 NUMA node數(shù)據(jù)的 latency 不穩(wěn)定結(jié)果就成了“抽卡”玄學(xué)。用 numactl 可以把 CPU 和內(nèi)存綁在同一跳數(shù)上# 所有壓測線程固定在 node0 的 CPU 上內(nèi)存也從 node0 分配 # 想測跨 node 影響時(shí)換 --cpunodebind0 --membind1注意兩者要不同 numactl --cpunodebind0 --membind0 \ stress-ng --vm 4 --vm-bytes 4G --timeout 300s--cpunodebind0表示線程只跑在 node0 的核上--membind0表示所有內(nèi)存分配都落在 node0 對應(yīng)的 memory 控制器上。這么一釘壓測數(shù)據(jù)才有可比性。如果想測試的是 NUMA 干擾問題那就改成--membind1讓跨 node 訪問成為常態(tài)看看業(yè)務(wù)是不是有隱藏的放大延遲。調(diào)參前記得用numactl --hardware看機(jī)器拓?fù)溆行┰浦鳈C(jī)在 vCPU 已經(jīng)做了綁核再用 numactl 反而可能提示無法分配。多路物理機(jī)做性能回歸時(shí)這個(gè)綁定動(dòng)作是所有對照組必須保持一致的前置條件。4.3 容器里壓測最容易誤讀的數(shù)據(jù)視圖容器內(nèi)的/proc/meminfo并不總是被運(yùn)行時(shí)重寫。實(shí)際工作中遇到過容器free -h顯示總內(nèi)存和宿主機(jī)一樣大但應(yīng)用進(jìn)程在 2G 時(shí)被殺。原因就是容器看到的是宿主機(jī)的 meminfo而 cgroup 的memory.max才是真正的墻。所以壓測時(shí)判斷容器內(nèi)存不要只看free要看三處# 1) 容器自身視角 cat /etc/... 2/dev/null || true # 2) cgroup v2 限制 cat /sys/fs/cgroup/memory.max # 3) cgroup 當(dāng)前使用量 cat /sys/fs/cgroup/memory.current如果你用的是 Dockerdocker rm后容器直接以宿主機(jī)/proc/meminfo為視圖這是正常現(xiàn)象。在 K8s 里即使部分運(yùn)行時(shí)做了 /proc 隔離很多監(jiān)控組件采集的仍是宿主機(jī)數(shù)據(jù)源。因此寫壓測報(bào)告時(shí)不寫“容器內(nèi) free 顯示多少”而寫“cgroup memory.current 漲到多少占比 memory.max 多少”。容器里壓測還有一層如果應(yīng)用和壓測工具都在同一個(gè) Pod 的同一個(gè)容器里壓測工具在分配一大塊內(nèi)存時(shí)可能先把應(yīng)用自己的內(nèi)存擠 OOM造成“壓測致癱”假象。常見做法是把壓測進(jìn)程放進(jìn) sidecar 容器并給 sidecar 單獨(dú)設(shè)resources.limits.memory這樣壓測側(cè)能分配的范圍與業(yè)務(wù)側(cè)隔離至少不會(huì)一測就把自己家業(yè)務(wù)殺了。5. 避坑與排查內(nèi)存壓測常見“看起來正?!钡募傧笙旅孢@些坑基本是血淚經(jīng)驗(yàn)每一條都按“現(xiàn)象 → 原因 → 解決”寫明。建議先收藏跑壓測翻車時(shí)回來對號入座。5.1 壓測跑一半進(jìn)程被殺但 free 還有幾十GB空閑現(xiàn)象stress-ng報(bào) out of memory stuckdmesg 里出現(xiàn)Killed process但free -h明明顯示還有 40G available。原因進(jìn)程不是被整機(jī)水位殺的而是被 cgroup 限額殺的。容器、systemd-run 或 systemd 服務(wù)都可能在更小的 memory.max 里運(yùn)行。整機(jī)空閑不代表該進(jìn)程有內(nèi)存配額空閑另外也可能是RLIMIT_AS或RLIMIT_DATA撞上了ulimit限制。解決先cat /proc/pid/limits | grep address看當(dāng)前進(jìn)程的地址空間上限再看/sys/fs/cgroup/memory.max是否遠(yuǎn)小于整機(jī)內(nèi)存。如果是在 systemd 服務(wù)里壓測檢查service文件是否帶了MemoryMax。確認(rèn)限制后再?zèng)Q定調(diào)大限額還是縮小--vm-bytes。5.2 內(nèi)存明明打滿了業(yè)務(wù)延遲很高free 卻顯示 available 還有余量現(xiàn)象業(yè)務(wù)接口 P99 延遲從 3ms 漲到 200ms監(jiān)控顯示整機(jī) used 內(nèi)存高但free -h的 available 還剩 20%按常理不該這么卡。原因available 是內(nèi)核估算的可回收頁數(shù)量它把 page cache、slab 可回收部分都算進(jìn)去了但回收這些頁需要時(shí)間。當(dāng)壓力進(jìn)程持續(xù)快速分配臟頁時(shí)內(nèi)核反復(fù)觸發(fā) direct reclaim回收線程和業(yè)務(wù)線程爭搶內(nèi)存管理鎖延遲就上去了??苫厥詹淮砜闪⒖袒厥?。解決不要只看 available配合meminfo里的MemFree、Active(anon)、Shmem和/proc/pressure/memory來看。memory.pressure中的avg10如果持續(xù)大于 60說明內(nèi)存回收壓力已經(jīng)很高業(yè)務(wù)延遲劣化會(huì)先于 OOM 出現(xiàn)。5.3 評測環(huán)境 swap 和 swappiness 不一致結(jié)果完全不可比對現(xiàn)象同一份壓測腳本測試機(jī) A 表現(xiàn)正常測試機(jī) B 直接卡死檢查發(fā)現(xiàn) A 沒開 swapB 開著 16G swap且 swappiness60。原因swap 的影響不是“多了一塊后備空間”那么簡單。開啟 swap 后內(nèi)存壓力上來會(huì)優(yōu)先換出匿名頁頁面換入換出的 IO 增加了全局鎖競爭測試行為從“內(nèi)存到底夠不夠”變成“盤能不能扛住換頁”。兩套參數(shù)下測出的水位閾值完全不同。解決壓測報(bào)告里必須聲明swapoff -a與否、swappiness 值。我一般將環(huán)境組固定為“統(tǒng)一不開 swap”和“統(tǒng)一開 swap 且 swappiness30”兩套分開出結(jié)論。對比基線時(shí)任何改 swap 的變更都要重新壓一組不能沿用舊基線。5.4 如何區(qū)分壓測工具自身分配與真正的內(nèi)存泄漏現(xiàn)象壓測結(jié)束后釋放了所有壓力進(jìn)程但業(yè)務(wù)容器內(nèi)存仍然緩慢上升懷疑是壓測工具讓系統(tǒng)產(chǎn)生了不敢回收的內(nèi)存。原因這往往是工具自身留下的內(nèi)核內(nèi)存。比如stress-ng --vm分配大量頁后快速釋放會(huì)使 slab 中的 vm_area_struct 和頁表頁來不及回收或壓測期間訪問的 tmpfs、SYSV 共享內(nèi)存未清理。過一段時(shí)間后內(nèi)核回收數(shù)值會(huì)回落這不是應(yīng)用泄漏而是不可回收的瞬時(shí)內(nèi)核開銷。解決壓測結(jié)束后先等 5 分鐘再分別對比/proc/meminfo的SReclaimable、PageTables、Shmem。如果這幾個(gè)字段回落緩慢且業(yè)務(wù) RSS 不降才懷疑用戶態(tài)泄漏。壓測期間不要并發(fā)跑多個(gè)工具否則數(shù)據(jù)混在一起就是黑匣子誰也拆不清。5.5 虛擬化環(huán)境中壓測指標(biāo)忽高忽低現(xiàn)象同一臺 KVM 虛擬機(jī)重復(fù)跑十輪 sysbench memory吞吐差 30% 以上物理機(jī)上同樣命令卻很穩(wěn)定。原因云廠商或自建虛擬化平臺的 CPU steal、內(nèi)存 balloon 在后臺隨時(shí)自動(dòng)調(diào)節(jié)。虛擬機(jī)看得到的內(nèi)存地址是虛擬的memory.max可能還會(huì)隨著 balloon 設(shè)備變化而動(dòng)態(tài)收縮。解決壓測前先cat /proc/iomem或者lscpu | grep steal看看是否有虛擬化痕跡有則換到物理機(jī)壓或者至少保證壓測期間宿主機(jī)沒有其他租戶高負(fù)載??巛啽葘r(shí)固定宿主機(jī)負(fù)載不要把虛擬化的波動(dòng)當(dāng)作工具問題。6. 壓測之后怎么落地把 metrics 變成線上監(jiān)控閾值與回歸基線壓測跑完不是收個(gè)日志就完事得把結(jié)果變成能指導(dǎo)線上配置的東西。我最常做的三個(gè)落地動(dòng)作計(jì)算 MemAvailable 的安全下限、采集 memory.pressure 作為延遲劣化預(yù)警、把壓測基線固化成一個(gè)可重復(fù)的回歸腳本。先看安全下限。壓測時(shí)逐秒記錄/proc/meminfo的 MemAvailable 和業(yè)務(wù) P99 延遲找到延遲開始顯著抬升的拐點(diǎn)。比如某網(wǎng)關(guān)服務(wù)在 MemAvailable 低于 4G 時(shí)延遲從 10ms 跳到 80ms那么線上告警閾值就別等到 MemAvailable0 才觸發(fā)。在 Prometheus 里可以用node_memory_MemAvailable_bytes 4e9作為紅色告警黃色告警則抬到 6G。這里的關(guān)鍵是閾值來自你的壓測拐點(diǎn)不是抄來的統(tǒng)一數(shù)字。再看 memory.pressure。Linux 內(nèi)核 4.20 提供 PSI 接口能精確量化內(nèi)存回收對任務(wù)進(jìn)度的阻塞程度。壓測時(shí)單獨(dú)記錄# 每 5 秒采一次 memory pressure 的最近 10 秒平均 while true; do cat /proc/pressure/memory | awk {print $2}; sleep 5; done數(shù)值里some avg10表示有任務(wù)在等待回收的時(shí)間占比full avg10表示所有任務(wù)都在等待的時(shí)間占比。如果壓測中發(fā)現(xiàn)full avg10超過 20 時(shí)業(yè)務(wù)吞吐驟降就可以把這個(gè)指標(biāo)接到監(jiān)控里比固定內(nèi)存使用率更貼近用戶感受。最后是回歸腳本。我會(huì)把同一組壓測命令連同環(huán)境快照寫成一個(gè) shell 文件每次內(nèi)核升級、內(nèi)存調(diào)優(yōu)、容器 runtime 升級后先跑一遍比較三個(gè)數(shù)值stress-ng 的 bogo ops、sysbench 吞吐、以及壓測中出現(xiàn) OOM 的時(shí)間點(diǎn)。不用每次都追求精確只要這些數(shù)字在同一個(gè)量級上下浮動(dòng)不超過 10%就可以放心上線。這也讓我養(yǎng)成了習(xí)慣任何內(nèi)存相關(guān)變更先在壓測環(huán)境制造一次高水位再打電話喊業(yè)務(wù)確認(rèn)觀察窗口。邏輯永遠(yuǎn)別在生產(chǎn)環(huán)境里驗(yàn)證內(nèi)存假設(shè)。希望這份踩坑記錄能讓你少走幾回路也幫你的業(yè)務(wù)在內(nèi)存水位頂點(diǎn)前多一層確定性。本文還有配套的精品資源點(diǎn)擊獲取