凌晨三点的告警短信突然砸过来,运维同学在群里喊“Nginx实例OOM了,重启后又炸了”,我当时正盯着电商直播连麦服务的监控面板,红色的内存曲线像山一样往上冲,峰值直接摸到了2G的单实例内存,而平时这个数才200M,这就是我去年遇到的Nginx扛海量长连接时的内存暴涨坑。

一、问题浮现:海量长连接下的内存暴涨现象

1.1 触发场景

当时我们的直播连麦服务,峰值时同时在线的长连接数突破12万,这些连接是前端观众和主播之间的实时音视频交互,一旦建立就不会轻易断开,属于典型的长连接场景。负责负载均衡的Nginx是现成部署的,当时没针对长连接做过特殊调整,只默认用了短连接的配置,结果就踩了坑。

1.2 排查过程

紧急停掉部分实例后,我先查了Nginx的内存占用来源:每个长连接都占了一个固定的4K缓冲区,12万连接就是480M,加上Nginx用来管理连接的控制块,又占了1.5G,加起来刚好是2G左右。再看连接数分布,发现大部分连接都是空闲超过5分钟的无效长连接,这些连接还占着资源,这才找到了问题的核心。

二、核心问题拆解:为什么长连接会吃这么多内存

2.1 长连接的本质

长连接就像你开了个社区便利店,每个顾客进门后就坐在位置上不走(不主动断开),还一直占着一个购物篮(连接句柄)。短连接则是顾客买完东西就放下篮子离开,不会额外占位置。当顾客数量(长连接数)太多时,货架空间(内存)自然不够用,Nginx默认的配置就没考虑这么多顾客的情况。

2.2 内存暴涨的具体原因

两个关键原因:一是Nginx默认的长连接参数没做资源复用,每个新连接都申请新的内存块;二是连接池的超时时间设得太长,空闲了几分钟的连接还没被回收,一直占着内存名额。我当时就犯了这个错,把超时时间设成了30分钟,相当于让没人的座位一直占着,浪费了大量空间。

三、调优实战:负载均衡层的连接池参数调整

3.1 调优前的错误配置示例

技术栈:Nginx 1.20.1

user nginx;
worker_processes auto;
events {
    worker_connections 1024; # 每个worker最多支持1024连接,远远不够12万的需求
}
http {
    upstream backend {
        server 192.168.1.10:8080;
        server 192.168.1.11:8080;
    }
    server {
        listen 80;
        location /live {
            proxy_pass http://backend;
            proxy_http_version 1.1;
            # 未配置长连接复用,默认是短连接逻辑,后来改长连接也没调整参数
        }
    }
}

3.2 调优后的正确配置示例

技术栈:Nginx 1.20.1

user nginx;
worker_processes auto; # 自动匹配CPU核心数,比如8核就开8个worker,充分利用多核能力
worker_rlimit_nofile 65535; # 每个worker进程最多能使用的“连接名额”,要比单个worker的连接数多,避免名额不足
events {
    use epoll; # Linux系统下用epoll IO模型,比默认的poll更高效,适合高并发场景
    worker_connections 65535; # 每个worker最多支持65535个连接,足够承载12万以上的长连接
    multi_accept on; # 允许worker一次性接收多个新连接,不用一个个处理,提高并发效率
}
http {
    # 上游服务连接池配置,核心是复用Nginx到后端的连接
    upstream backend {
        server 192.168.1.10:8080 max_fails=3 fail_timeout=30s; # 后端服务器健康检查,3次失败就标记为故障
        server 192.168.1.11:8080 max_fails=3 fail_timeout=30s;
        keepalive 64; # 每个worker向前端开放的复用长连接总数,控制连接数,避免内存浪费
        keepalive_timeout 60s; # 长连接的超时时间,60秒没数据就自动断开,回收内存
    }
    server {
        listen 80;
        # 前端到Nginx的长连接配置,同样控制资源占用
        keepalive_requests 100; # 单个前端长连接最多处理100个请求,避免长期占着连接不释放
        keepalive_timeout 75s; # 前端连接超时时间,比上游稍长,避免中途断开影响用户体验
        location /live {
            proxy_pass http://backend;
            proxy_http_version 1.1;
            proxy_set_header Connection ""; # 清除默认Connection头,允许长连接复用
            proxy_connect_timeout 5s; # Nginx连接后端的超时时间,5秒连不上就报错,不占用资源
            proxy_send_timeout 10s; # Nginx向上游发数据的超时时间
            proxy_read_timeout 10s; # Nginx从上游读数据的超时时间,比keepalive短,及时回收空闲连接
            proxy_set_header X-Real-IP $remote_addr;
        }
    }
}

3.3 调优后的效果

调整完配置后,重启Nginx,监控面板里的内存曲线瞬间平稳了:单实例内存从2G降到了300M,峰值长连接数12万时,CPU负载从之前的80%降到了20%,再也没有出现OOM告警,直播服务的连麦延迟也降低了15%,因为连接复用省去了握手的时间。

四、应用场景、优缺点与注意事项

4.1 适用场景

这个调优方案适合所有需要海量长连接的场景,比如直播连麦、IM即时通讯、物联网设备长连接、在线游戏对战等,特别是负载均衡层的Nginx节点,只要遇到内存暴涨、连接数打满的问题,都可以用这个思路调整。

4.2 技术优缺点

优点非常明显:一是内存占用直接降低70%以上,解决了OOM问题;二是长连接复用减少了TCP握手的开销,提升了服务响应速度;三是参数调整简单,适合快速落地。缺点是需要根据实际连接数调整keepalive和worker_connections的数值,不能随便设;另外长连接需要配套的心跳检测机制,避免连接中途断开影响用户使用。

4.3 注意事项

有几个关键点要注意:一是keepalive的数值不能设得太大,比如每个worker的keepalive最多设成worker_connections的一半,不然会把连接名额占满;二是超时时间要平衡,太长浪费内存,太短会导致连接复用率低,一般设成60-120秒比较合适;三是要定期检查后端服务器的健康状态,及时剔除故障节点,避免把流量转到故障节点上;四是要根据CPU核心数调整worker_processes,设成auto即可,不用手动指定,更适配不同的服务器配置。

五、总结

这次实战让我明白,高并发长连接的调优,核心不是硬扛连接数,而是做好资源的复用和回收。Nginx的连接池参数不是默认就好用的,需要根据业务场景调整,尤其是像直播连麦这种10万级以上的长连接场景,多花点时间调整参数,就能解决大问题,还能提升服务的稳定性和性能。这个调优方案我后来也用到了公司的IM服务上,同样解决了内存暴涨的问题,给不同基础的开发者分享,希望能帮大家少踩坑。