有一段时间,咱们的推送服务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的信息,仅列出关键信息:test
goroutine profile: total 10004 10000 @ 0x186f6 0x616b 0x6298 0x2033 0x188c0 # 0x2033 main.f+0x33 /Users/siddontang/test/pprof.go:11
能够看到,在main.f这个函数中,有10000个goroutine正在执行,符合咱们的预期。import
在go里面,还有不少运行时查看机制,能够很方便的帮咱们定位程序问题,不得不赞一下。