生产服务器出现CPU负载异常飙高但top中无明显占用进程时,软中断(softirq)往往是罪魁祸首。网络收包软中断NET_RX在万兆网卡满载时可消耗单核100%的CPU周期,导致应用进程响应延迟急剧上升。这类服务器故障排查需要从内核网络栈逐层定位,而非简单查看进程列表。
软中断飙升的现象与初步定位
典型的软中断飙高现象:top命令显示CPU的si(softirq)列达到50%以上,但us和sy列正常。通过/proc/interrupts和/proc/softirqs确认哪个软中断类型在飙高:
# 查看软中断统计(隔1秒取样两次对比)
cat /proc/softirqs; sleep 1; cat /proc/softirqs
# 输出示例:
# CPU0 CPU1 CPU2 CPU3
# NET_RX: 1294382 1245661 1302917 1268930
# NET_TX: 15832 14923 16281 15104
# TIMER: 892341 889022 891563 888447
# 如果NET_RX增长远超其他类型,确认是网络收包软中断
# 查看中断分布
grep -E "eth0|ens|enp" /proc/interrupts
RSS(Receive Side Scaling)将网卡收包中断分散到多个CPU核心。如果所有中断都绑定到CPU0,单核会成为瓶颈:
# 查看网卡多队列配置
ethtool -l eth0
# Channel parameters for eth0:
# Combined: 8
# 查看中断亲和性
for irq in $(grep eth0 /proc/interrupts | cut -d: -f1); do
smp_affinity=$(cat /proc/irq/$irq/smp_affinity)
echo "IRQ $irq -> CPU mask: $smp_affinity"
done
# 修复:将队列均匀绑定到多核
i=0
for irq in $(grep eth0 /proc/interrupts | cut -d: -f1 | tr -d ' '); do
mask=$(printf "%x" $((1 << i)))
echo $mask > /proc/irq/$irq/smp_affinity
echo "IRQ $irq -> CPU$i (mask: $mask)"
((i++))
done
RPS/RFS实现软件层负载分散
部分网卡不支持硬件多队列或队列数少于CPU核数,通过RPS(Receive Packet Steering)在软件层将收包分散到多个CPU处理:
# 启用RPS(以64核为例,所有核参与)
for i in /sys/class/net/eth0/queues/rx-*/rps_cpus; do
echo ffffffff,ffffffff > $i
done
# RFS确保同一连接的包始终在同一CPU处理
echo 32768 > /proc/sys/net/core/rps_sock_flow_entries
for i in /sys/class/net/eth0/queues/rx-*/rps_flow_cnt; do
echo 4096 > $i
done
# XPS优化发包路径
i=0
for i in /sys/class/net/eth0/queues/tx-*/xps_cpus; do
mask=$(printf "%x" $((1 << (i % 64))))
echo $mask > $i
((i++))
done
网络包丢包定位
软中断飙高往往伴随丢包。从网卡硬件层到内核协议栈逐层排查:
# 1. 网卡硬件层丢包
ethtool -S eth0 | grep -iE "drop|discard|miss|error"
# 2. 驱动层丢包 - 查看环形缓冲区
ethtool -g eth0
# Current hardware settings:
# RX: 512 # 偏小,调大
# TX: 512
ethtool -G eth0 rx 4096 tx 4096
# 3. 协议栈层丢包
netstat -su
# Udp:
# packet receive errors
# receive buffer errors
# TCP:
# 1342 resets received
# 56 SYN to LISTEN socket
# 4. conntrack表满导致丢包
cat /proc/sys/net/netfilter/nf_conntrack_count
cat /proc/sys/net/netfilter/nf_conntrack_max
# 临时扩容:
echo 524288 > /proc/sys/net/netfilter/nf_conntrack_max
tcpdump抓包定位网络层问题
# 抓取指定端口的TCP包
tcpdump -i eth0 -nn -w /tmp/capture.pcap port 443 and host 10.0.1.50
# 分析重传
tcpdump -r /tmp/capture.pcap -nn | grep -c "retransmission"
# 用ss查看socket队列积压
ss -tnp | awk '{print $1, $2, $3}' | sort | tail -20
# Recv-Q: 持续>0说明应用读取太慢
# Send-Q: 持续>0说明对端不收或网络拥塞
NAPI与中断轮询优化
NAPI机制在高负载时从中断驱动切换为轮询模式,减少中断风暴:
cat /proc/net/softnet_stat | head
# 第一列:已处理的包总数
# 第二列:轮询模式中处理的数量
# 第三列:因budget耗尽而退出轮询的次数
# 调整netdev_budget
sysctl -w net.core.netdev_budget=600
sysctl -w net.core.netdev_budget_usecs=8000
# 持久化配置
cat >> /etc/sysctl.d/99-network.conf << EOF
net.core.netdev_budget = 600
net.core.netdev_budget_usecs = 8000
net.core.netdev_max_backlog = 10000
EOF
sysctl -p /etc/sysctl.d/99-network.conf
eBPF精准定位内核网络栈丢包
传统工具只能看到丢包总量,eBPF能精确追踪每个包在内核网络栈中的路径和耗时:
# 追踪tcp_v4_rcv耗时
bpftrace -e '
kprobe:tcp_v4_rcv { @start[tid] = nsecs; }
kretprobe:tcp_v4_rcv /@start[tid]/ {
$delta = (nsecs - @start[tid]) / 1000;
printf("tcp_v4_rcv latency=%d us\n", $delta);
delete(@start[tid]);
}'
# 追踪kfree_skb(内核丢弃skb的位置)
bpftrace -e '
kprobe:__kfree_skb {
printf("skb dropped at %p, reason=%d\n", arg1, arg2);
}'
# dropwatch定位丢包位置
dropwatch -l kas
# 1 drops at tcp_rcv_established+0x1a2/0x3f0
# 15 drops at __udp4_lib_rcv+0x1b3/0x290
dropwatch直接显示内核函数级别的丢包位置,结合kernel symbol map可以定位到具体代码行。
生产案例:Nginx代理服务器丢包
某Nginx反向代理服务器在QPS达到5万时出现间歇性502错误。排查过程:
# 1. 现象:CPU si达到60%,但us+sy仅30%
top -d1
# CPU0: 5.0%us, 3.0%sy, 62.0%si <- 软中断异常
# 2. 确认NET_RX软中断
watch -n1 "grep NET_RX /proc/softirqs"
# NET_RX计数每秒增长约200万次,但连接数仅5万
# 3. 检查网卡中断绑定
grep eth0 /proc/interrupts | wc -l
# 只有2个队列,但CPU有16核
# 4. 修复:启用RPS分散到所有核
for i in /sys/class/net/eth0/queues/rx-*/rps_cpus; do
echo ffff > $i
done
# 5. 调大接收缓冲区和backlog
sysctl -w net.core.netdev_max_backlog=250000
sysctl -w net.core.rmem_max=16777216
sysctl -w net.core.wmem_max=16777216
# 修复后:CPU si降至15%,502错误消除,QPS提升到7万+
根因是网卡只有2个硬件队列,中断全部集中在2个CPU核上。启用RPS后将软中断处理分散到全部16核,配合backlog和缓冲区调优,问题彻底解决。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/fu-wu-qi-gu-zhang-pai-cha-shi-zhan-cpu-ruan-zhong-duan-biao/