有一段时间,咱们的推送服务socket占用很是不正常,咱们本身统计的同一时候在线就10w的用户,但是占用的socket竟然达到30w,而后查看goroutine的数量,发现已经60w+。缓存
每个用户占用一个socket,而一个socket,有read和write两个goroutine,简化的代码例如如下:并发
c, _ := listerner.Accept() go c.run() func (c *conn) run() { go c.onWrite() c.onRead() } func (c *conn) onRead() { stat.AddConnCount(1) //on something stat.AddConnCount(-1) //clear //notify onWrite to quit }
当时我就怀疑,用户同一时候在线的统计是正确的,也就是以后的clear阶段出现了问题,致使两个goroutine都没法正常结束。在检查代码以后,咱们发现了一个可疑的地方,因为咱们不光有本身的统计,还会将一些统计信息发送到咱们公司的统计平台,代码例如如下:socket
ch = make([]byte, 100000) func send(msg []byte) { ch <- msg } //在还有一个goroutine的地方, msg <- msg httpsend(msg)
咱们channel的缓存分配了10w,假设公司统计平台出现了问题,可能会致使channel堵塞。但到底是不是这个缘由呢?函数
幸运的是,咱们先前已经在代码里面内置了pprof的功能,经过pprof goroutine的信息,发现大量的goroutine的当前执行函数在httpsend里面,也就是说,公司的统计平台在大并发如下服务不可用,尽管咱们有http超时的处理,但是因为发送的数据量太频繁,致使整体堵塞。ui
暂时的解决的方法就是关闭了统计信息的发送,兴许咱们会考虑将其发送到本身的mq上面,尽管也可能会出现mq服务不可用的问题,但是说句实话,比起本身实现的mq,公司的统计平台更让我不可信。spa
这同一时候也给了我一个教训,訪问外部服务必定要好优势理外部服务不可用的状况,即便可用,也要考虑压力问题。code
对于pprof怎样查看了goroutine的问题,可以经过一个简单的样例说明:it
package main import ( "net/http" "runtime/pprof" ) var quit chan struct{} = make(chan struct{}) func f() { <-quit } func handler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/plain") p := pprof.Lookup("goroutine") p.WriteTo(w, 1) } func main() { for i := 0; i < 10000; i++ { go f() } http.HandleFunc("/", handler) http.ListenAndServe(":11181", nil) }
这上面的样例中,咱们启动了10000个goroutine,并堵塞,而后经过訪问http://localhost:11181/,咱们就可以获得整个goroutine的信息,仅列出关键信息:class
goroutine profile: total 10004 10000 @ 0x186f6 0x616b 0x6298 0x2033 0x188c0 # 0x2033 main.f+0x33 /Users/siddontang/test/pprof.go:11
可以看到,在main.f这个函数中,有10000个goroutine正在运行,符合咱们的预期。test
在go里面,还有很是多执行时查看机制,可以很是方便的帮咱们定位程序问题,不得不赞一下。