前言:
公司一个同事使用go websocket开发了k8s在线调试服务,该服务也部署在k8s集群中,没几天运维那边通告说 cpu 100% 高负载了,还把限制的范围内的cpu core都干满了。由于那人休假,我帮忙处理下。
通常来说这类cpu高负载的问题相对好排查,多是bug造成了。像这个调试服务在一个量级请求完毕后,cpu使用率居然还是爆满。😅 不用想直接,肯定是协程泄露了,造成了某个逻辑的忙轮询。
准备
该服务在k8s集群中部署,没有接入ingress,所以在有外网的k8s node上做了一个端口映射。这样开发机可go tool pprof外网地址。
还有一个好方法是我以前常用的,在k8s node上把golang的pprof可分析数据导入到文件里,然后把文件post到一个支持上传的服务,比如http upload。后面大家就知道该怎么操作了。
分析问题
下面是pprof生成的火焰图和调用链耗时图,很明显的看到writeLoop不断的调用Close()方法。
既然确定了问题,通过pprof source定位热点代码,可以看到为什么不断调用wsConn.Close()呢 ? 因为没有return,既然已感知连接关闭,那么就应该return出去 !!!
// xiaorui.cc
Total: 2.31s 1.35mins (flat, cum) 99.25%
76 . . }
77 . . }
78 . .
79 . . // 发送协程
80 . . func (wsConn *WsConnection) wsWriteLoop() {
81 330ms 330ms defer fmt.Println("write exited")
82 . . for {
83 740ms 1.05mins select {
84 720ms 730ms case msg = <-wsConn.outChan:
85 . . if err = wsConn.socket.WriteMessage(msg.MessageType, msg.Data); err != nil {
86 . . log.Error("websocket write message error", err)
87 . . }
88 220ms 220ms case <-wsConn.closeChan:
89 300ms 16.21s wsConn.Close()
90 . . }
91 . . }
92 . . }
总结
相比性能调优,这类由于bug引起的cpu高负载问题反而特别处理,基本上通过pprof看火焰图就可快速定位问题。