在golang中创建goroutine很容易,但不合理的使用可能会导致大量goroutine无法结束,无法释放资源,久而久之造成内存泄漏。避免goroutine泄漏的关键是妥善管理goroutine的生命周期。通过导出运行时指标并使用pprof,可以发现并解决goroutine泄漏。作者维护了一个服务,通过SSH连接到目标机器并执行命令。这是一个小的内部服务,平时没问题的时候我是不会去关注的。大约4个月前,在上次更新的时候,添加了一个taskcounter,并导出到prometheus进行监控。最近,发现这个计数器在稳步增加。第一反应是,好东西!来电数量稳步上升!!但是我觉得不对,这个小小的内部服务哪里来这么多调用。于是查看goroutine的监听情况(这个数据是从runtime.NumGoroutine()中获取的)。goroutines的数量也在稳步增长。单位时间内的请求数增加了,goroutines的数量也增加了。没有什么问题。但是转念一想,小的内部服务是不可能的。于是查看mm系统中所有请求的视图:可以看到请求量大约是每5分钟2000个左右,平均请求量是每分钟400个。在上面的prometheus监控图中,每条曲线都是一个实例。实际上deployment有4个instance,所以400要除以4才能得到单instance的请求量(曲线)应该在100/min左右,服务时计数器确实在100/min左右刚刚开始,随着时间的推移慢慢泄露。Goroutine泄漏(Goroutineleak)虽然我认为99%都是泄漏,但是还是需要看一些详细的资料。net/http/pprof之前在服务中开启过,所以直接请求pprof暴露的HTTP接口。#Goroutinessummarycurlhttp://service:port/debug/pprof/goroutine?debug=1#Goroutinesdetailscurlhttp://service:port/debug/pprof/goroutine?debug=2先看导出的goroutine总结:有1000+个goroutine处于同一个状态,简单的看等待读取数据,再看导出的goroutine详情:不知道没看到,看到就惊呆了,有详细信息(4个月)中是否有goroutines阻塞超过200,000分钟...肯定是goroutine泄漏。为什么会漏水?只能按照pprof导出的goroutine信息查看。打印出1000多个处于IO等待最多状态的goroutine的调用栈。根据这个调用栈的内容,找到对应代码的位置。从ssh.Dial到某个地方,io.ReadFull会被阻塞。向上。此服务使用golang.org/x/crypto/ssh包进行ssh连接。先看这个服务调用ssh.Dial的地方:clientConfig:=&ssh.ClientConfig{...Timeout:3*time.Second,...}//connettosshclient,err:=ssh.Dial("tcp",fmt.Sprintf("%s:%d",s.Host,36000),clientConfig)好像没什么问题,毕竟传入了一个Timeout参数,应该不会被阻塞。然后继续看下去,发现了一些问题。直接到调用栈中阻塞的地方(抛开library和runtime,这两个一般没问题),就是SSHHandshake的第一步,交换SSH版本的步骤。//Sendsandreceivesaversionline.TheversionLinestringshould//beUSASCII,startwith"SSH-2.0-",andshouldnotincludea//newline.exchangeVersionsreturnstheotherside'sversionline.funcexchangeVersions(rwio.ReadWriter,versionLine[]byte)(them[]byte,errorror){...if_,err=rw.Write(append(versionLine,'\r','\n'));err!=nil{return}them,err=readVersion(rw)returnthem,err}//maxVersionStringBytes是我们将接受的最大字节数//接受版本字符串.RFC4253section4.2limitsthisat255//charsconstmaxVersionStringBytes=255//ReadversionstringasspecifiedbyRFC4253,section4.2.funcreadVersion(rio.Reader)([]byte,error){versionString:=make([]byte,0,64)varokboolvarbuf[1]byteforlength:=0;length
