OpenIM Go 后端 死锁 Socket K8s 一致性Hash

独立部署OpenIM遇到的坑(1)上下线卡住

公司项目有im场景,我们自己在k8s中部署了3.5.1版本的openim,在开发和运维的过程中,我们遇到了一些问题,这里记录一下问题详情和解决的过程。 问题触发场景 我们编写了一个压力测试程序,模拟用户的典型使用场景: 建立连接(上线) 发送消息 接收回复 断开连接(下线) 测试设置:100个并发用户账号,每个账号持续重复上述流程,模拟高频上下线场景。 问题现象 压力测试程序运行一段时间后(通常几分钟到几十分钟),开始出现以下现象: 新用户连接请求无响应,卡在WebSocket握手阶段 已连接用户无法正常下线 服务器CPU使用率正常,但连接数不再变化 重启openim-msggateway服务后问题暂时恢复 原因 先说结论:上下线排队处理,导致了多个管道(channel)之间的死锁。 以下是详细解释。 为了理解这个问题,我们需要先了解OpenIM的连接链路。 这里说的上线是指openim服务器接收客户端建立的长连接,长连接采用websocket实现。长连接建立好之后,客户端和服务器就可以向对方发送消息了。 同一个客户端端长连接链路上有两个服务器:openim-msggateway-proxy和openim-msggateway openim-msggateway-proxy自身没有什么业务逻辑,它起到长连接负载均衡的作用,方便openim-msggateway的扩容。 问题出现在openim-msggateway,它是负责处理向客户端的长连接收发消息的核心服务。 具体地,在WsServer中定义的registerChan和unregisterChan互相之间产生了消费和生产的依赖关系,导致了死锁。 让我们来看一下相关的代码。 WsServer中定义了registerChan和unregisterChan: 1 2 3 4 5 6 7 // internal/msggateway/n_ws_server.go type WsServer struct { // ... registerChan chan *Client // 处理上线的channel unregisterChan chan *Client // 处理下线的channel // ... } 初始化为大小为1000的有缓冲channel 1 2 3 4 5 6 7 8 9 10 // internal/msggateway/n_ws_server.go func NewWsServer(globalConfig *config.GlobalConfig, opts ...Option) (*WsServer, error) { // ... return &WsServer{ // ... registerChan: make(chan *Client, 1000), unregisterChan: make(chan *Client, 1000), // ... }, nil } 用户建立websocket连接时,会触发向ws.registerChan中写入数据 ...

2026年2月12日 · 3 分钟 · 456 字 · Simon Sun

独立部署OpenIM遇到的坑(2)Socket泄漏

公司项目有im场景,我们自己在k8s中部署了3.5.1版本的openim,在开发和运维的过程中,我们遇到了一些问题,这里记录一下问题详情和解决的过程。 问题触发场景 300个用户同时在线,并不定期单聊发送消息 问题现象 从监控指标里可以看到: openimserver-openim-push和openimserver-openim-msggateway 的goroutine数量多 openimserver-openim-push和openimserver-openim-msggateway pod socket数很多 原因 这看起来像是一个socket泄漏问题,即打开了socket连接,但没有主动关闭,继而引发了协程泄漏。 解决办法 1 2 3 4 5 6 7 8 9 10 11 12 // open-im-server/internal/push/push_to_client.go for host, userIds := range usersHost { tconn, err := p.discov.GetConn(ctx, host) if err != nil { log.ZError(ctx, "p.discov.GetConn failed", err) } // 新增以下代码,在使用tconn后关闭 if tconn != nil { defer tconn.Close() } } 总结与反思 关注重要运行指标: 我们当时是从监控看板及时看到了socket数量异常,排查后发现了这个问题,且后来openim-msggatewaypod socket占用量最多达到了四万多个。如果没有处理,极端情况下会是pod socket用尽,导致程序异常。 ...

2026年2月12日 · 1 分钟 · 69 字 · Simon Sun

独立部署OpenIM遇到的坑(3)扩容出错

公司项目有im场景,我们自己在k8s中部署了3.5.1版本的openim,在开发和运维的过程中,我们遇到了一些问题,这里记录一下问题详情和解决的过程。 问题简述 简单来说,就是:用户明明在线,但收不到实时消息,只能收到推送通知,过一会儿消息才出现在聊天界面。 这个问题只在扩容后出现,单个节点时一切正常。问题的根源是OpenIM内部的负载均衡机制出现了不一致,导致消息被发送到了错误的服务节点。 触发场景 对 openim-msggateway 进行扩容(从1个节点扩容到多个节点) 遇到的问题 扩容后在线用户无法收到后端推送的websocket消息,只能收到离线的通知栏推送消息,过一段时间后,可以在客户端的聊天界面中看到推送的消息 问题原理(通俗版) 在深入代码之前,我们先用一个比喻来理解这个问题: 想象一个快递系统: 📱 客户端:收件人 📍 openim-msggateway-proxy:客户服务中心(通过导航系统B给客户发分配快递站点) 🏠 openim-msggateway:快递站点(有多个) 🚚 openim-push:快递员(有自己的导航系统A) 实际的流程是: 建立连接:收件人先到客户服务中心,客户服务中心通过导航系统B为收件人分配快递站点A 投递包裹:快递员使用自己的导航系统A,独立计算这个收件人应该在哪个快递站点 正常情况下: 客户服务中心的导航系统B:收件人 → 站点A 快递员的导航系统A:收件人 → 站点A 收件人在站点A顺利收到包裹 但现在的问题是: 客户服务中心的导航系统B:收件人 → 站点A 快递员的导航系统A:收件人 → 站点B 收件人在站点A等包裹,快递员却把包裹送到了站点B 两个独立的导航系统给出了不同的结果! 技术原因分析 OpenIM内部连接链路 从图中可以看到,问题涉及两个独立的路由过程: 消息发送路径:openim-push 直接通过一致性hash找到用户连接的 openim-msggateway 节点 客户端连接路径:客户端通过 openim-msggateway-proxy 建立与 openim-msggateway 的websocket连接 这两个路径都需要通过一致性hash算法选择同一个 openim-msggateway 节点,但它们使用了不同的节点地址格式! 关键代码差异 问题的核心在于两个地方构建服务节点地址的方式不一致: 第一处:openim-push 侧(发送消息时) 1 2 3 4 // open-im-server/pkg/common/discoveryregister/kubernetes/kubernetes.go // 构建的地址格式(注意末尾的:88端口号) host := fmt.Sprintf("%s-openim-msggateway-%d.%s-openim-msggateway-headless.%s.svc.cluster.local:88", instance, i, instance, ns, port) 第二处:openim-msggateway-proxy 侧(建立连接时) ...

2026年2月12日 · 1 分钟 · 150 字 · Simon Sun