xxx SYNs to LISTEN sockets dropped
$ netstat -s | grep -i "listen"
6006609 times the listen queue of a socket overflowed
6006609 SYNs to LISTEN sockets dropped
从 netstat -s
的输出可以看到,有 6006609 次 SYN 请求被丢弃,具体原因是 监听队列(listen queue)溢出。这表明你的服务器在高并发连接时,TCP 的 SYN 半连接队列(SYN backlog) 或 Accept 队列 已满,导致新的连接请求被丢弃。
问题原因
当客户端发起 TCP 连接(net.Dial
)时,服务器会经历以下阶段:
- SYN 队列(半连接队列):客户端发送
SYN
,服务器回复SYN-ACK
,此时连接进入SYN_RECV
状态,等待客户端ACK
。 - Accept 队列(全连接队列):客户端发送
ACK
后,连接进入ESTABLISHED
状态,并进入 Accept 队列,等待服务器调用Accept()
处理。
你的问题可能是:
- SYN 队列太小:服务器来不及处理
SYN
请求,导致SYN
被丢弃。 - Accept 队列太小:服务器
Accept()
调用太慢,导致已建立的连接无法进入应用层,最终被丢弃。 - 服务器处理能力不足:Go 的
Accept()
速度跟不上SYN
请求的速度。
解决方案
1. 增大 TCP SYN 队列和 Accept 队列
Linux 通过 net.core.somaxconn
控制 Accept 队列的最大长度,而 Go 的 Listen()
默认使用该值。你需要:
# 查看当前 somaxconn 值(默认可能是 128 或 512)
sysctl net.core.somaxconn
# 临时增大(立即生效)
sudo sysctl -w net.core.somaxconn=65535
# 永久生效(写入 /etc/sysctl.conf)
echo "net.core.somaxconn=65535" | sudo tee -a /etc/sysctl.conf
sudo sysctl -p
在 Go 代码中显式设置 Backlog
(建议):
ln, err := net.Listen("tcp", ":20007")
// 设置较大的 Backlog(建议 >= somaxconn)
if tcpLn, ok := ln.(*net.TCPListener); ok {
tcpLn.SetDeadline(time.Now().Add(1 * time.Second)) // 可选:设置 Accept 超时
}
2. 检查 SYN 队列是否溢出
# 查看 SYN 丢弃情况(和你的 netstat -s 输出一致)
netstat -s | grep -i "listen"
如果 SYNs to LISTEN sockets dropped
持续增长,说明 SYN
队列仍然太小。
调整 SYN 队列大小:
# 临时调整(默认可能是 128)
echo 8192 | sudo tee /proc/sys/net/ipv4/tcp_max_syn_backlog
# 永久生效
echo "net.ipv4.tcp_max_syn_backlog=8192" | sudo tee -a /etc/sysctl.conf
sudo sysctl -p
3. 优化服务器 Accept 性能
如果 Go 的 Accept()
处理太慢,会导致 Accept 队列堆积。可以:
- 增加
Accept
的 Goroutine:for i := 0; i < runtime.NumCPU() * 2; i++ { go func() { for { conn, err := ln.Accept() if err != nil { log.Println("Accept error:", err) continue } go handleConn(conn) // 处理连接 } }() }
- 使用
SO_REUSEPORT
(Linux 3.9+):ln, err := net.Listen("tcp", ":20007") if err != nil { log.Fatal(err) } // 设置 SO_REUSEPORT(需要 Linux 内核支持) if tcpLn, ok := ln.(*net.TCPListener); ok { file, _ := tcpLn.File() syscall.SetsockoptInt(int(file.Fd()), syscall.SOL_SOCKET, syscall.SO_REUSEPORT, 1) }
4. 检查服务器资源限制
即使调整了队列,如果服务器 CPU/内存/文件描述符不足,仍然可能丢弃连接:
# 检查文件描述符限制
ulimit -n
# 检查系统全局 FD 限制
cat /proc/sys/fs/file-max
# 修改 limits.conf(如 65535)
echo "* soft nofile 65535" | sudo tee -a /etc/security/limits.conf
echo "* hard nofile 65535" | sudo tee -a /etc/security/limits.conf
5. 客户端优化
如果客户端短时间建立大量连接,可能会触发服务器的保护机制:
- 增加重试机制:
for i := 0; i < 3; i++ { // 最多重试 3 次 conn, err := net.Dial("tcp", "192.168.11.35:20007") if err == nil { break } time.Sleep(100 * time.Millisecond) }
- 控制并发连接数(如使用
semaphore
):sem := make(chan struct{}, 4000) // 限制并发 4000 for i := 0; i < 10000; i++ { sem <- struct{}{} go func() { defer func() { <-sem }() conn, err := net.Dial("tcp", "192.168.11.35:20007") if err != nil { log.Println("Dial error:", err) return } defer conn.Close() // 发送请求... }() }
总结
问题 | 解决方案 |
---|---|
SYN 队列溢出 | 增大 net.ipv4.tcp_max_syn_backlog |
Accept 队列溢出 | 增大 net.core.somaxconn ,Go 代码设置 Backlog |
服务器 Accept 处理慢 | 多 Goroutine Accept() ,或 SO_REUSEPORT |
文件描述符不足 | 修改 limits.conf ,增加 nofile |
客户端并发过高 | 增加重试,限制并发量 |
建议先调整 somaxconn
和 tcp_max_syn_backlog
,再优化服务器 Accept
逻辑。如果问题仍然存在,可以结合 tcpdump
抓包分析具体丢包原因:
sudo tcpdump -i any port 20007 -nn -vv
Do not communicate by sharing memory; instead, share memory by communicating.