3、Linux 系统调优:文件描述符限制、TCP/IP 参数调优、内核参数优化
系统调优这件事,说白了就是让操作系统别拖后腿。你代码写得再漂亮,算法再高效,到了 Linux 内核这里被限流了,一切都是白搭。我见过太多团队,上线前只盯着业务代码,结果压测一上,连接数上不去、队列溢出、内存交换频繁——全是系统层面的问题。
这一章,我们就聚焦三个最关键的调优方向:文件描述符、TCP/IP 协议栈、内核参数。这三板斧砍下去,大部分高并发场景下的系统瓶颈都能缓解。
3.1 文件描述符限制:别让系统“拒接”你的连接
每个 TCP 连接在 Linux 里都是一个文件描述符。你想想看,如果系统默认只允许 1024 个文件描述符,那你的服务最多只能同时处理 1024 个连接。对于高并发服务来说,这显然不够。
3.1.1 用户级限制 vs 系统级限制
文件描述符限制分两层:用户级和系统级。用户级限制针对单个进程,系统级限制针对整个操作系统。
| 限制类型 | 配置文件 | 默认值 | 推荐值(高并发) |
|---|---|---|---|
| 用户级(soft) | /etc/security/limits.conf | 1024 | 1000000 |
| 用户级(hard) | /etc/security/limits.conf | 4096 | 1000000 |
| 系统级 | /proc/sys/fs/file-max | 取决于内存 | 2000000+ |
核心原则:系统级限制必须大于所有用户级限制之和。否则,即使你给进程设了 100 万,系统也分配不出来。
3.1.2 配置方法
修改用户级限制,编辑 /etc/security/limits.conf:
* soft nofile 1000000
* hard nofile 1000000
root soft nofile 1000000
root hard nofile 1000000
修改系统级限制:
echo "fs.file-max = 2000000" >> /etc/sysctl.conf
sysctl -p
避坑指南:我曾经遇到过一个问题——明明改了 limits.conf,但进程启动后 ulimit -n 还是 1024。后来发现是 systemd 服务单元里没有设置 LimitNOFILE。如果你用 systemd 管理服务,记得在 .service 文件里加上 LimitNOFILE=1000000。
3.2 TCP/IP 参数调优:让网络栈跑得更快
高并发服务最怕什么?连接建立慢、连接被重置、缓冲区溢出。TCP/IP 协议栈的默认参数是为通用场景设计的,对于高并发服务来说,很多参数都需要调整。
3.2.1 连接队列调优
TCP 连接建立分为两步:SYN 队列(半连接队列)和 Accept 队列(全连接队列)。当 Accept 队列满了,新的连接请求会被直接丢弃。
我个人习惯把 net.core.somaxconn 和 net.ipv4.tcp_max_syn_backlog 都调大:
net.core.somaxconn = 65535
net.ipv4.tcp_max_syn_backlog = 65535
这里有个细节:somaxconn 的值不能超过 65535,而且应用程序在 listen() 时传入的 backlog 参数不能超过这个值。我见过有人把 somaxconn 设成 1024,但应用里 listen(2048),结果实际生效的还是 1024。
3.2.2 TIME_WAIT 优化
TIME_WAIT 状态是 TCP 协议为了保证可靠关闭而设计的。但在高并发短连接场景下,大量 TIME_WAIT 会耗尽端口资源。
我的做法是开启 tcp_tw_reuse 和 tcp_timestamps:
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_timestamps = 1
注意:Linux 4.12 之后已经移除了 tcp_tw_recycle 参数,因为它存在严重的 NAT 兼容性问题。如果你还在用旧内核,千万别开 tcp_tw_recycle,否则会出现连接时通时不通的诡异问题。
3.2.3 端口范围与缓冲区
客户端端口范围默认是 32768-60999,对于高并发客户端来说不够用:
net.ipv4.ip_local_port_range = 1024 65535
TCP 读写缓冲区也要调大,特别是延迟敏感的服务:
net.core.rmem_default = 262144
net.core.wmem_default = 262144
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216
3.3 内核参数优化:内存与调度的艺术
内核参数里有两个我特别关注:vm.swappiness 和 net.core.somaxconn。前者控制内存交换行为,后者我们已经聊过了。
3.3.1 vm.swappiness:别让内存拖慢你的服务
vm.swappiness 控制内核使用交换分区的倾向性。取值范围 0-100,默认 60。值越大,内核越倾向于把不常用的内存页换到磁盘上。
对于高并发服务,内存是宝贵的资源。如果内核频繁进行内存交换,I/O 会成为瓶颈,服务响应时间会急剧上升。
我建议把 swappiness 调低:
vm.swappiness = 10
为什么是 10 而不是 0?因为完全禁用交换在某些极端内存压力下可能导致 OOM Killer 误杀进程。设成 10,内核只在万不得已时才交换。
经验之谈:我曾经在一个 Redis 集群上遇到过诡异的问题——平时延迟 1ms,高峰期突然跳到 100ms。排查了半天,发现是 swappiness 没改,内核在内存紧张时把 Redis 的部分数据页换到了磁盘上。改完 swappiness=10,问题消失。
3.3.2 其他关键内核参数
除了 swappiness 和 somaxconn,还有几个参数值得关注:
| 参数 | 作用 | 推荐值 |
|---|---|---|
| vm.overcommit_memory | 控制内存过量分配策略 | 1(Redis/MySQL 建议) |
| vm.dirty_ratio | 脏页占内存比例上限 | 20 |
| vm.dirty_background_ratio | 后台刷脏页的触发比例 | 10 |
| net.ipv4.tcp_fin_timeout | FIN_WAIT2 状态的超时时间 | 15 |
| net.ipv4.tcp_keepalive_time | TCP keepalive 探测间隔 | 120 |
3.4 本章知识体系
下面这张图梳理了 Linux 系统调优的三个核心维度,以及它们之间的关联关系:
3.5 一键调优脚本
为了方便,我整理了一个常用的 sysctl 配置片段。你可以直接追加到 /etc/sysctl.conf 里:
# 文件描述符
fs.file-max = 2000000
# 连接队列
net.core.somaxconn = 65535
net.ipv4.tcp_max_syn_backlog = 65535
# TIME_WAIT 优化
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_timestamps = 1
net.ipv4.tcp_fin_timeout = 15
# 端口范围
net.ipv4.ip_local_port_range = 1024 65535
# 缓冲区
net.core.rmem_default = 262144
net.core.wmem_default = 262144
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216
# 内存
vm.swappiness = 10
vm.overcommit_memory = 1
vm.dirty_ratio = 20
vm.dirty_background_ratio = 10
# 其他
net.ipv4.tcp_keepalive_time = 120
net.ipv4.tcp_syncookies = 1
小提示:执行 sysctl -p 后,建议用 sysctl -a | grep <参数名> 确认参数是否生效。我习惯把修改前的参数值先备份一下,万一调出问题可以快速回滚。
嗯,到这里 Linux 系统调优的三个核心方向就讲完了。文件描述符限制解决的是“能不能接住连接”的问题,TCP/IP 参数解决的是“连接快不快”的问题,内核参数解决的是“内存和调度稳不稳”的问题。这三层调优做好了,你的服务在系统层面就不会掉链子。