4. 内核参数调优:网络协议栈关键参数

说到内核参数调优,我得先跟你交个底——这不是玄学,是实打实的数学和工程。我见过太多人一上来就改 rmem_defaultwmem_default,改完发现延迟反而更高了。为什么?因为参数之间是联动的,牵一发而动全身。

今天咱们就聚焦三个最关键的参数:net.core.rmem_defaultnet.core.netdev_budgetnet.core.somaxconn。这三个家伙,一个管内存、一个管处理量、一个管排队深度。你想想看,它们是不是正好对应了网络收发的三个核心环节?

4.1 net.core.rmem_default:接收缓冲区的“起跑线”

这个参数,说白了就是每个 socket 创建时默认分配的接收缓冲区大小。单位是字节。我习惯把它理解为“起跑线”——你给每个连接准备了多少内存来装数据。

默认值:通常 212992(约 208 KB)

低延迟场景建议:4194304(4 MB)或更高

我在项目中遇到过一个问题:某金融交易系统的延迟突然从 10 微秒飙到 200 微秒。查了半天,发现是 rmem_default 太小,导致频繁触发内存回收。数据包还没到应用层,就被内核“请”出去了。嗯,这就像你家的快递柜太小,快递员只能把包裹扔地上——取件时间自然就长了。

调大这个值的好处很明显:

  • 减少内存分配和回收的次数
  • 降低系统调用开销
  • 给 TCP 窗口增长留出空间

但注意,不是越大越好。我曾经把 rmem_default 调到 16 MB,结果内存直接爆了——因为每个连接都占 16 MB,1000 个连接就是 16 GB。所以,你得算清楚:预期并发连接数 × 缓冲区大小 ≤ 可用内存的 30%。这是我个人的经验公式。

调优技巧:如果你用的是 DPDK 或 AF_XDP,这个参数基本不用动。因为数据面已经绕过了内核协议栈。但如果你还在用传统的 epoll + TCP,那这个参数就是你的第一道防线。

4.2 net.core.netdev_budget:一次轮询能处理多少包

这个参数控制的是 NAPI(New API)轮询机制中,一次中断处理函数能处理的最大数据包数量。默认值是 300。什么意思呢?就是网卡触发一次中断,内核最多处理 300 个包就收手。

你可能会问:处理完 300 个包之后呢?剩下的包怎么办?答案是:等下一次中断。这就引出了一个问题——如果流量很大,300 个包根本不够用,就会频繁触发中断,导致 CPU 上下文切换开销飙升。

默认值:300

低延迟场景建议:600 - 1200

高吞吐场景建议:2000 - 4000

我记得有一次帮一个游戏公司调优,他们的游戏服务器每秒钟要处理 50 万个小包。默认的 300 根本扛不住,CPU 软中断占用直接飙到 80%。我把 netdev_budget 调到 2000 后,软中断占用降到了 15%,延迟从 5 毫秒降到了 0.3 毫秒。

但这里有个坑:调太大也不行。为什么?因为 NAPI 是关中断的。如果你一次处理太多包,其他设备的中断就会被饿死。我曾经把 netdev_budget 调到 10000,结果磁盘 IO 延迟暴涨——因为网卡把 CPU 占死了,磁盘中断根本抢不到时间片。

避坑指南:我曾经犯过一个错误——只调了 netdev_budget,没调 netdev_budget_usecs。后者控制的是轮询的最大时间(微秒)。如果你只调包数不调时间,在高延迟链路上可能超时。建议两个一起调:netdev_budget = 600netdev_budget_usecs = 8000

4.3 net.core.somaxconn:连接队列的“天花板”

这个参数决定了每个监听 socket 上,已完成三次握手但还没被应用 accept 的连接队列最大长度。默认值是 128。对于低延迟场景来说,128 实在太少了。

你想想看:如果一瞬间有 1000 个连接同时进来,队列只能装 128 个,剩下的 872 个怎么办?内核会直接丢包,客户端收到 RST。这就是经典的“连接风暴”问题。

默认值:128

低延迟场景建议:4096 - 65535

注意:这个值不能超过 net.core.somaxconn 的上限(通常 65535)

我在做 Redis 集群调优时遇到过这个坑。Redis 的默认 tcp-backlog 是 511,但内核的 somaxconn 只有 128。结果就是:无论 Redis 怎么设置,实际队列长度都被内核限制在 128。客户端稍微一多,连接就失败。调大 somaxconn 后,问题立刻解决。

但这里有个细节:调大 somaxconn 的同时,必须调大应用层的 backlog。比如 Nginx 的 listen backlog、Redis 的 tcp-backlog。否则应用层自己就把队列截断了,内核调再大也没用。

个人习惯:我一般把 somaxconn 设为 65535,然后根据实际并发量调整应用层的 backlog。这样内核层不会成为瓶颈,问题都暴露在应用层,排查起来更方便。

4.4 三个参数的联动关系

这三个参数不是孤立的。我画了一张图,帮你理解它们之间的关系:

网络协议栈关键参数联动关系 网卡硬件 中断/NAPI NAPI 轮询 netdev_budget 数据入队 Socket 缓冲区 rmem_default 应用读取 App 连接队列 somaxconn 三次握手完成 accept() 调优原则: 1. 先调 somaxconn(队列深度)→ 再调 rmem_default(缓冲区大小)→ 最后调 netdev_budget(处理能力) 2. 每个参数都要考虑内存和 CPU 的平衡 3. 调完一定要做压力测试验证

从这张图你能看到:数据从网卡进来,经过 NAPI 轮询(受 netdev_budget 限制),进入 socket 缓冲区(受 rmem_default 限制),最后被应用读取。而连接队列(受 somaxconn 限制)则独立于数据路径,专门管理新连接的排队。

这三个参数就像三个水龙头:netdev_budget 是进水阀,rmem_default 是蓄水池,somaxconn 是入口闸门。任何一个调小了,整个系统都会卡住。我个人的调优顺序是:先调 somaxconn,再调 rmem_default,最后调 netdev_budget。为什么?因为连接队列是入口,缓冲区是中间层,处理能力是出口。从外往里调,不容易出问题。

重要提醒:这些参数都是全局的,会影响所有 socket。如果你只想对特定应用调优,可以用 setsockopt() 在应用层单独设置。比如:setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &size, sizeof(size))。这样更精细,也更安全。

好了,这三个参数的核心逻辑就这些。记住:调优不是一锤子买卖,你得结合自己的业务场景反复试。我每次调完都会用 ss -msar -n DEV 1 观察效果,确认没有副作用才上线。