Linux 网络协议栈调优:从中断亲和到 eBPF XDP 的极致吞吐之路 Linux 网络协议栈调优从中断亲和到 eBPF XDP 的极致吞吐之路一、当网卡成为天花板百万 QPS 下的内核瓶颈某次线上压测8 核机器跑 HTTP 服务QPS 卡在 45 万上不去。CPU 利用率仅 60%但软中断softirq占比高达 40%。火焰图一烧发现net_rx_softirq占了整个 CPU 时间片的 35%——网卡收包的中断处理把 CPU 吃满了。问题根源Linux 默认的网络收包路径要经过中断、软中断、协议栈解析、socket 缓冲区拷贝每个包至少 4 次上下文切换。在百万 QPS 场景下内核协议栈本身就是瓶颈。本文从中断亲和、RPS/RFS、eBPF XDP 三个层次逐级压榨网络吞吐。二、网络收包路径从网卡中断到用户态的完整链路2.1 默认收包路径的开销分析一个数据包从网卡到应用程序默认路径如下flowchart TD A[网卡收到数据包] -- B[触发硬件中断 IRQ] B -- C[中断处理函数: NAPI 轮询] C -- D[分配 skb 结构体] D -- E[软中断 net_rx_softirq] E -- F[协议栈解析: L2/L3/L4] F -- G[路由查找与 Netfilter] G -- H[socket 匹配与数据拷贝] H -- I[唤醒用户态进程] style B fill:#f96,stroke:#333 style E fill:#f96,stroke:#333 style F fill:#ff9,stroke:#333 style G fill:#ff9,stroke:#333红色节点是中断开销黄色节点是协议栈处理开销。在 100Gbps 网卡上每秒约 1.5 亿个最小包64B默认路径根本扛不住。2.2 三级加速策略级别策略加速原理适用场景L1IRQ 亲和 RPS将中断绑定到指定 CPURPS 做 software RSS10Gbps 以下L2Busy Poll RFS用户态主动轮询减少上下文切换低延迟场景L3XDP / AF_XDP绕过内核协议栈在驱动层处理百万 QPS三、生产级调优实现与性能验证3.1 IRQ 亲和与 RPS 配置#!/bin/bash # irq_affinity_setup.sh # 将网卡中断均匀绑定到指定 CPU 集合避免单核中断瓶颈 INTERFACEeth0 # 排除 CPU0留给系统进程使用 CPU1-CPU7 CPU_MASKfe # 二进制 11111110对应 CPU1-CPU7 # 获取网卡中断号 irq_list$(grep $INTERFACE /proc/interrupts | awk -F: {print $1} | tr -d ) if [ -z $irq_list ]; then echo [ERROR] 未找到网卡 $INTERFACE 的中断 exit 1 fi # 设置 IRQ 亲和性 for irq in $irq_list; do echo $CPU_MASK /proc/irq/$irq/smp_affinity echo [OK] IRQ $irq - CPU mask $CPU_MASK done # 启用 RPS将收包软中断分发到多核 # 计算所有数据 CPU 的掩码CPU1-CPU7 0xfe rps_maskfe rx_queues$(ls -d /sys/class/net/$INTERFACE/queues/rx-* 2/dev/null) for queue in $rx_queues; do echo $rps_mask $queue/rps_cpus echo [OK] $(basename $queue) RPS - CPU mask $rps_mask done # 启用 RFS将包分发到应用程序所在的 CPU # flow_entries 建议为活跃连接数的 2 倍 echo 32768 /proc/sys/net/core/rps_sock_flow_entries for queue in $rx_queues; do echo 4096 $queue/rps_flow_cnt done echo [DONE] IRQ 亲和与 RPS 配置完成3.2 eBPF XDP 快速丢包与重定向XDPeXpress Data Path在网卡驱动层执行 eBPF 程序在 skb 分配之前就做决策比传统 iptables 快 5-10 倍。// xdp_firewall.bpf.c // XDP 防火墙在驱动层过滤恶意流量绕过内核协议栈 #include linux/bpf.h #include linux/if_ether.h #include linux/ip.h #include linux/tcp.h #include bpf/bpf_helpers.h #include bpf/bpf_endian.h // 黑名单 Map存储需要丢弃的源 IP struct { __uint(type, BPF_MAP_TYPE_HASH); __uint(max_entries, 65536); __type(key, __u32); // 源 IP __type(value, __u8); // 标记值 } blacklist SEC(.maps); // 统计 Map记录通过/丢弃的包数 struct { __uint(type, BPF_MAP_TYPE_PERCPU_ARRAY); __uint(max_entries, 2); __type(key, __u32); __type(value, __u64); } stats SEC(.maps); SEC(xdp) int xdp_firewall(struct xdp_md *ctx) { void *data_end (void *)(long)ctx-data_end; void *data (void *)(long)ctx-data; // 解析以太网头 struct ethhdr *eth data; if ((void *)(eth 1) data_end) return XDP_PASS; // 仅处理 IPv4 if (eth-h_proto ! bpf_htons(ETH_P_IP)) return XDP_PASS; // 解析 IP 头 struct iphdr *ip (void *)(eth 1); if ((void *)(ip 1) data_end) return XDP_PASS; // 查黑名单O(1) 哈希查找 __u8 *blocked bpf_map_lookup_elem(blacklist, ip-saddr); if (blocked) { // 命中黑名单在驱动层直接丢弃不进协议栈 __u32 key 1; __u64 *cnt bpf_map_lookup_elem(stats, key); if (cnt) __sync_fetch_and_add(cnt, 1); return XDP_DROP; } // TCP Syn Flood 检测仅放行已建立连接的包 if (ip-protocol IPPROTO_TCP) { struct tcphdr *tcp (void *)ip ip-ihl * 4; if ((void *)(tcp 1) data_end) return XDP_PASS; // 如果是 SYN 包且目标端口不在白名单限速 if (tcp-syn !tcp-ack) { __u16 dport bpf_ntohs(tcp-dest); // 仅放行 80 和 443 if (dport ! 80 dport ! 443) return XDP_DROP; } } __u32 key 0; __u64 *cnt bpf_map_lookup_elem(stats, key); if (cnt) __sync_fetch_and_add(cnt, 1); return XDP_PASS; } char _license[] SEC(license) GPL;3.3 内核参数全量调优#!/bin/bash # network_tuning.sh # 生产级网络协议栈参数调优 # 增大 socket 缓冲区减少丢包 sysctl -w net.core.rmem_max16777216 sysctl -w net.core.wmem_max16777216 sysctl -w net.core.rmem_default1048576 sysctl -w net.core.wmem_default1048576 sysctl -w net.ipv4.tcp_rmem4096 1048576 16777216 sysctl -w net.ipv4.tcp_wmem4096 1048576 16777216 # 增大 backlog 队列应对突发流量 sysctl -w net.core.netdev_max_backlog50000 sysctl -w net.core.somaxconn65535 # TCP 连接优化 sysctl -w net.ipv4.tcp_max_syn_backlog65535 sysctl -w net.ipv4.tcp_tw_reuse1 sysctl -w net.ipv4.tcp_fin_timeout15 sysctl -w net.ipv4.tcp_keepalive_time300 # 开启 TCP Fast Open sysctl -w net.ipv4.tcp_fastopen3 # 减少 TIME_WAIT 占用 sysctl -w net.ipv4.tcp_max_tw_buckets65535 echo [DONE] 网络协议栈参数调优完成3.4 压测数据三级加速效果对比测试环境8 核 Xeon、10Gbps 网卡、HTTP 短连接请求配置QPSP99 延迟软中断 CPU 占比默认配置450K3.2ms42%IRQ 亲和 RPS620K2.1ms28% XDP 防火墙780K1.4ms15% 内核参数调优850K1.1ms12%IRQ 亲和将 QPS 提升 38%XDP 在有恶意流量时效果尤为显著——驱动层丢包比 iptables 快 8 倍。四、协议栈调优的暗面权衡与边界4.1 IRQ 亲和的 NUMA 陷阱多路服务器上网卡中断绑到远端 NUMA 节点的 CPU跨 NUMA 访问内存延迟增加 40%。必须确保中断 CPU 与网卡在同一个 NUMA 节点否则 RPS 的收益会被 NUMA 延迟吃掉。4.2 RPS 的锁竞争RPS 在多队列场景下使用 per-cpu 的 input_pkt_queue但 flow hash 到同一 CPU 的包会竞争 __netif_receive_skb_core 的自旋锁。实测在 16 队列以上时RPS 的边际收益递减应改用多队列网卡的硬件 RSS。4.3 XDP 的兼容性限制XDP 程序运行在驱动层无法访问完整的内核网络栈功能。以下场景禁用 XDP需要 NAT/连接追踪的场景XDP 在 conntrack 之前需要分片重组的场景XDP 看到的是原始帧网卡驱动不支持 XDP部分虚拟网卡不支持 native XDP仅能 fallback 到 generic 模式性能反而更差4.4 内核参数的副作用tcp_tw_reuse1允许复用 TIME_WAIT 连接在 NAT 环境下可能导致连接串扰。netdev_max_backlog设过大在内存紧张时可能触发 OOM。调优不是无脑调大而是根据实际负载精确匹配。五、总结Linux 网络协议栈调优的核心逻辑是减少每个包的处理开销。IRQ 亲和消除中断单核瓶颈RPS/RFS 实现多核负载均衡XDP 在驱动层绕过协议栈直接决策内核参数优化缓冲区与连接管理。三级策略叠加在 10Gbps 环境下将 QPS 从 45 万提升到 85 万软中断 CPU 占比从 42% 降到 12%。但每级优化都有边界IRQ 亲和受 NUMA 拓扑约束RPS 受锁竞争制约XDP 受驱动兼容性限制内核参数受副作用影响。性能调优的本质是在具体硬件拓扑和业务流量模式下找到开销与收益的最优解。