前段时间,维护的一个golang服务出现了一次问题:请求无响应且无日志输出。重启进程后,服务恢复了正常,但却没有足够信息来跟进分析此次问题了。
之前运维过java服务,java的分析工具还是比较丰富的,针对thread有jstack或者可以发送SIGQUIT信号,针对heap有jmap等,以及其它基于JMX的分析工具。
以类似的思维来分析golang服务的问题,先获取goroutine的stack trace信息。
golang提供了runtime这个包,其Stack函数可以输出所有goroutine的stack trace信息;另外,golang进程也可以接受SIGQUIT信号,默认会输出目前的goroutine的stack trace信息,但是主进程会以返回码2退出。
所以,如果不侵入代码且不影响进程正常运行,基本无法获得goroutine的stack trace信息。当然,这也提醒我们如果线上的golang服务出现了问题需要stack trace,至少应先发送个SIGQUIT信号保留现场goroutine的stack trace信息,然后再进行重启等处理。
如果利用golang的channel,侵入应用代码,则可以实现类似java的thread dump的效果,这样在收到SIGQUIT信号后会输出goroutine的stack trace,同时golang进程会正常运行。代码如下:
package main import ( "log" "os" "os/signal" "runtime" "syscall" ) func main() { go func() { sigs := make(chan os.Signal, 1) signal.Notify(sigs, syscall.SIGQUIT) for { <-sigs // 给buf 1MB的空间,最好判断下空间是否足够 buf := make([]byte, 1<<20) runtime.Stack(buf, true) log.Printf("=== goroutine stack trace.../n%s/n=== end/n", buf) } }() // 应用代码 }
runtime/pprof提供了更详细的分析数据,包括cpu/heap/block/goroutinethreadcreate等数据。基于runtime/pprof,github.com/pkg/profile等更加方便了这些分析数据的采集。但是,这些采集对应用代码侵入较多,更适合于开发/测试环境。另外,golang也提供了基于runtime/pprof的net/http/pprof,以Http Server的方式提供,方便工程师查看分析数据。代码如下:
package main import ( "log" "net/http" _ "net/http/pprof" ) func main() { go func() { log.Println(http.ListenAndServe(":6060", nil)) }() // 应用代码 }
访问http://127.0.0.1:6060/debug/pprof/,可以看到各项数据的菜单,http://127.0.0.1:6060/debug/pprof/goroutine?debug=2显示了goroutine的stack trace信息;同时,也可以使用go tool pprof分析数据。
参考链接
http://blog.golang.org/profiling-go-programs
https://github.com/quipo/goprofiler
https://github.com/pkg/profile
https://golang.org/pkg/net/http/pprof/
https://golang.org/pkg/runtime/