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 次
