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)时,服务器会经历以下阶段:

  1. SYN 队列(半连接队列):客户端发送 SYN,服务器回复 SYN-ACK,此时连接进入 SYN_RECV 状态,等待客户端 ACK
  2. 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
客户端并发过高 增加重试,限制并发量

建议先调整 somaxconntcp_max_syn_backlog,再优化服务器 Accept 逻辑。如果问题仍然存在,可以结合 tcpdump 抓包分析具体丢包原因:

sudo tcpdump -i any port 20007 -nn -vv
posted @ 2025-04-07 14:39  guanyubo  阅读(198)  评论(0)    收藏  举报