Go 长连接网关设计:多设备、心跳与连接池

test12026-09-112 次阅读

为什么网关用 Go

IM 系统里,真正做业务的地方(消息存储、关系链、推送策略)可以交给任意后端,但"扛海量长连接"这一层,用 Go 非常合适:goroutine 轻量,一个连接一个 goroutine 成本极低;标准库和生态对 WebSocket、网络编程支持成熟。所以我们的架构是:Go 做长连接网关,负责连接管理、心跳、消息路由,业务下沉给别的服务的 FastAPI。

连接模型

每条连接是一个 Client,挂在 Hub 上。Hub 维护 userID → []*Client 的映射,这样推消息时能找到该用户的所有在线连接(多设备)。

type Client struct {
    UserID  int64
    Device  string // web / desktop / ios
    Conn    *websocket.Conn
    Send    chan []byte
}

type Hub struct {
    mu       sync.RWMutex
    clients  map[int64][]*Client
}

心跳与保活

TCP 连接空闲时,中间网络设备可能默默把映射删掉,导致连接"假活"——能写但不通。必须靠应用层心跳探活。服务端读超时设为心跳间隔的若干倍,超时没收到 ping 就主动关连接。

func (c *Client) readPump() {
    c.Conn.SetReadDeadline(time.Now().Add(pongWait))
    c.Conn.SetPongHandler(func(string) error {
        c.Conn.SetReadDeadline(time.Now().Add(pongWait))
        return nil
    })
    for {
        if _, _, err := c.Conn.ReadMessage(); err != nil {
            break // 心跳超时或断开,退出,由 Hub 清理
        }
    }
}

客户端定时发 ping,服务端回 pong,Keep-Alive 就这么简单。关键是 pongWait 要明显大于心跳周期,给网络抖动留余量。

广播与优雅关闭

给用户推消息时,遍历该 userID 下的所有 Client,逐连接写。某条连接写阻塞就丢进 Send channel 异步处理,别让一个慢连接卡住整个推送循环。服务要重启时,先关闭 Hub 的注册通道、停止接收新连接,再逐个关连接,做到"正在聊天的用户不被瞬断"。

小结

Go 网关的核心就是三件事:用 map 把连接按用户聚合(支撑多设备)、用应用层心跳做保活、用 channel 做异步写避免阻塞。把这三件做稳,十万级长连接也能跑得稳。

T

test1

文章作者

为什么网关用 Go IM 系统里,真正做业务的地方(消息存储、关系链、推送策略)可以交给任意后端,但"扛海量长连接"这...

分类
技术
发布时间
2026-09-11
字数
约 1225 字
阅读
2 次