问题背景
最近遇到一个棘手的问题:
服务器的WebSocket服务在高并发场景下,客户端连接数达到一万多时,服务器的端口资源被耗尽了。导致后面的任何请求都没响应。
通过 netstat 查看连接情况,发现所有连接的本地端口都是从32768开始的,即使我已经将系统的端口范围设置为1024-65535:
sh
[root@server ~]# cat /proc/sys/net/ipv4/ip_local_port_range
1024 65535
但实际分配的端口还是从32768开始,导致高位端口很快被耗尽,新连接无法建立。
问题分析
经过一番调研,发现这个问题在Linux系统中很常见。虽然设置了 ip_local_port_range 为1024-65535,但实际上:
-
glibc的默认行为:很多Linux发行版的glibc硬编码了临时端口分配的起始值为32768
-
内核版本限制:老版本内核(如3.10)没有 ip_unprivileged_port_start 参数
-
历史遗留问题:这是Linux系统的"正常"行为,不是配置错误
我的服务器环境:
-
内核:3.10.0-1127.19.1.el7.x86_64
-
glibc:2.17
-
系统:CentOS 7
解决方案探索
方案一:升级系统(不推荐)
升级glibc和内核到新版本可以支持更灵活的端口分配,但在生产环境中风险极高,容易导致系统崩溃。
方案二:虚拟IP扩容(推荐)
通过在同一台服务器上添加多个虚拟IP,每个IP都拥有独立的端口池,从而成倍扩容端口资源。
虚拟IP方案实施
第一步:查看网络环境
首先确认服务器的网卡和IP配置:
sh
[root@server ~]# ip addr show eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
link/ether 00:16:3e:1f:ee:09 brd ff:ff:ff:ff:ff:ff
inet 1.2.3.17/20 brd 11.22.33.255 scope global dynamic eth0
注:这里的ip随便编的
可以看到主IP是 1.2.3.17/20,子网掩码是 /20。
第二步:添加虚拟IP
根据主IP的网段,添加几个虚拟IP:
sh
ip addr add 1.2.3.100/20 dev eth0
ip addr add 1.2.3.101/20 dev eth0
ip addr add 1.2.3.102/20 dev eth0
这里的ip后三位可以随便写。ip个数最好是100个以内,多了会影响性能。
在没这样添加这些虚拟ip时,Ping这些虚拟ip是ping不通的,添加后就可以ping通了
验证添加成功:
sh
ip addr show eth0
显示出刚才添加的ip就是没问题的
第三步:配置Nginx负载均衡
修改Nginx配置,将流量分发到不同的虚拟IP:
json
upstream websocket_backend {
server 1.2.3.100:38088;
server 1.2.3.101:38088;
server 1.2.3.102:38088;
}
server {
listen 80;
server_name xx.com;
location /请求上下文 {
proxy_pass http://websocket_backend/请求上下文;
proxy_redirect default;
proxy_pass_header Server;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Scheme $scheme;
}
}
第四步:重启服务
重启WebSocket服务端和Nginx,让配置生效:
sh
nginx -t
nginx -s reload
效果验证
查看连接分布
使用 ss 命令查看连接是否被正确分发到不同虚拟IP:
sh
ss -tnp | grep 38088
输出显示连接确实被分发到了不同IP:
sh
CLOSE-WAIT 1 0 1.2.3.100:38088 169.24.13.2:36294
CLOSE-WAIT 1 0 1.2.3.101:38088 169.24.13.2:57430
CLOSE-WAIT 1 0 1.2.3.102:38088 169.24.13.2:37558
这里的169.24.13.2不用管,这是我的nginx用的docker镜像的原因
端口资源扩容效果
-
之前:所有连接都占用 1.2.3.17 的端口池(32768~65535)
-
现在:连接分散到3个虚拟IP,每个IP都有独立的端口池
-
效果:端口资源扩容3倍,极大缓解端口耗尽问题
注意事项
1. 子网掩码的确定
添加虚拟IP时,子网掩码必须和主IP一致。查看方法很简单:
sh
ip addr show eth0
看输出中的 inet 1.2.3.17/20,直接使用 /20 即可。
2. 虚拟IP的持久化
临时添加的虚拟IP重启后会失效,需要写入启动脚本:
sh
# 编辑 /etc/rc.local
ip addr add 1.2.3.100/20 dev eth0
ip addr add 1.2.3.101/20 dev eth0
ip addr add 1.2.3.102/20 dev eth0
# 赋予执行权限
chmod +x /etc/rc.d/rc.local
3. 网络架构要求
-
Nginx和WebSocket服务端必须在同一台服务器
-
虚拟IP必须在同一网段内,不能和其他机器冲突
-
如果是云服务器,可能需要先在控制台申请辅助私网IP
总结
通过虚拟IP + Nginx负载均衡的方案,成功将端口资源扩容了3倍,有效解决了高并发WebSocket场景下的端口耗尽问题。这个方案的优势:
-
实施简单:只需添加虚拟IP和配置Nginx
-
风险较低:不需要升级系统内核
-
效果显著:端口资源成倍扩容
-
扩展性好:可以根据需要继续添加虚拟IP
对于高并发WebSocket、短连接等场景,这是一个非常实用的解决方案。