golang程序优化起来是比较简单的,这得益于pprof这样的性能分析工具,有了它,程序能很容易分析像内存泄漏、cpu泄漏这样的问题。网上关于使用pprof或者分析pprof源码实现的文章也不少,但是在项目中实战的文章比较少。大多数关于pprof的文章讲的是,这样:
//引入pprof
import _ "net/http/pprof"
......
//起一个http服务器,接着请求/debug/pprof等接口
http.ListenAndServe("0.0.0.0:6060", nil)
......
类似这样的做法,大多是本地调试可以这么用。我们可以把这段直接copy放进我们的项目里面吗?答案是可以。但是有个问题,可能需要我们思考,如果我们直接把它放进我们的代码里,而没有任何代价的话,那这段代码应该成为标配,放进所有项目里面。然而,只有利没有弊的事情没那么多,需要以一定性能作为代价。
我们可以参考一下tarsgo的做法:
// Notify handler for cmds from admin
func (a *Admin) Notify(command string) (string, error) {
cmd := strings.Split(command, " ")
// report command to notify
go ReportNotifyInfo(NOTIFY_NORMAL, "AdminServant::notify:"+command)
switch cmd[0] {
case "tars.viewversion":
return GetServerConfig().Version, nil
case "tars.setloglevel":
if len(cmd) >= 2 {
appCache.LogLevel = cmd[1]
switch cmd[1] {
case "INFO":
logger.SetLevel(logger.INFO)
case "WARN":
logger.SetLevel(logger.WARN)
case "ERROR":
logger.SetLevel(logger.ERROR)
case "DEBUG":
logger.SetLevel(logger.DEBUG)
case "NONE":
logger.SetLevel(logger.OFF)
default:
return fmt.Sprintf("%s failed: unknown log level [%s]!", cmd[0], cmd[1]), nil
}
return fmt.Sprintf("%s succ", command), nil
}
return fmt.Sprintf("%s failed: missing loglevel!", command), nil
case "tars.dumpstack":
debug.DumpStack(true, "stackinfo", "tars.dumpstack:")
return fmt.Sprintf("%s succ", command), nil
case "tars.loadconfig":
cfg := GetServerConfig()
remoteConf := NewRConf(cfg.App, cfg.Server, cfg.BasePath)
_, err := remoteConf.GetConfig(cmd[1])
if err != nil {
return fmt.Sprintf("Getconfig Error!: %s", cmd[1]), err
}
return fmt.Sprintf("Getconfig Success!: %s", cmd[1]), nil
case "tars.connection":
return fmt.Sprintf("%s not support now!", command), nil
case "tars.gracerestart":
graceRestart()
return "restart gracefully!", nil
case "tars.pprof":
port := ":8080"
timeout := time.Second * 600
if len(cmd) > 1 {
port = ":" + cmd[1]
}
if len(cmd) > 2 {
t, _ := strconv.ParseInt(cmd[2], 10, 64)
if 0 < t && t < 3600 {
timeout = time.Second * time.Duration(t)
}
}
cfg := GetServerConfig()
addr := cfg.LocalIP + port
go func() {
mux := http.NewServeMux()
mux.Handle("/debug/pprof/", http.HandlerFunc(pprof.Index))
mux.Handle("/debug/pprof/cmdline", http.HandlerFunc(pprof.Cmdline))
mux.Handle("/debug/pprof/profile", http.HandlerFunc(pprof.Profile))
mux.Handle("/debug/pprof/symbol", http.HandlerFunc(pprof.Symbol))
mux.Handle("/debug/pprof/trace", http.HandlerFunc(pprof.Trace))
s := &http.Server{Addr: addr, Handler: mux}
TLOG.Info("start serve pprof ", addr)
go s.ListenAndServe()
time.Sleep(timeout)
s.Shutdown(context.Background())
TLOG.Info("stop serve pprof ", addr)
}()
return "see http://" + addr + "/debug/pprof/", nil
default:
if fn, ok := adminMethods[cmd[0]]; ok {
return fn(command)
}
return fmt.Sprintf("%s not support now!", command), nil
}
}
tarsgo的做法是用一个命令来开启pprof,一定时间之后就关闭。它并不是隐式地引用pprof包,而是自己调用了pprof的方法,这样的好处是想关闭pprof的时候就可以关闭它。我之前搭建了一个自己平时用到的项目框架,我参考tarsgo的做法改了一下,用channel来开关pprof:
for {
select {
case <-pCh.OnOff:
timeout := time.Second * time.Duration(pCh.Timeout)
go func() {
mux := http.NewServeMux()
mux.Handle("/debug/pprof/", http.HandlerFunc(pprof.Index))
mux.Handle("/debug/pprof/cmdline", http.HandlerFunc(pprof.Cmdline))
mux.Handle("/debug/pprof/profile", http.HandlerFunc(pprof.Profile))
mux.Handle("/debug/pprof/symbol", http.HandlerFunc(pprof.Symbol))
mux.Handle("/debug/pprof/trace", http.HandlerFunc(pprof.Trace))
s := &http.Server{Addr: pCh.Host, Handler: mux}
logger.GetLogger().Info("start serve pprof")
go s.ListenAndServe()
time.Sleep(timeout)
s.Shutdown(context.Background())
<-pCh.Used
logger.GetLogger().Info("stop serve pprof")
}()
pCh.Used <- struct{}{}
}
}
完整的代码,可以参考:https://github.com/TomatoMr/awesomeframework
现在我们可以随意地开关pprof的接口了,十分实用。可以结合我们的监控工具来监控我们的服务,比如,当内存或者cpu超过一定的限额的时候,就开启pprof,并收集日志,这样,我们可以在事后分析当时的异常原因,而不用添加代码,也不用担心收集不到。
以上就是我对pprof在项目中实战的理解,希望我的使用姿势是正确的,如有说的不对的,或者有其他更好的方式,欢迎给我留言。
欢迎关注我的公众号:onepunchgo,会整理相关的文档和资料。
有疑问加站长微信联系(非本文作者)