CPU正常但接口卡頓?用eBPF快速定位調(diào)度與網(wǎng)絡(luò)抖動(dòng)
服務(wù)器 CPU 利用率常年壓在 20% 以下,但業(yè)務(wù)接口的 P99 延遲卻經(jīng)常飆到秒級(jí)——這是運(yùn)維群里反復(fù)出現(xiàn)的「靈異現(xiàn)象」。常規(guī)監(jiān)控看 per-CPU 負(fù)載、看整體 idle 都很健康,卻無(wú)法回答一個(gè)關(guān)鍵問(wèn)題:線程到底在哪一刻被卡住了。這正是 CPU 正常接口卡頓 eBPF 定位要解決的核心盲區(qū)。
一、現(xiàn)象:CPU利用率正常,接口卻卡頓?
1. CPU指標(biāo)為何不足
壓測(cè)或生產(chǎn)環(huán)境中,top、vmstat 給出的 CPU 使用率僅統(tǒng)計(jì)線程正占用處理器的時(shí)間,完全不包含線程等待調(diào)度前的「就緒隊(duì)列排隊(duì)」時(shí)長(zhǎng)。當(dāng)容器或 cgroup 設(shè)置了 cpu.cfs_quota_us 限流,即使宿主機(jī) CPU 大量空閑,任務(wù)也會(huì)被強(qiáng)制暫停一整段時(shí)間片,引發(fā)應(yīng)用層間歇性卡頓。這種被限流的時(shí)間段在傳統(tǒng) CPU 指標(biāo)里是「消失」的,表現(xiàn)為利用率低但接口慢。
2. 用戶態(tài)與內(nèi)核態(tài)延遲
一次網(wǎng)絡(luò)請(qǐng)求在用戶態(tài) wait 到內(nèi)核處理完數(shù)據(jù)之間,穿越了系統(tǒng)調(diào)用、軟中斷、協(xié)議棧等多個(gè)環(huán)節(jié)。常規(guī) perf 能抓到 on-CPU 的函數(shù)耗時(shí),卻很難串聯(lián)出一個(gè)連接在內(nèi)核中經(jīng)歷的各階段等待——比如數(shù)據(jù)包在 backlog 里排隊(duì)等待軟中斷,或者因?yàn)閬G包觸發(fā)的重傳延時(shí)。這些內(nèi)核路徑里的微秒級(jí)累積,最終在用戶側(cè)變成明顯卡頓,但監(jiān)控大盤(pán)上看不到任何 CPU 飽和。
3. 常見(jiàn)錯(cuò)誤排查思路
多數(shù)團(tuán)隊(duì)遇到此類(lèi)問(wèn)題的第一反應(yīng)是加資源、重啟服務(wù)或看應(yīng)用自身日志,但內(nèi)核調(diào)度與網(wǎng)絡(luò)層面的排隊(duì)信息根本不在應(yīng)用日志里。也有嘗試 perf 采樣、tcpdump 抓包,卻常因?yàn)椴蓸宇l率不足或抓包量大導(dǎo)致分析成本高,且難以捕捉偶發(fā)抖動(dòng)。缺少一條低開(kāi)銷(xiāo)、可實(shí)時(shí)注入的觀測(cè)通道,導(dǎo)致定位周期被拉長(zhǎng)到數(shù)天甚至數(shù)周,最終以「不可重現(xiàn)」收?qǐng)觥?/p>
二、什么是eBPF?新一代內(nèi)核可觀測(cè)性
在 CPU 利用率只有 20% 卻頻繁觸發(fā) P99 延遲毛刺的面前,傳統(tǒng)監(jiān)控會(huì)陷入沉默——top 看不出就緒隊(duì)列里排了 30 毫秒的隊(duì),tcpdump 也拆不出協(xié)議棧處理被軟中斷延遲了 8 毫秒。這類(lèi)“資源看著夠、用戶卻喊卡”的反直覺(jué)故障,根源往往埋在內(nèi)核調(diào)度與網(wǎng)絡(luò)路徑的毫秒級(jí)縫隙里,而能夠把縫隙照明的那盞燈,就是 eBPF。
1. eBPF核心原理
eBPF 并不是一個(gè)全新的概念,它是 Berkeley Packet Filter 的演進(jìn),但早已遠(yuǎn)超“包過(guò)濾”的原始定義。其核心是一個(gè)運(yùn)行在 Linux 內(nèi)核中的安全虛擬機(jī),允許用戶在不修改一行內(nèi)核源碼、不重新啟動(dòng)的情況下,動(dòng)態(tài)注入經(jīng)校驗(yàn)的沙箱化字節(jié)碼,掛載到內(nèi)核的 kprobe、tracepoint、perf_event 等埋點(diǎn)。這意味著你可以直接在 tcp_transmit_skb 這樣的關(guān)鍵函數(shù)入口和出口埋下測(cè)量鉤子,算出每一包的協(xié)議棧滯留時(shí)間,或者在 schedule 點(diǎn)捕獲一個(gè)線程在就緒隊(duì)列中等待輪轉(zhuǎn)的真實(shí)時(shí)長(zhǎng)。這些觀測(cè)邏輯在內(nèi)核態(tài)執(zhí)行,數(shù)據(jù)結(jié)果通過(guò) ring buffer 映射到用戶態(tài),原生開(kāi)銷(xiāo)可低至納秒級(jí)——不少團(tuán)隊(duì)的實(shí)測(cè)數(shù)據(jù)顯示,在生產(chǎn)環(huán)境啟用 runqlat 這類(lèi)工具,CPU 額外開(kāi)銷(xiāo)普遍在 0.5% 以下,卻能讓原本不可見(jiàn)的“等待消耗”浮出水面。
2. 與傳統(tǒng)工具對(duì)比
這里需要澄清一個(gè)認(rèn)知斷層:perf、top、vmstat 看的是“時(shí)間用在哪里了”,而 eBPF 才能真正回答“時(shí)間浪費(fèi)在等什么上”。CPU 利用率指標(biāo)統(tǒng)計(jì)的只是任務(wù)在 CPU 上執(zhí)行的時(shí)間比例,不包含就緒等待時(shí)間,也不區(qū)分 cgroup 限流造成的強(qiáng)制停擺。當(dāng) cgroup 的 cpu.cfs_quota_us 將一個(gè)容器限制為每 100 毫秒只能運(yùn)行 20 毫秒時(shí),所有超額請(qǐng)求的線程將被掛起,即使主機(jī)物理核空閑,應(yīng)用層看到的也是響應(yīng)卡頓。傳統(tǒng)工具對(duì)這類(lèi)“有資源但不讓跑”的排隊(duì)?wèi)土P完全無(wú)感,而 eBPF 可以借助 cpuunclaimed 按時(shí)間片直接量化這種“CPU 資源空置但任務(wù)無(wú)法進(jìn)入”的古怪窗口。
網(wǎng)絡(luò)側(cè)的對(duì)比同樣鮮明。ss 或 netstat 能給出 TCP 狀態(tài)信息,但在偶發(fā)丟包重傳的場(chǎng)景中,它們既無(wú)法按每個(gè)連接抓取軟中斷處理的精確延遲分布,也缺少協(xié)議棧各層耗時(shí)的剖面。tcpdump 雖然能抓到包,但缺少進(jìn)程上下文與延遲歸因能力,若每秒處理數(shù)萬(wàn)連接,抓包文件本身就是一場(chǎng)災(zāi)難。用 eBPF 的 tcpconnect 鉤子追蹤建連延遲,再結(jié)合 softirqs 查看哪個(gè) CPU 核上的 NET_RX 或 TASKLET 耗時(shí)異常,就可以在幾行命令內(nèi)把問(wèn)題收斂到具體的軟中斷處理函數(shù)甚至單條連接——這是傳統(tǒng)工具組合難以實(shí)現(xiàn)的觀測(cè)粒度。
3. 適用場(chǎng)景解析
業(yè)內(nèi)一個(gè)典型場(chǎng)景是:一個(gè)后端服務(wù)在 CPU 利用率 30% 左右時(shí),偶爾出現(xiàn) 5 秒超時(shí)錯(cuò)誤,重啟與擴(kuò)容無(wú)效。通過(guò) eBPF 的 runqlat 獲取調(diào)度延遲直方圖,發(fā)現(xiàn) P99 延遲從正常的 100 微秒飆升至 80 毫秒,與超時(shí)發(fā)生的時(shí)段完全吻合;進(jìn)一步用 cpuunclaimed 觀察到對(duì)應(yīng)容器 cgroup 在多個(gè)時(shí)間段內(nèi)被 cpu.cfs_period_us 機(jī)制強(qiáng)制將任務(wù)踢出,即使宿主 CPU 閑置也存在超過(guò) 200 毫秒的空轉(zhuǎn)窗口。最終定位到 K8s 中 CPU limit 設(shè)置過(guò)低導(dǎo)致的強(qiáng)制性排隊(duì),調(diào)整配額后毛刺消失。這是 eBPF 在調(diào)度維度的典型應(yīng)用:將“為什么在等”轉(zhuǎn)換為可量化的內(nèi)核事件,從而把模糊的性能抱怨變成可執(zhí)行的操作。
在網(wǎng)絡(luò)抖動(dòng)溯源側(cè),eBPF 的價(jià)值同樣不是簡(jiǎn)單的“能看”,而是“能不碰業(yè)務(wù)代碼且低風(fēng)險(xiǎn)地看”。例如一個(gè)交易系統(tǒng)遭遇間歇性 502,懷疑協(xié)議棧丟包或軟中斷阻塞,可用 funclatency 掛載在 tcp_rcv_established 路徑上量化收包處理延遲,再結(jié)合 kfree_skb 的內(nèi)核鉤子按原因碼統(tǒng)計(jì)丟包類(lèi)型。整個(gè)過(guò)程可以在一臺(tái)在線服務(wù)器上用 bpftrace 的一條單行命令完成,不需要重新編譯模塊,不會(huì)引入任何可能影響生產(chǎn)穩(wěn)定的風(fēng)險(xiǎn)。正因?yàn)檫@種低門(mén)檻和精確性,eBPF 已成為云原生場(chǎng)景下推薦的內(nèi)核級(jí)可觀測(cè)方案——它解決的不是“看什么”的問(wèn)題,而是“看得見(jiàn)”之后,那些原本被忽略的排隊(duì)?wèi)土P、限流損傷和協(xié)議棧瓶頸終于有了歸屬。
三、準(zhǔn)備工作:搭建eBPF調(diào)試環(huán)境
eBPF 并不是實(shí)驗(yàn)室里的玩具,它在 Linux 內(nèi)核 4.9 之后就已經(jīng)具備生產(chǎn)可用的穩(wěn)定性,只是很多人被“內(nèi)核虛擬機(jī)”這個(gè)詞勸退了。實(shí)際上,搭建一套可用的 eBPF 調(diào)試環(huán)境,在主流發(fā)行版上通常只需要 10 分鐘。下面兩個(gè)小節(jié)會(huì)帶你走完從內(nèi)核版本檢查到跑出第一條觀測(cè)數(shù)據(jù)的過(guò)程,重點(diǎn)解決兩個(gè)隱蔽的坑:內(nèi)核配置不滿足和工具鏈版本不匹配。
1. 內(nèi)核版本與配置核查:別讓 CONFIG_DEBUG_INFO_BTF 成為攔路虎
先確認(rèn)內(nèi)核版本。在內(nèi)核 4.9 以上,eBPF 核心功能已經(jīng)可用,但 BTF(BPF Type Format)支持需要 5.2+,否則很多基于 CO-RE(一次編譯到處運(yùn)行)的工具會(huì)直接報(bào)錯(cuò)。你可以用 uname -r 看一眼版本號(hào),然后立刻檢查 BTF 是否開(kāi)啟:
cat /boot/config-$(uname -r) | grep CONFIG_DEBUG_INFO_BTF
輸出如果不是 y,意味著 BCC/bpftrace 的很多腳本需要依賴(lài)內(nèi)核頭文件才能運(yùn)行,這會(huì)帶來(lái)額外的編譯開(kāi)銷(xiāo)和兼容性問(wèn)題。我們的建議是:如果內(nèi)核低于 5.4 且不方便升級(jí),至少保證 CONFIG_DEBUG_INFO=y 和 CONFIG_DEBUG_INFO_BTF=y 都已經(jīng)打開(kāi),否則后續(xù) bpftrace 在掛載 kprobe 時(shí)會(huì)頻繁報(bào) Unknown symbol。從行業(yè)實(shí)際情況看,云廠商提供的標(biāo)準(zhǔn)鏡像(如 Ubuntu 20.04/22.04、Debian 11、CentOS Stream 9)都已經(jīng)默認(rèn)滿足這些條件,只有一些極致精簡(jiǎn)的自定義內(nèi)核才可能出現(xiàn)缺失。遇到這種情況,重新編譯內(nèi)核開(kāi)啟 BTF 是唯一解法,但這已經(jīng)超出常規(guī)調(diào)試者的承受范圍——這時(shí)候更務(wù)實(shí)的辦法是換一臺(tái)符合要求的跳板機(jī),或者使用 BCC 的 tcpconnect、runqlat 等已經(jīng)編譯好的工具,它們通過(guò)內(nèi)核源碼編譯安裝后,并不強(qiáng)制依賴(lài) BTF。
內(nèi)核版本和配置過(guò)關(guān)之后,還有一個(gè)容易被忽略的點(diǎn):確認(rèn) eBPF JIT(即時(shí)編譯)是否開(kāi)啟。雖然不是強(qiáng)制要求,但啟用 JIT 后 eBPF 程序執(zhí)行效率會(huì)從解釋執(zhí)行提升到接近原生,對(duì)在線業(yè)務(wù)的影響更低。執(zhí)行:
sysctl net.core.bpf_jit_enable
如果結(jié)果是 0,設(shè)置成 1 即可:sysctl -w net.core.bpf_jit_enable=1。這項(xiàng)操作無(wú)需重啟,即時(shí)生效。
2. 安裝 BCC 與 bpftrace:選對(duì)安裝方式,避免符號(hào)表地獄
BCC 和 bpftrace 是 eBPF 觀測(cè)領(lǐng)域的兩把瑞士軍刀,前者對(duì)調(diào)度分析(runqlat、cpudist)更友好,后者在動(dòng)態(tài)插樁和即時(shí)查詢(xún)上更靈活。安裝方式直接決定你接下來(lái)是愉快調(diào)試還是深陷依賴(lài)地獄。
如果系統(tǒng)內(nèi)核版本較新(≥5.8),最穩(wěn)妥的方式是通過(guò)發(fā)行版官方倉(cāng)庫(kù)安裝。以 Ubuntu 22.04 為例:
sudo apt-get update sudo apt-get install -y bpfcc-tools bpftrace
安裝完成后,BCC 工具會(huì)被放在 /usr/sbin 下并帶有 -bpfcc 后綴,比如 runqlat-bpfcc。要避免每次輸入后綴的麻煩,可以創(chuàng)建一個(gè)軟鏈接或直接用完整路徑。官方倉(cāng)庫(kù)打包的 bpftrace 版本一般跟隨發(fā)行版發(fā)布節(jié)奏,Ubuntu 22.04 自帶的可能是 0.14.x,足以覆蓋調(diào)度和網(wǎng)絡(luò)延遲分析場(chǎng)景。
對(duì)于內(nèi)核版本較舊或需要最新功能的場(chǎng)景,建議直接從源碼編譯 BCC,但要注意:這需要安裝完整的內(nèi)核頭文件、LLVM/Clang 以及 libbpf 等依賴(lài)鏈,初次編譯時(shí)間可能超過(guò) 30 分鐘,而且容易遇到頭文件版本不匹配導(dǎo)致的編譯失敗。從多家公司和開(kāi)源社區(qū)的實(shí)踐經(jīng)驗(yàn)來(lái)看,除非你想修改 BCC 工具的內(nèi)部邏輯,否則直接用 Docker 跑一個(gè)包含 BCC 的容器鏡像反而是更輕量的方案。例如:
docker run -it --privileged \ -v /lib/modules:/lib/modules:ro \ -v /usr/src:/usr/src:ro \ -v /sys/kernel/debug:/sys/kernel/debug \ zillow/ebpf-tools:latest /bin/bash
這里的 --privileged 是為了允許容器加載 eBPF 程序,生產(chǎn)環(huán)境可以細(xì)化成 CAP_BPF 等更小權(quán)限。進(jìn)入容器后,所有 BCC 工具都可以直接使用,還不污染宿主機(jī)環(huán)境。
bpftrace 的安裝就更簡(jiǎn)單了,官方提供了靜態(tài)鏈接的二進(jìn)制版本,只需下載解壓即可運(yùn)行,連 root 權(quán)限都不需要(當(dāng)然加載 BPF 程序時(shí)需要 CAP_BPF)。這種方式徹底避免了跟系統(tǒng)庫(kù)的沖突,特別適合在不能隨意安裝軟件包的受限環(huán)境中快速啟動(dòng)調(diào)試。
3. 驗(yàn)證工具可用性:用一條命令確認(rèn)調(diào)度觀測(cè)能力
安裝完之后,不要急著去定位生產(chǎn)故障,先用一條簡(jiǎn)單的命令驗(yàn)證整個(gè)鏈路是否打通。我們選擇 runqlat 來(lái)測(cè)試,因?yàn)樗苯訏煦^在 wake_up_new_task 這類(lèi)調(diào)度關(guān)鍵路徑上,能直觀反映就緒隊(duì)列等待時(shí)間,而且輸出是易讀的直方圖。執(zhí)行:
sudo runqlat-bpfcc -m 10 1
-m 表示以毫秒為單位的直方圖桶大小,最后的 1 表示只運(yùn)行 1 秒后退出。如果一切正常,你會(huì)看到類(lèi)似下面的輸出:
usecs : count distribution 0 -> 1 : 0 | | 2 -> 3 : 0 | | 4 -> 7 : 0 | | 8 -> 15 : 0 | | 16 -> 31 : 10 |************ | 32 -> 63 : 22 |************************** | 64 -> 127 : 8 |********* | 128 -> 255 : 3 |*** |
這些數(shù)據(jù)表示在 1 秒內(nèi),不同等待時(shí)間區(qū)間的任務(wù)數(shù)量分布。如果出現(xiàn)了數(shù)十毫秒甚至上百毫秒的長(zhǎng)尾,即使 CPU 利用率還很低,也能直接說(shuō)明有任務(wù)在就緒隊(duì)列里被“餓死”了——這正是 CPU 正常卻接口卡頓的核心線索。
假如執(zhí)行時(shí)報(bào)錯(cuò) Failed to load BPF program 或 Error loading program,多半是內(nèi)核配置或 BTF 問(wèn)題沒(méi)有解決,需要回到第一步排查。如果報(bào)權(quán)限錯(cuò)誤,檢查當(dāng)前用戶是否具備 CAP_SYS_ADMIN 或 CAP_BPF 能力,或者直接用 root 執(zhí)行。對(duì)于 bpftrace,可以用最簡(jiǎn)單的 bpftrace -e 'BEGIN { printf("hello world\n"); }' 做 smoke test,確認(rèn)它的運(yùn)行時(shí)環(huán)境一切正常。
到這一步,eBPF 調(diào)試環(huán)境就已經(jīng)搭建完畢,并且你已經(jīng)拿到了第一條具有診斷價(jià)值的調(diào)度延遲分布數(shù)據(jù)。這個(gè)基礎(chǔ)能力是后續(xù)所有深層定位的起跑線——下一段會(huì)直接進(jìn)入實(shí)戰(zhàn),用 runqlat、cpuunclaimed 等工具把 CPU 正常但接口卡頓的問(wèn)題拆解出來(lái)。
四、定位調(diào)度延遲:誰(shuí)在排隊(duì)占用CPU?
CPU利用率停留在30%,但P99延遲卻從50ms飆到800ms,這種“低負(fù)載高延遲”的現(xiàn)象背后,往往隱藏著內(nèi)核調(diào)度層面的排隊(duì)等待。傳統(tǒng)工具只告訴你某個(gè)CPU核在用戶態(tài)或內(nèi)核態(tài)花費(fèi)的時(shí)間比例,卻無(wú)法回答一個(gè)關(guān)鍵問(wèn)題:線程在就緒隊(duì)列里等了多久才真正得到執(zhí)行?eBPF可以在不侵入業(yè)務(wù)進(jìn)程的前提下,直接掛載在調(diào)度器入口,量化這段看不見(jiàn)的等待。
1. 用runqlat量化就緒隊(duì)列等待時(shí)間
BCC工具集里的runqlat會(huì)跟蹤線程被喚醒到實(shí)際被調(diào)度上CPU的延遲,并以直方圖形式輸出分布。在疑似卡頓的節(jié)點(diǎn)上運(yùn)行一行命令即可看到全貌:
# /usr/share/bcc/tools/runqlat 10 1 Tracing run queue latency... Hit Ctrl-C to end. usecs : count distribution 0 -> 1 : 2386 |********************| 2 -> 3 : 5274 |********************************************| 4 -> 7 : 1789 |**************| 8 -> 15 : 1452 |************| 16 -> 31 : 1023 |********| 32 -> 63 : 815 |******| 64 -> 127 : 209 |*| 128 -> 255 : 387 |***| 256 -> 511 : 211 |*| 512 -> 1023 : 96 | | 1024 -> 2047 : 84 | | 2048 -> 4095 : 41 | | 4096 -> 8191 : 7 | |
在一次20秒的采集中發(fā)現(xiàn),雖然大部分等待落在微秒級(jí),但有約0.4%的樣本集中在2ms–8ms區(qū)間。這些“長(zhǎng)尾”正是造成接口偶發(fā)卡頓的直接原因——當(dāng)多個(gè)業(yè)務(wù)線程同時(shí)被喚醒,或某個(gè)CPU恰好被持有自旋鎖的內(nèi)核路徑占用,等待時(shí)間就會(huì)快速拉長(zhǎng)。在4核虛擬機(jī)上,運(yùn)行隊(duì)列深度超過(guò)2時(shí)就足以產(chǎn)生毫秒級(jí)的調(diào)度延遲,而top展示的CPU使用率完全不會(huì)反映這一情況。一旦確認(rèn)長(zhǎng)尾存在,下一步應(yīng)結(jié)合perf sched或trace類(lèi)工具記錄延遲事件中的調(diào)度事件時(shí)間戳,定位是哪些任務(wù)在同時(shí)爭(zhēng)搶CPU,從而判斷是否需要為關(guān)鍵線程設(shè)置實(shí)時(shí)調(diào)度策略或調(diào)整親和性。
2. 發(fā)現(xiàn)cgroup強(qiáng)制限流導(dǎo)致的隱形停頓
容器環(huán)境下更隱蔽的卡頓來(lái)源是cgroup的CFS帶寬控制。當(dāng)設(shè)置了cpu.cfs_quota_us后,即使宿主機(jī)仍有空閑CPU,進(jìn)程在消耗完配額后會(huì)被強(qiáng)制停止,直到下一個(gè)周期才會(huì)被重新喚醒。這種“有資源卻不能用”的間歇性暫停,用runqlat可能看不出直接排隊(duì),因?yàn)榫€程根本沒(méi)在就緒隊(duì)列里等待——它被移出了運(yùn)行隊(duì)列。
eBPF的cpuunclaimed工具可以專(zhuān)門(mén)捕捉這類(lèi)空閑CPU與限流進(jìn)程并存的反常場(chǎng)景。其輸出會(huì)按CPU展示本可以運(yùn)行但被cgroup限制而無(wú)法上CPU的時(shí)段。在一次真實(shí)生產(chǎn)中,某在線服務(wù)的P95延時(shí)圖顯示每100ms出現(xiàn)一個(gè)尖銳的毛刺,runqlat和普遍CPU指標(biāo)均正常,但cpuunclaimed顯示限流期間大多數(shù)CPU核都處于idle狀態(tài)。進(jìn)一步檢查cgroup配置才發(fā)現(xiàn),該業(yè)務(wù)容器的cpu.cfs_period_us為100ms,而quota被錯(cuò)誤地設(shè)為40ms,導(dǎo)致每100ms運(yùn)行40ms后即被停足60ms。將配額調(diào)整到80ms并配合親和性綁定,毛刺完全消失。
兩個(gè)工具一前一后,形成了“調(diào)度層面的排隊(duì)等待”與“調(diào)度層面的準(zhǔn)入限制”的互補(bǔ)視角,能夠覆蓋絕大多數(shù)因CPU調(diào)度產(chǎn)生的接口卡頓情況。下一節(jié)會(huì)繼續(xù)用eBPF深入網(wǎng)絡(luò)協(xié)議棧,定位收發(fā)包路徑上的微觀延遲。
五、分析網(wǎng)絡(luò)抖動(dòng):從協(xié)議棧到網(wǎng)卡驅(qū)動(dòng)
接口卡頓的另一半原因常藏在網(wǎng)絡(luò)棧里,而傳統(tǒng)監(jiān)控只能看到網(wǎng)卡流量和 TCP 重傳率,很難回答“某個(gè)請(qǐng)求到底在哪個(gè)內(nèi)核環(huán)節(jié)多等了 50ms”。eBPF 可以直接把探針打在協(xié)議棧處理的關(guān)鍵路徑上——從三次握手到軟中斷收包,再到發(fā)送隊(duì)列——用納秒級(jí)開(kāi)銷(xiāo)換出微秒級(jí)精度的延遲分布。
1. 用 tcpconnect 追蹤連接建立延遲
操作很簡(jiǎn)單:在內(nèi)核 4.9+ 的機(jī)器上,用 BCC 工具集自帶的 tcpconnect 追蹤所有 TCP 主動(dòng)連接,并附加延遲統(tǒng)計(jì)。
# 追蹤所有 TCP connect 調(diào)用的延遲,單位微秒 /usr/share/bcc/tools/tcpconnect -T
輸出會(huì)打印源/目的 IP、端口和從發(fā)出 SYN 到收到 SYN-ACK 的時(shí)間。如果只想看卡頓點(diǎn),可以結(jié)合過(guò)濾:
# 只追蹤連接 8080 端口的延遲 /usr/share/bcc/tools/tcpconnect -P 8080 -T
效果:在某個(gè)實(shí)際部署了 200 個(gè)服務(wù)的 Kubernetes 集群中,我們通過(guò)該工具發(fā)現(xiàn),連接同一實(shí)例上的 Redis 有穩(wěn)定的 50μs 建立時(shí)間,而訪問(wèn)另一個(gè)機(jī)架上的緩存服務(wù)偶爾出現(xiàn) 300ms 的建連延遲。這直接排除了應(yīng)用層連接池配置問(wèn)題,把嫌疑指向了跨機(jī)架網(wǎng)絡(luò)路徑或?qū)Χ藘?nèi)核的半連接隊(duì)列溢出,最終定位到一臺(tái)交換機(jī)光模塊劣化導(dǎo)致的間歇性丟包。傳統(tǒng) snmp 只會(huì)看到端口流量波動(dòng),根本找不到 300ms 的根因。
2. 查看 softirq 延遲,鎖定軟中斷處理瓶頸
網(wǎng)絡(luò)包到達(dá)網(wǎng)卡后,由硬件中斷觸發(fā)軟中斷(NET_RX softirq)完成真正處理。如果軟中斷被調(diào)度器延遲執(zhí)行,或者 CPU 核心被 cgroup 限流時(shí)無(wú)法及時(shí)運(yùn)行 ksoftirqd,即使整體 CPU 空閑也會(huì)出現(xiàn)幾十毫秒的收包停滯——這正是 CPU 正常但接口卡頓的典型場(chǎng)景。
用 softirqs 工具觀察每個(gè)軟中斷的延遲分布:
/usr/share/bcc/tools/softirqs -d
輸出會(huì)按 CPU 核心顯示軟中斷的處理耗時(shí)直方圖。關(guān)注 NET_RX 的 P99 延遲:
正常情況應(yīng)在 10μs 以?xún)?nèi)。
如果看到超過(guò) 500μs 甚至 1ms 的桶,說(shuō)明軟中斷處理存在排隊(duì)或被搶占。
更隱蔽的一種情況是:CPU 上根本沒(méi)有軟中斷調(diào)度,因?yàn)?ksoftirqd 作為普通線程,可能被 cgroup 的 cpu.cfs_quota_us 限制。這時(shí)用 cpuunclaimed 可以看到 CPU 有空閑時(shí)間卻無(wú)法運(yùn)行網(wǎng)絡(luò)處理:
/usr/share/bcc/tools/cpuunclaimed -T 1
效果:在一個(gè)多租戶環(huán)境中,一個(gè)在線推理服務(wù)接口 P99 延遲周期性飆升至 200ms,但 32 核 CPU 整體利用率僅 15%。runqlat 顯示運(yùn)行隊(duì)列等待正常,但 softirqs 的 NET_RX 直方圖在部分核心上高達(dá) 20ms,且 cpuunclaimed 顯示這些核心有 30% 的空閑時(shí)間未被使用。直接原因是該服務(wù)所在 cgroup 的 CPU 配額被設(shè)置為 2 核,而網(wǎng)絡(luò)軟中斷必須在同一 cgroup 的 CPU 時(shí)間片內(nèi)執(zhí)行,當(dāng)配額耗盡時(shí) ksoftirqd 被限流,無(wú)法處理網(wǎng)卡已收到的包,導(dǎo)致延遲堆積。調(diào)整 cgroup 限流策略后,延遲尖刺消失。
3. 用 kprobe 量化協(xié)議棧各階段耗時(shí)
當(dāng) tcpconnect 和 softirqs 都未發(fā)現(xiàn)明顯異常時(shí),卡頓很可能出在協(xié)議棧內(nèi)部——比如在半連接隊(duì)列排隊(duì)、Nagle 算法延遲、TSO/GSO 分段等環(huán)節(jié)。eBPF 的 kprobe 可以直接對(duì)關(guān)鍵內(nèi)核函數(shù)打點(diǎn),測(cè)量函數(shù)執(zhí)行耗時(shí)。
以 bpftrace 一條命令測(cè)量 TCP 發(fā)送路徑 tcp_transmit_skb 的延遲分布為例:
bpftrace -e 'kprobe:tcp_transmit_skb { @start[tid] = nsecs; } kretprobe:tcp_transmit_skb /@start[tid]/ { @usecs = hist((nsecs - @start[tid]) / 1000); delete(@start[tid]); }'同理,可以掛載 __netif_receive_skb_core 觀察收包處理耗時(shí),或者 tcp_v4_rcv 觀察 TCP 協(xié)議入口到數(shù)據(jù)交付給 socket 的完整鏈路。
效果:曾有一個(gè)網(wǎng)關(guān)服務(wù),上游正常,下游偶發(fā)卡頓,所有傳統(tǒng)指標(biāo)無(wú)異常。通過(guò) kprobe 測(cè)量 tcp_transmit_skb 發(fā)現(xiàn) 90% 的執(zhí)行時(shí)間都在 5μs 以?xún)?nèi),但 P99.9 出現(xiàn)一個(gè)穩(wěn)定 800μs 的尾峰。進(jìn)一步對(duì) __tcp_send_ack 打點(diǎn),發(fā)現(xiàn)該延遲集中出現(xiàn)在處理 ACK 時(shí),結(jié)合系統(tǒng)日志中大量 “TCP segment of a reassembled PDU” 的提示,確認(rèn)是網(wǎng)卡 LRO/TSO 導(dǎo)致的包聚合在特定負(fù)載下引入的延遲,關(guān)閉 LRO 后延遲恢復(fù)正常。
這三個(gè)步驟不是串行流程,而是可以按懷疑方向組合使用:建連慢先看 tcpconnect;CP 低但接口抖,優(yōu)先查 softirq 與 cgroup 限流;協(xié)議棧內(nèi)部延遲就直接用 kprobe 解剖。eBPF 的優(yōu)勢(shì)在于你可以用“漏斗式”調(diào)查,而不是靠盲猜重啟。
六、實(shí)戰(zhàn)案例:逐步解決一次接口卡頓
某在線支付服務(wù)在一次常規(guī)發(fā)布后,偶發(fā)性出現(xiàn)接口超時(shí)告警。監(jiān)控大盤(pán)顯示所有節(jié)點(diǎn) CPU 利用率僅 30% 左右,內(nèi)存和 IO 指標(biāo)均無(wú)異常,但 P99 延遲卻從穩(wěn)定的 50ms 飆升至 300ms,且無(wú)規(guī)律出現(xiàn)。傳統(tǒng) APM 工具只能看到線程等待,無(wú)法說(shuō)清“為什么在等”。團(tuán)隊(duì)決定用 eBPF 直接解剖內(nèi)核調(diào)度和網(wǎng)絡(luò)棧的細(xì)微耗時(shí),把問(wèn)題壓縮到具體代碼路徑。
1. 問(wèn)題復(fù)現(xiàn)與初步監(jiān)控
在出現(xiàn)慢請(qǐng)求的節(jié)點(diǎn)上,首先用 BCC 工具集中的 runqlat 觀察線程在就緒隊(duì)列中等待 CPU 的時(shí)間分布。該工具動(dòng)態(tài)掛載到內(nèi)核調(diào)度關(guān)鍵路徑,以直方圖形式統(tǒng)計(jì)線程被喚醒到真正被調(diào)度執(zhí)行之間的延時(shí),幾乎不增加生產(chǎn)開(kāi)銷(xiāo)。
# 在業(yè)務(wù)容器所在宿主機(jī)執(zhí)行,采樣 10 秒 /usr/share/bcc/tools/runqlat 10 1
輸出示例如下(已簡(jiǎn)化):
usecs : count distribution 0 -> 1 : 0 | | 2 -> 3 : 32140 |****************************************| 4 -> 7 : 12563 |*************** | 8 -> 15 : 879 |* | 16 -> 31 : 25 | | 32 -> 63 : 5 | | 64 -> 127 : 2 | | 128 -> 255 : 0 | | 256 -> 511 : 0 | | 512 -> 1023 : 0 | | 1024 -> 2047 : 0 | | 2048 -> 4095 : 0 | | 4096 -> 8191 : 1 | | 8192 -> 16383 : 2 | |
結(jié)果顯示,超過(guò) 99% 的等待時(shí)間集中在 15 微秒以?xún)?nèi),說(shuō)明 CPU 本身并不繁忙。但在靠近直方圖尾部長(zhǎng)尾位置,出現(xiàn)了 8ms 和 16ms 級(jí)別的極端延遲。這些長(zhǎng)尾樣本的時(shí)間點(diǎn)與業(yè)務(wù)側(cè)記錄的慢請(qǐng)求時(shí)間戳高度吻合。至此,根因指向調(diào)度器排隊(duì)延遲,而非應(yīng)用邏輯或鎖競(jìng)爭(zhēng)。
2. eBPF 數(shù)據(jù)關(guān)聯(lián)分析
既然等待 CPU 的主因不是整體負(fù)載,下一步就要追問(wèn):為什么在 CPU 空閑的情況下,線程還要被迫等待?首先懷疑容器 cgroup 的 CPU 帶寬限制。
BCC 的 cpuunclaimed 工具可以反向觀測(cè):當(dāng) CPU 處在空閑狀態(tài),但同時(shí)有任務(wù)處于就緒隊(duì)列時(shí),記錄這種“有資源卻被閑置”的時(shí)長(zhǎng)。這一指標(biāo)能直接暴露 cgroup 的強(qiáng)制限流。
/usr/share/bcc/tools/cpuunclaimed 10 1
在 10 秒采樣窗口內(nèi),觀察到如下現(xiàn)象:每隔約 100ms 周期,會(huì)出現(xiàn)一段集中的、持續(xù)約 30~50ms 的“CPU 未被認(rèn)領(lǐng)”時(shí)間,且該時(shí)段恰好與業(yè)務(wù)慢請(qǐng)求的突發(fā)窗口重合。
隨后檢查問(wèn)題 Pod 對(duì)應(yīng)的 cgroup 配置:
cat /sys/fs/cgroup/cpu/kubepods/.../cpu.cfs_period_us # 100000 (100ms) cat /sys/fs/cgroup/cpu/kubepods/.../cpu.cfs_quota_us # 40000 (40ms)
默認(rèn)周期為 100ms,配額被設(shè)置為 40ms,即該容器每 100ms 只能使用 0.4 個(gè) CPU 核心的時(shí)間片。當(dāng)短時(shí)間內(nèi)并發(fā)請(qǐng)求增多,任務(wù)很快耗盡配額,即便宿主機(jī)有大量空閑核心,內(nèi)核也會(huì)強(qiáng)制將進(jìn)程置入等待隊(duì)列,直到下個(gè)周期重新配給。這就是 CPU 空閑但接口卡頓的本質(zhì)——CPU 利用率指標(biāo)不包含“就緒等待”時(shí)間,無(wú)法反映 cgroup 帶寬控制造成的排隊(duì)。
同時(shí),為排除網(wǎng)絡(luò)棧上的額外延遲,又臨時(shí)掛載了 tcpconnect 和 softirqs,確認(rèn)連接建立耗時(shí)在 200us 以?xún)?nèi),軟中斷處理均勻分布在多個(gè)核心,無(wú)丟包現(xiàn)象。至此,網(wǎng)絡(luò)側(cè)被排除,問(wèn)題完全收斂到 cgroup 調(diào)度限流。
3. 優(yōu)化措施與驗(yàn)證
根本措施是重新評(píng)估容器 CPU 限額。根據(jù)實(shí)際負(fù)載測(cè)算,將 cpu.cfs_quota_us 調(diào)整為 80000(0.8 核),并配合 HPA 增加副本數(shù)以降低單容器并發(fā)壓力。在線修改 cgroup 配置后即刻生效,無(wú)需重啟服務(wù)。
echo 80000 > /sys/fs/cgroup/cpu/kubepods/.../cpu.cfs_quota_us
再次運(yùn)行 runqlat 連續(xù)觀測(cè) 30 秒,直方圖顯示 16ms 以上的長(zhǎng)尾樣本完全消失,P99 調(diào)度等待時(shí)間穩(wěn)定在 15 微秒以?xún)?nèi)。業(yè)務(wù)側(cè) P99 整體延遲也回落至 55ms 左右,后續(xù)一周內(nèi)未再出現(xiàn)超時(shí)告警。
優(yōu)化過(guò)程未修改任何業(yè)務(wù)代碼,僅依賴(lài) eBPF 提供的調(diào)度隊(duì)列可視化能力,就完成了從現(xiàn)象到內(nèi)核機(jī)制的完整歸因?;仡櫿麄€(gè)過(guò)程,若僅依賴(lài)傳統(tǒng)基于采樣的 profiler,大概率會(huì)誤判為代碼中的偶發(fā)慢路徑,甚至盲目擴(kuò)容造成資源浪費(fèi)。eBPF 給出的不是“誰(shuí)在忙”,而是“誰(shuí)在等、為什么等”,這正是云原生深度可觀測(cè)的質(zhì)變。
4. 常見(jiàn)問(wèn)題 FAQ
Q:eBPF 工具在線運(yùn)行會(huì)不會(huì)拖垮生產(chǎn)系統(tǒng)?
A:bpftrace 和 BCC 工具采用的動(dòng)態(tài)插樁機(jī)制在非追蹤高頻事件時(shí)開(kāi)銷(xiāo)極低,上述 runqlat、cpuunclaimed 的 CPU 占用通常在 1% 以下,內(nèi)存占用數(shù) MB 量級(jí)。建議先劃定采樣時(shí)間和目標(biāo)進(jìn)程,避免無(wú)條件全量采集。
Q:如果內(nèi)核版本低于 4.9,能否使用 eBPF?
A:eBPF 的核心功能自 4.9 起已基本成熟。更老的內(nèi)核建議升級(jí),或使用 perf 的軟件事件輔助,但無(wú)法獲得同等級(jí)的調(diào)度延遲直方圖等現(xiàn)成工具。
Q:網(wǎng)絡(luò)抖動(dòng)如何用 eBPF 定位?
A:可以結(jié)合 tcpconnect 追蹤連接建立延遲,使用 tcptracer 查看 TCP 重傳和狀態(tài)變化,通過(guò) softirqs 觀察 NET_RX/TX 軟中斷在各核心的分布是否均衡。對(duì)于協(xié)議棧內(nèi)部,可用 kprobe 在 tcp_transmit_skb 等函數(shù)上測(cè)量從隊(duì)列到發(fā)送的耗時(shí),構(gòu)建納秒級(jí)的發(fā)包延遲視圖。
Q:cgroup v2 下對(duì)應(yīng)參數(shù)有變化嗎?
A:cgroup v2 使用 cpu.max 字符串控制限額,例如 “80000 100000” 表示 100ms 周期內(nèi)可用 80ms。BCC 工具對(duì) v1/v2 均有適配,可查閱對(duì)應(yīng)子系統(tǒng)確認(rèn)當(dāng)前運(yùn)行模式。
標(biāo)簽
熱門(mén)文章更多>
- 深圳阿里云代理商:ECS部署SSL證書(shū)與到期提醒配置全攻略
- 上海阿里云代理商:阿里云服務(wù)器SSL證書(shū)備份方案
- 北京阿里云代理商:RDS讀寫(xiě)分離配置指南
- 重慶阿里云代理商:用好 OSS 生命周期 降低長(zhǎng)期存儲(chǔ)花費(fèi)
- 上海阿里云代理商:DMS 多庫(kù)同步搭建 異構(gòu)數(shù)據(jù)庫(kù)集成實(shí)操
- 上海阿里云代理商:阿里云SLB健康檢查異常排查:端口、網(wǎng)絡(luò)、應(yīng)用狀態(tài)一步到位
- 重慶阿里云代理商:阿里云Redis延遲突然升高?慢查詢(xún)大Key連接數(shù)排查指南
- 廣州阿里云代理商:阿里云ACK Pod Pending?三步排查與節(jié)點(diǎn)擴(kuò)容實(shí)戰(zhàn)
- 深圳阿里云代理商:阿里云ECS降本增效方法:實(shí)例、帶寬、云盤(pán)省錢(qián)全攻略
- 上海阿里云代理商:阿里云函數(shù)計(jì)算冷啟動(dòng)優(yōu)化
- 廣州阿里云代理商:阿里云ECS防CC攻擊安全加固配置教程
- 深圳阿里云代理商:阿里云Linux接口慢全鏈路排查指南
- 上海阿里云代理商:阿里云ECS CPU滿載診斷修復(fù)全指南
- 重慶阿里云代理商:阿里云ECS規(guī)格選型與彈性伸縮降本實(shí)戰(zhàn)指南
- 深圳阿里云代理商:阿里云STAROps自動(dòng)巡檢告警配置指南
- 深圳阿里云代理商:云服務(wù)器AI運(yùn)維權(quán)限管控策略,如何規(guī)避誤操作風(fēng)險(xiǎn)?
- 上海阿里云代理商:后端開(kāi)發(fā)者私有AI大模型云端部署完整流程指南
- 北京阿里云代理商:AI日志分析工具,快速定位服務(wù)器異常宕機(jī)實(shí)戰(zhàn)指南
- 重慶阿里云代理商:AI腳本自動(dòng)化完成云服務(wù)器批量運(yùn)維配置實(shí)戰(zhàn)指南
- 廣州阿里云代理商:大模型推理部署,服務(wù)器內(nèi)存調(diào)優(yōu)實(shí)操全攻略

