Golang中TCP连接发生永久阻塞问题如何解决

Golang中TCP连接发生永久阻塞问题如何解决 我有一个MQTT代理运行在高负载环境下。由于所有连接的客户端都是外部客户端,我无法直接访问它们。但在堆栈跟踪中发现了一个有趣的现象:在向某个TCP客户端转发发布消息时,对Write的调用会永久阻塞。以下是部分堆栈跟踪:

goroutine 108 [IO wait, 54 minutes]:
internal/poll.runtime_pollWait(0x7f679a35e910, 0x77, 0xffffffffffffffff)
/usr/local/go/src/runtime/netpoll.go:182 +0x56
internal/poll.(*pollDesc).wait(0xc000505998, 0x77, 0x200, 0x300, 0xffffffffffffffff)
/usr/local/go/src/internal/poll/fd_poll_runtime.go:87 +0x9b
internal/poll.(*pollDesc).waitWrite(...)
/usr/local/go/src/internal/poll/fd_poll_runtime.go:96
internal/poll.(*FD).Write(0xc000505980, 0xc002b4ac00, 0x2dd, 0x300, 0x0, 0x0, 0x0)
/usr/local/go/src/internal/poll/fd_unix.go:276 +0x26e
net.(*netFD).Write(0xc000505980, 0xc002b4ac00, 0x2dd, 0x300, 0x300, 0x4, 0x300)
/usr/local/go/src/net/fd_unix.go:220 +0x4f
net.(*conn).Write(0xc0009e8f80, 0xc002b4ac00, 0x2dd, 0x300, 0x0, 0x0, 0x0)
/usr/local/go/src/net/net.go:189 +0x69

有人知道为什么会发生这种情况吗?我已经在网上搜索了许多帖子,但没有人遇到过这个问题。针对这个问题有什么可能的解决方案吗?

如果需要更多详细信息,请告诉我。

谢谢


更多关于Golang中TCP连接发生永久阻塞问题如何解决的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于Golang中TCP连接发生永久阻塞问题如何解决的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


在高负载环境下,TCP连接在Write调用时发生永久阻塞通常是由于底层网络问题或客户端处理能力不足导致的。当操作系统发送缓冲区已满且对端未及时确认接收数据时,写入操作会阻塞,直到缓冲区可用或连接超时。以下是几种解决方案及示例代码:

1. 设置写入超时

使用SetWriteDeadline为TCP连接设置写入超时,避免永久阻塞:

conn, err := net.Dial("tcp", "address")
if err != nil {
    log.Fatal(err)
}
defer conn.Close()

// 设置写入超时为5秒
deadline := time.Now().Add(5 * time.Second)
err = conn.SetWriteDeadline(deadline)
if err != nil {
    log.Printf("SetWriteDeadline failed: %v", err)
    return
}

data := []byte("message")
_, err = conn.Write(data)
if err != nil {
    if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
        log.Println("Write timeout: closing connection")
        conn.Close()
    } else {
        log.Printf("Write failed: %v", err)
    }
}

2. 使用带缓冲的通道和goroutine处理写入

通过goroutine异步处理写入,并结合超时控制:

type AsyncWriter struct {
    conn    net.Conn
    timeout time.Duration
    ch      chan []byte
}

func NewAsyncWriter(conn net.Conn, timeout time.Duration) *AsyncWriter {
    w := &AsyncWriter{
        conn:    conn,
        timeout: timeout,
        ch:      make(chan []byte, 100), // 缓冲100条消息
    }
    go w.writeLoop()
    return w
}

func (w *AsyncWriter) Write(data []byte) error {
    select {
    case w.ch <- data:
        return nil
    default:
        return errors.New("write buffer full")
    }
}

func (w *AsyncWriter) writeLoop() {
    for data := range w.ch {
        deadline := time.Now().Add(w.timeout)
        err := w.conn.SetWriteDeadline(deadline)
        if err != nil {
            log.Printf("SetWriteDeadline failed: %v", err)
            continue
        }
        
        _, err = w.conn.Write(data)
        if err != nil {
            log.Printf("Async write failed: %v", err)
            w.conn.Close()
            break
        }
    }
}

3. 实现连接健康检查

定期检查连接是否可写,及时关闭失效连接:

func checkWritable(conn net.Conn, timeout time.Duration) bool {
    deadline := time.Now().Add(timeout)
    err := conn.SetWriteDeadline(deadline)
    if err != nil {
        return false
    }
    
    _, err = conn.Write([]byte{}) // 发送空数据测试
    if err != nil {
        return false
    }
    
    // 重置deadline避免影响后续写入
    conn.SetWriteDeadline(time.Time{})
    return true
}

// 使用示例:在goroutine中定期检查
go func() {
    ticker := time.NewTicker(30 * time.Second)
    defer ticker.Stop()
    
    for range ticker.C {
        if !checkWritable(conn, 2*time.Second) {
            conn.Close()
            break
        }
    }
}()

4. 调整系统TCP参数

通过修改系统配置优化TCP行为(需系统权限):

// 在Linux系统中,可通过以下命令调整TCP缓冲区大小
// sysctl -w net.ipv4.tcp_wmem="4096 16384 4194304"

这些方法通过超时控制、异步处理和健康检查机制,可以有效避免TCP写入永久阻塞的问题。在高负载MQTT代理中,建议结合使用写入超时和异步写入机制,确保系统稳定性。

回到顶部