最近产线环境偶发会出现用户收不到别人给他发送的IM消息,同事邀请我一起排查,现象是:他们的连接状态看着正常,自己能发送消息,但是收不到,退出重新登录能恢复,而且每次出现都是一大批消息,问题挺严重。
第一次出现问题时,首先是从消息投递和接收环节进行分析,得到如下信息:
原因推测: 由于线上没有开pprof,拿不到程序的运行情况,只能初步推测问题可能发生在长连接服务内部的mq consumer上。看起来像是mq consumer遇到了某种预料之外的异常情况,导致内部出现阻塞,支撑这种猜想的一个理由是:我们的mq client很老,大概是2015年的版本。
给出的处理办法:
看着上面的推测有根有据的,但上线没过多久,再一次出现,问题现象完全相同。
像以前一样,每次怀疑开源公共库有问题时,最后都会被无情的打脸,这次也不例外。
这次出现后首先想到的点是:自己给mq手动加的心跳过期检测和断开重连逻辑,看起来没有漏洞,为何没有生效呢?
在公司与同事讨论时,没有想明白。回家的路上,在晚风的吹拂下,突然想到一个可能的点:会不会是自己的程序出现死锁或被阻塞了?
于是,回到家后立刻验证自己的想法,首先让运维同学帮忙生成了进程 的goroutine运行状态信息,发现mq消费者线程被阻塞在recvmsg.go:64
这行代码上,阻塞了1461分钟,如下图所示:
goroutine 32 [chan send, 1461 minutes]:
ucasserver/pool.handlerDefaultMsg(0x0, 0x0, 0xc00151c7b8, 0x2, 0x0, 0x0, 0x0, 0xc00244a2c0, 0x1, 0x4, ...)
/home/jenkins/workspace/Git_go_ucasserver_KickOff/pool/recvmsg.go:64 +0x1af
ucasserver/pool.RecvMsg(0xc001ba6500, 0x131, 0x131, 0x0, 0x1)
/home/jenkins/workspace/Git_go_ucasserver_KickOff/pool/recvmsg.go:27 +0x165
ucasserver/mq.createConsume(0xc000536b80, 0x11, 0xc000098300, 0x57)
/home/jenkins/workspace/Git_go_ucasserver_KickOff/mq/init.go:75 +0x57b
created by ucasserver/mq.Init
/home/jenkins/workspace/Git_go_ucasserver_KickOff/mq/init.go:18 +0x61
这块代码正好是mq收到消息后向pool线程分发消息的位置:
结合上面代码阻塞的位置,有经验的小伙伴就能猜到,pool里的receive channel应该是满了,进一步,这100个pool线程里面是不是有一个不运行了?
带着这个疑问,我们就去代码里看,如果程序异常退出,应该会defer阶段打印一条日志handle send msg panic occurred
的
这个pool线程崩在了gorilla/websocket/conn.go 610行,defer里这行日志打的有点粗糙,没有将panic的错误描述打出来。
不过这个难不到善于想办法的我们,找到gorilla库的源代码,看看conn.go 610行报了什么错:
熟悉websocket的同学看到这个描述就能想到问题所在,同一个连接上出现了多线程并发write, 接下来就完全看自己对服务代码的熟悉程度了。
涉及到公司里关键服务的源码,这里不便贴出,就直接说排查到的原因:
解法也比较简单:
case msg, ok := <-p.xxxChannel:
, 专门用来接收和处理内部特殊消息。本文由博客一文多发平台 OpenWrite 发布!