4. 内核参数调优:网络协议栈关键参数
说到内核参数调优,我得先跟你交个底——这不是玄学,是实打实的数学和工程。我见过太多人一上来就改 rmem_default、wmem_default,改完发现延迟反而更高了。为什么?因为参数之间是联动的,牵一发而动全身。
今天咱们就聚焦三个最关键的参数:net.core.rmem_default、net.core.netdev_budget 和 net.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 = 600,netdev_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 轮询(受 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 -m 和 sar -n DEV 1 观察效果,确认没有副作用才上线。