线上服务突然大量Connection refused,原来是这个队列满了

上周线上告警,服务间调用大量超时,日志里全是Connection refused。排查发现是TCP全连接队列满了,很多人不知道这个坑。

先搞懂TCP连接的两个队列

客户端发起连接时,服务端有两个队列在工作:

客户端                        服务端
  |                             |
  |-------- SYN --------->     |
  |                       [半连接队列:存放SYN_RECV状态的连接]
  |<------ SYN+ACK -------|    |
  |                             |
  |-------- ACK --------->     |
  |                       [全连接队列:存放ESTABLISHED但未被accept的连接]
  |                             |
  • 半连接队列(SYN Queue):收到SYN后,等待第三次握手
  • 全连接队列(Accept Queue):三次握手完成,等待应用程序accept

任何一个队列满了,新连接都会被拒绝。

实战:怎么知道队列满了

1. 查看全连接队列溢出

# 查看溢出统计(数值持续增长说明有问题)
netstat -s | grep -i "listen"

# 输出示例:
# 12345 times the listen queue of a socket overflowed
# 12345 SYNs to LISTEN sockets dropped

或者用ss命令:

# 查看每个监听端口的队列情况
ss -lnt

# 输出说明:
# Recv-Q:当前全连接队列中的连接数
# Send-Q:全连接队列最大容量

# 示例输出:
# State    Recv-Q  Send-Q  Local Address:Port
# LISTEN   0       128     *:8080
# LISTEN   129     128     *:3306   ← 这个有问题!Recv-Q > Send-Q

关键指标:如果Recv-Q接近或超过Send-Q,说明队列快满了或已经满了。

2. 查看半连接队列溢出

# 查看SYN_RECV状态的连接数
netstat -ant | grep SYN_RECV | wc -l

# 或者
ss -n state syn-recv | wc -l

如果数值异常高(比如几千),可能遭受SYN洪水攻击或半连接队列太小。

队列大小由什么决定

全连接队列大小

全连接队列大小 = min(backlog, somaxconn)
  • backlog:应用程序listen()时指定
  • somaxconn:系统参数net.core.somaxconn
# 查看somaxconn
cat /proc/sys/net/core/somaxconn
# 默认通常是128,太小了

# 临时修改
sysctl -w net.core.somaxconn=65535

# 永久修改(写入/etc/sysctl.conf)
net.core.somaxconn = 65535

常见应用的backlog配置

# Nginx
listen 80 backlog=65535;

# Tomcat server.xml
<Connector port="8080" acceptCount="1000" />

# Spring Boot
server.tomcat.accept-count=1000

半连接队列大小

# 查看
cat /proc/sys/net/ipv4/tcp_max_syn_backlog

# 修改
sysctl -w net.ipv4.tcp_max_syn_backlog=65535

实战案例:排查Connection refused

场景复现

某服务高峰期大量报错:Connection refused,但服务进程正常运行。

排查步骤

Step 1:确认服务在监听

ss -tlnp | grep 8080
# LISTEN   0   128   *:8080   users:(("java",pid=12345,fd=10))

服务正常监听,排除进程挂掉的可能。

Step 2:检查全连接队列

ss -lnt | grep 8080
# LISTEN   128   128   *:8080

# Recv-Q=128, Send-Q=128,队列已满!

Step 3:确认溢出

netstat -s | grep -i "listen"
# 123456 times the listen queue of a socket overflowed

数值还在增长,确认是全连接队列溢出。

Step 4:定位原因

队列满说明应用程序accept不够快,可能原因:

  • 应用处理太慢(GC、锁竞争等)
  • 线程池满了
  • somaxconn太小

Step 5:解决

# 1. 调大系统参数
sysctl -w net.core.somaxconn=65535

# 2. 调大应用backlog(Tomcat为例)
# server.xml: acceptCount="65535"

# 3. 增加处理线程
# server.xml: maxThreads="500"

# 4. 重启服务生效

TIME_WAIT过多问题

另一个常见问题:TIME_WAIT状态连接过多,占用端口资源。

查看TIME_WAIT数量

ss -s
# 或
netstat -ant | grep TIME_WAIT | wc -l

为什么会有大量TIME_WAIT

  • TIME_WAIT是主动关闭连接的一方产生的
  • 默认持续时间:2MSL(Linux下通常是60秒)
  • 高并发短连接场景容易积累

优化方案

# /etc/sysctl.conf

# 1. 允许TIME_WAIT复用
net.ipv4.tcp_tw_reuse = 1

# 2. 快速回收(有风险,NAT环境慎用)
# net.ipv4.tcp_tw_recycle = 1  # 内核4.12后已删除

# 3. 增加本地端口范围
net.ipv4.ip_local_port_range = 1024 65535

# 4. 减少FIN_WAIT时间
net.ipv4.tcp_fin_timeout = 30

监控脚本

写个脚本定期监控队列状态:

#!/bin/bash
# tcp_queue_monitor.sh

echo "=== 全连接队列溢出统计 ==="
netstat -s | grep -i "listen"

echo ""
echo "=== 各端口队列情况 ==="
echo "State      Recv-Q Send-Q Local:Port"
ss -lnt | tail -n +2

echo ""
echo "=== TIME_WAIT统计 ==="
ss -s | grep -i time-wait

echo ""
echo "=== 各状态连接数 ==="
ss -ant | awk '{print $1}' | sort | uniq -c | sort -rn

完整调优参数

# /etc/sysctl.conf

# 全连接队列
net.core.somaxconn = 65535

# 半连接队列
net.ipv4.tcp_max_syn_backlog = 65535

# SYN Cookie(防SYN攻击)
net.ipv4.tcp_syncookies = 1

# TIME_WAIT优化
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 30
net.ipv4.ip_local_port_range = 1024 65535

# 连接跟踪表(高并发必调)
net.netfilter.nf_conntrack_max = 2097152

# 应用生效
sysctl -p

远程排查小技巧

如果问题服务器不在身边,需要远程登录排查,又没有公网IP,可以用组网工具直接连接。我用的星空组网,提前把服务器加入虚拟局域网,随时随地SSH上去抓数据,比临时开VPN方便。

总结

问题 表现 排查命令 解决方案
全连接队列满 Connection refused ss -lnt 调大somaxconn和backlog
半连接队列满 连接超时 ss -n state syn-recv 调大tcp_max_syn_backlog
TIME_WAIT过多 端口耗尽 ss -s 开启tw_reuse

记住排查顺序:先看ss -lnt确认队列状态,再看netstat -s确认溢出,最后定位应用层原因。

posted @ 2025-12-09 11:06  花宝宝  阅读(18)  评论(0)    收藏  举报