调高长连接服务的连接上限是防止用户被踢的最直接手段,但只调一个参数远远不够,需要从操作系统、网关、应用服务到数据库连接池层层协同调整,才能真正扛住高并发用户不掉线。
很多团队遇到过这样的场景:早上九点半刚过,客服群就炸了,用户反馈”消息发不出去””页面一直在转圈”,后台一查日志,长连接服务报错说连接被重置,或者是客户端莫名其妙断开连接,大多数情况下,这就是连接上限被打满导致的被动踢人。
长连接和短连接不一样,短连接用完就扔,长连接是用户挂着不走,连接数只会越积越多,如果服务器的连接上限没有跟着用户量一起往上调,当并发连接数触顶时,新用户连不上,老用户也会被系统强制清理,这就是用户感知上的”被踢下线”。
连接上限调高前先清点三笔账
先别急着改配置,动手之前得算清楚当前服务的运行情况和余量,没有规划的调参,要么调了个寂寞,要么直接把机器调挂。
先看操作系统能撑多少文件描述符
Linux系统里每个TCP连接都对应一个文件描述符,这个值在系统层面是硬约束,很多运维同学在应用层把参数调得再高,系统根本不买账。
检查当前限制:
ulimit -n
如果输出的是1024或者65535这种偏低的值,就需要先改这个,永久生效要改/etc/security/limits.conf文件:
soft nofile 1048576
hard nofile 1048576
改完记得重新登录或者重启进程。
再确认内核参数有没有锁死连接回收
文件描述符只是第一步,连接频繁建立和断开,会让系统进入TIME_WAIT状态,这些连接会占用系统资源,严重时导致新连接无法建立。
核心参数检查:
sysctl net.ipv4.tcp_tw_reuse sysctl net.ipv4.tcp_fin_timeout
行业共识认为,对于长连接服务,tcp_tw_reuse可以设置为1,tcp_fin_timeout建议调低到30秒左右,长连接本身连接建立频率低,但如果你的服务端会主动断开一些异常连接,这些参数能帮你快速回收资源。
最后清算应用本身的连接数模型
每个连接在应用层会占用内存,Java服务每个连接大概几十KB到几百KB内存,Go服务因为协程模型,单连接内存占用低很多,如果是Java应用,2G堆内存撑几万连接就是极限了,这一点要在部署前就根据机器规格量好力。
Nginx层长连接连接上限调高配置实例
大多数长连接服务前端都会挂Nginx做负载均衡,Nginx这里容易出现的坑是:默认配置下,Nginx和上游服务之间用短连接交互,或者Nginx本身能维持的连接数不够。
Nginx客户端连接数
worker_connections这个参数决定每个worker进程能同时处理的连接数,总连接上限约等于worker_processes乘以worker_connections。
示例配置:
worker_processes auto; events { worker_connections 102400; }
单机10万以上连接数,Nginx是完全能扛住的,重点在于操作系统内核参数的配合,特别是net.core.somaxconn和net.ipv4.ip_local_port_range。
Nginx到后端的长连接复用
长连接服务最怕的不是客户端到Nginx的连接断,而是Nginx到后端服务的连接频繁重建,需要在upstream里开启keepalive:
upstream backend {
server 10.0.0.1:8080;
keepalive 4096;
}
server {
location / {
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_pass http://backend;
}
}
keepalive 4096表示Nginx会保留和上游的4096个空闲长连接用于复用,这个值不建议设置过大,空闲连接太多会白白占着资源,业内专家指出,keepalive数量设置为单机峰值QPS的1/10到1/5比较合适。
Java服务端怎么改连接上限
如果你的长连接服务是Java写的,用的是Tomcat、Netty或者Spring Boot内置容器,配置各有区别。
Spring Boot内置Tomcat核心参数
在application.yml中:
server:
tomcat:
max-connections: 20000
accept-count: 1000
threads:
max: 800
min-spare: 100
这三个参数的含义比较关键:
- max-connections:Tomcat能接受的最大连接数,超出后进入等待队列
- accept-count:等待队列长度
- max-threads:处理请求的工作线程数
连接数上限提高了,线程数也要一起提,但线程数不是越多越好,800个线程对于大部分业务系统已经是上限了,线程切换的开销会让CPU先扛不住。
Netty服务参数配置
Netty的配置更灵活,但要调的参数更多:
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup(Runtime.getRuntime().availableProcessors() 2);
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024)
.childOption(ChannelOption.SO_KEEPALIVE, true)
.childOption(ChannelOption.TCP_NODELAY, true);
这边有一个常见误区:调大SO_BACKLOG不等于调大连接上限。SO_BACKLOG只是等待队列的长度,真正决定连接容量的是workerGroup的线程数和系统文件描述符上限。
Netty是事件驱动模型,一个线程可以处理成千上万个连接,Worker线程数是CPU核数的两倍时,单机支撑10万连接不是问题。
数据库和Redis连接池同样制约长连接容量
连接上限调高之后,用户的连接保住了,但每个连接背后的逻辑层不一定撑得住,最典型的场景:长连接服务收到消息要写数据库,要查Redis,数据库连接池被打满,用户照样表现为”被踢”。
配置连接池时先看数据库侧限制
MySQL默认的max_connections是151,这个数值对于长连接服务来说非常容易触顶。
查看当前数据库连接数:
SHOW VARIABLES LIKE 'max_connections'; SHOW STATUS LIKE 'Threads_connected';
如果Threads_connected经常接近上限,需要先在MySQL侧调整:
SET GLOBAL max_connections = 2000;
同时要注意,连接数不是凭空来的,数据库每维护一个连接都要消耗内存,连接池大小设置在业务QPS的10到20倍左右,已经是比较激进的配置了,更合理的方案是给连接池加排队机制,而不是无限放大连接数。
Redis连接池的微妙之处
Redis默认maxclients是10000,看着很充裕,但实际使用中,每个长连接服务实例如果各自建连接池,上百个实例加起来很快就打满。
常见配置:
maxclients 20000
timeout 300
tcp-keepalive 60
timeout一定要设置,如果不设置,Redis会一直保留空闲连接,连接数会持续上涨,最终把Redis拖垮。tcp-keepalive 60是让Redis每60秒检查一次连接是否存活,有效清理死连接。
连接调高后的心跳机制与防踢策略
参数都调到位了,连接还掉,多半是心跳和超时策略的问题。
TCP层的心跳参数
Linux内核的TCP keepalive默认是2小时才探测一次,这个间隔对于大多数移动端场景来说太长了。
调整方式:
sysctl -w net.ipv4.tcp_keepalive_time=600 sysctl -w net.ipv4.tcp_keepalive_intvl=30 sysctl -w net.ipv4.tcp_keepalive_probes=3
意思是连接空闲600秒后开始探测,每次间隔30秒,连续3次无响应就判定连接失效,这套组合有效地清理半死连接,防止它们占着连接名额。
应用层心跳的设计原则
应用层心跳和TCP心跳是两码事,TCP心跳保证的是网络层面通畅,应用层心跳保证的是业务逻辑存活。
推荐的策略是客户端每30秒发送一次心跳,服务端超过90秒没收到心跳就判定超时,这个比率的逻辑是:至少允许连续两次心跳丢失,才做踢出判定,太灵敏容易误杀正常用户,太迟钝又浪费连接资源。
配套的超时参数建议:
- 服务端读取空闲超时:75秒
- 服务端写出空闲超时:75秒
- 客户端重连间隔:5秒到10秒之间带随机值,避免雪崩式重连
验证调优效果和常见坑
调完参数不能直接上线,要验证,有条件的团队建议用压测工具模拟高并发连接,压测时重点观察两个指标:连接建立成功率和内存增长曲线。
压测时的关注指标
- 连接数达到预期峰值时,CPU是否还有余量
- 内存占用是否稳定在堆内存的70%以下
- 断开连接后系统能否快速回收资源
调高连接上限后反而掉线更频繁的三个原因
- 文件描述符没改彻底:改了
limits.conf但进程没重启,或者systemd服务里被单独限制 - 线程池太小:连接数上去了,线程数没跟着动,大量连接排队等待处理,客户端等不到响应先断了
- 负载均衡器策略问题:比如云上的SLB或CLB默认空闲超时时间只有60秒,需要去控制台把空闲超时调大到300秒
这里说一个可能让你意外的情况:有相当一部分团队把连接上限调高后,发现服务器的负载并没有明显上升,原因在于长连接是”挂机”状态,不传输数据时CPU占用几乎为零,真正影响调优决策的是内存和文件描述符,不是CPU,这个认知能帮你判断该不该买更高配的机器。
长连接连接上限调高的完整操作顺序
按照下面的顺序操作,能避开大部分连锁问题。
第一步:改系统层配置
- 修改
/etc/security/limits.conf,把nofile调高到1048576 - 调整内核参数:
tcp_tw_reuse=1,tcp_fin_timeout=30 - 执行
sysctl -p生效
第二步:改中间件配置
- Nginx的
worker_connections调到102400 - 开启upstream keepalive,设置合理空闲连接数
- 确认Nginx和后端之间的超时时间大于业务心跳间隔
第三步:改应用服务配置
- Java服务调大
max-connections和accept-count - Netty调整
SO_BACKLOG并确认worker线程数 - 同步调整数据库和Redis连接池
第四步:验证与灰度
- 先在一台测试机压测,确认指标正常
- 灰度20%流量,观察一个业务周期
- 全量发布后监控连接数曲线,设置告警阈值在峰值的80%左右
Q&A:长连接连接上限调高常见疑问
客户端的socket连接数上限也需要调吗?
需要,特别是网关机或者推送服务所在的机器,Linux默认的ulimit -n只有1024,客户端单机如果启动多个长连接实例,很容易触顶,调整方法和服务端一样,改limits.conf并重启进程即可。
Nginx的worker_connections调多少才够?
计算公式是预计单机最大并发连接数除以worker进程数,假设单机要承载5万长连接,worker进程数为4,worker_connections至少要设12500,建议在此基础上再留30%余量,直接设20000更稳妥。
长连接服务连接上限提上去之后还需要注意什么?
连接只是入场券,入场之后的业务逻辑才是真正考验,每个连接背后的鉴权状态、会话缓存、未读消息推送队列都会跟着增长,如果发现调高连接上限后服务频繁Full GC,通常是会话数据在内存中堆积过多,需要把会话状态外置到Redis,而不是给服务加更多内存。
首发原创文章,作者:王坚,如若转载,请注明出处:https://idctop.com/article/633672.html





