Linux 内核网络调优实战 📅 2026/6/21 19:53:58 Linux 内核网络调优实战一、CPU 利用率不高吞吐量却上不去高并发服务上线后经常遇到 CPU 利用率只有 40%但吞吐量卡死的情况。问题通常不在应用层而在内核。Linux 网络协议栈的默认配置是针对通用场景设计的未必适合你的高吞吐需求。之前在一台 32 核机器上压测单机 QPS 卡在 12 万。应用层没瓶颈CPU 时间主要花在了软中断和上下文切换上。调整一组内核参数后QPS 直接拉到 28 万。这没什么魔法主要是理解了协议栈的数据流向。二、网络协议栈的数据流与瓶颈一个网络包从网卡到应用层路径比想象中复杂graph TD A[网卡接收数据帧] -- B[DMA写入Ring Buffer] B -- C[网卡触发硬中断 IRQ] C -- D[硬中断处理程序: NAPI轮询] D -- E[软中断 NET_RX_SOFTIRQ] E -- F[IP层处理: netif_receive_skb] F -- G[TCP层处理: tcp_v4_rcv] G -- H[Socket接收缓冲区] H -- I[应用进程 recv/read] J[应用进程 send/write] -- K[Socket发送缓冲区] K -- L[TCP层: tcp_transmit_skb] L -- M[IP层: ip_output] M -- N[网卡驱动: ndo_start_xmit] N -- O[DMA从Ring Buffer读取并发送] style C fill:#ff6b6b,stroke:#333,color:#fff style E fill:#ff6b6b,stroke:#333,color:#fff style H fill:#ffd93d,stroke:#333 style K fill:#ffd93d,stroke:#333红色节点是中断处理黄色节点是缓冲区管理。这两块是调优的重点。关键性能指标指标含义采集方式interrupts/s每秒硬中断数/proc/interruptssoftirq/s每秒软中断数/proc/softirqscontext_switches/s上下文切换频率/proc/statdropped网卡丢包数ethtool -Ssocket buffer fullSocket 缓冲区溢出netstat -s三、内核参数调优与生产配置3.1 中断亲和性与 RPS/RFS网卡中断默认集中在 CPU 0单核容易成为瓶颈。解决方法是把中断分散到多核。#!/bin/bash # irq-affinity-tuning.sh # 将网卡中断均匀分配到多核CPU消除单核中断瓶颈 NICeth0 IRQS$(grep $NIC /proc/interrupts | awk -F: {print $1}) NUM_CPUS$(nproc) i0 for IRQ in $IRQS; do CPU$((i % NUM_CPUS)) MASK$((1 CPU)) HEX_MASK$(printf %x $MASK) echo 设置 IRQ $IRQ - CPU $CPU (mask0x$HEX_MASK) echo $HEX_MASK /proc/irq/$IRQ/smp_affinity i$((i 1)) done # 启用RPSReceive Packet Steering # 将包处理从软中断CPU分发到应用所在的CPU减少缓存失效 for QUEUE in /sys/class/net/$NIC/queues/rx-*/rps_cpus; do echo ff $QUEUE done # 启用RFSReceive Flow Steering # 将包路由到正在处理该流的应用线程所在CPU比RPS更精准 echo 32768 /proc/sys/net/core/rps_sock_flow_entries for QUEUE in /sys/class/net/$NIC/queues/rx-*/rps_flow_cnt; do echo 4096 $QUEUE done3.2 Ring Buffer 与 Socket 缓冲区调优Ring Buffer 是网卡与内核之间的数据通道。太小会丢包太大会浪费内存且增加延迟。#!/bin/bash # buffer-tuning.sh # 调整网卡Ring Buffer和内核Socket缓冲区消除高并发下的丢包和延迟抖动 # 查看当前Ring Buffer大小 ethtool -g eth0 # 设置Ring Buffer为最大值 # 增大Ring Buffer可以吸收突发流量但会增加延迟 ethtool -G eth0 rx 4096 tx 4096 # 调整TCP Socket缓冲区 # tcp_rmem: 接收缓冲区影响吞吐量 # tcp_wmem: 发送缓冲区 echo 4096 87380 16777216 /proc/sys/net/ipv4/tcp_rmem echo 4096 65536 16777216 /proc/sys/net/ipv4/tcp_wmem # 全局Socket缓冲区上限必须大于tcp_rmem/wmem的最大值 echo 16777216 /proc/sys/net/core/rmem_max echo 16777216 /proc/sys/net/core/wmem_max # 全局Socket缓冲区默认值 echo 87380 /proc/sys/net/core/rmem_default echo 65536 /proc/sys/net/core/wmem_default # 启用TCP窗口缩放允许超过64KB的TCP窗口 echo 1 /proc/sys/net/ipv4/tcp_window_scaling3.3 TCP 协议栈深度调优#!/bin/bash # tcp-stack-tuning.sh # TCP协议栈关键参数调优针对高并发短连接和长连接混合场景 # TIME_WAIT优化 # 允许TIME_WAIT Socket复用对短连接高频场景有效 echo 1 /proc/sys/net/ipv4/tcp_tw_reuse # TIME_WAIT最大数量 echo 1048576 /proc/sys/net/ipv4/tcp_max_tw_buckets # FIN_WAIT超时时间缩短到15秒 echo 15 /proc/sys/net/ipv4/tcp_fin_timeout # SYN队列长度 echo 65535 /proc/sys/net/ipv4/tcp_max_syn_backlog # SYN Cookie防御SYN Flood攻击 echo 1 /proc/sys/net/ipv4/tcp_syncookies # Keepalive优化减少空闲连接的检测间隔 echo 30 /proc/sys/net/ipv4/tcp_keepalive_time echo 3 /proc/sys/net/ipv4/tcp_keepalive_intvl echo 3 /proc/sys/net/ipv4/tcp_keepalive_probes # 拥塞控制算法选择 # BBR适合高延迟/有丢包的网络Cubic适合低延迟/低丢包的数据中心内部 echo bbr /proc/sys/net/ipv4/tcp_congestion_control # 本地端口范围扩大可用端口数 echo 1024 65535 /proc/sys/net/ipv4/ip_local_port_range # SOMAXCONNlisten() backlog上限 echo 65535 /proc/sys/net/core/somaxconn3.4 内核旁路技术XDP 快速路径当内核协议栈本身成为瓶颈时XDP 允许在网卡驱动层直接处理包绕过内核协议栈。// xdp_drop_unwanted.c // XDP程序在网卡驱动层过滤不需要的包避免它们进入内核协议栈消耗CPU #include linux/bpf.h #include bpf/bpf_helpers.h #include linux/if_ether.h #include linux/ip.h #include linux/tcp.h // 允许的端口映射表只有这些端口的流量才放行 struct { __uint(type, BPF_MAP_TYPE_HASH); __uint(max_entries, 256); __type(key, __u16); __type(value, __u8); } allowed_ports SEC(.maps); SEC(xdp) int xdp_filter(struct xdp_md *ctx) { void *data (void *)(long)ctx-data; void *data_end (void *)(long)ctx-data_end; // 解析以太网头部 struct ethhdr *eth data; if ((void *)(eth 1) data_end) return XDP_PASS; // 只处理IP包 if (eth-h_proto ! __constant_htons(ETH_P_IP)) return XDP_PASS; // 解析IP头部 struct iphdr *ip (void *)(eth 1); if ((void *)(ip 1) data_end) return XDP_PASS; // 只处理TCP包 if (ip-protocol ! IPPROTO_TCP) return XDP_PASS; // 解析TCP头部 struct tcphdr *tcp (void *)ip ip-ihl * 4; if ((void *)(tcp 1) data_end) return XDP_PASS; // 查询目标端口是否在允许列表中 __u16 dest_port __builtin_bswap16(tcp-dest); __u8 *allowed bpf_map_lookup_elem(allowed_ports, dest_port); if (!allowed) { // 端口不在白名单中直接在驱动层丢弃 return XDP_DROP; } return XDP_PASS; } char _license[] SEC(license) GPL;四、调优的边界与风险内核参数不是越激进越好每个参数都有安全边界。tcp_tw_reuse在 NAT 环境下可能导致连接串扰。如果服务端在负载均衡器后面且负载均衡器复用源端口开启这个参数可能让新连接被误认为是旧连接的延续。必须确认网络拓扑后再开启。Ring Buffer 增大会带来延迟上升。更大的 Buffer 意味着包在队列中等待更久。对于延迟敏感型业务如在线推理Ring Buffer 不宜超过 2048。对于吞吐优先型业务如日志采集可以拉到 4096 甚至更大。XDP 的引入意味着部分网络逻辑从内核移到了 BPF 程序。这增加了调试难度——XDP 程序的 bug 不会产生内核日志只能通过 bpf_trace_printk 调试。同时XDP 程序有指令数限制通常 100 万条复杂逻辑无法实现。BBR 拥塞控制在有缓冲膨胀Bufferbloat的网络中表现优异但在公平性上存在争议。BBR 与 Cubic 混合部署时BBR 流可能抢占 Cubic 流的带宽。在数据中心内部Cubic 仍然是更安全的选择。调优项收益风险适用场景IRQ 亲和性30-50% 吞吐提升无明显风险多核服务器Ring Buffer 扩容消除丢包延迟微增突发流量场景tcp_tw_reuse连接复用率提升NAT 环境风险短连接高频XDP 快速路径10x 包处理能力调试困难DDoS 防御/过滤BBR 拥塞控制高延迟网络吞吐提升公平性争议跨地域传输五、总结Linux 内核调优的本质是理解数据路径找到瓶颈针对性调整。不要盲目照搬参数模板——每台机器的网络拓扑、硬件规格、业务模式都不同参数必须因地制宜。调优的顺序很重要先建监控再定位瓶颈最后调参数。每调一个参数都要验证效果用 before/after 数据说话。内核参数之间可能存在交互效应一次调多个参数会无法归因。一次只调一个验证后再调下一个。性能优化没有终点只有持续迭代。每一次压测都是对系统理解的深化每一个参数调整都是对瓶颈的精准打击。把内核的每一分潜力都榨出来这就是系统工程师的日常。