func main() {
orm.Debug = true
go func() {
log.Println(http.ListenAndServe("0.0.0.0:3351", nil))
}()
prod()
}
pprof是什么:
pprof是Go的性能分析工具,在程序运行过程中,可以记录程序的运行信息,可以是CPU使用情况、内存使用情况、goroutine运行情况等,当需要性能调优或者定位Bug时候,这些记录的信息是相当重要。
问题排查: 打开网页:[监控web地址](http://x.x.x.x:3351/debug/pprof/),(此图为后续相同代码复现的情况,并非当时的情况)显示如下:
看下具体参数的含义
allocs: 内存分配情况的抽象情况
block: 阻塞堆栈的采样信息
cmdline: 程序启动命令及其参数
goroutine: 当前协程的堆栈信息
heap: 堆内存的采样信息
mutex: 锁竞争的采样信息
profile: cpu使用情况的采样信息
threadcreate: 系统程序创建情况的采样信息
trace: 程序运行的跟踪信息
刷新一下,可以看到goroutine 在持续的增长,所以怀疑是goroutine泄漏导致的。点击 goroutine,发现代码中有一处产生了绝大部分的协程,而且还在在持续性的增长中。
goroutine监控
找到代码:
按道理来说,goroutine+chan 正常的生产消费是不会出现泄漏的问题。对着代码苦思不得其解,喝了杯冰可乐让自己冷静下来,突然发现在代码的181行,也会进行结果的输出,但是没有return,也就意味着如果上面获取redis中的数据,根据json解析异常后,进入异常,则会写入到chan中两次结果。上服务器日志进行排查,发现果然是这块的问题,由于redis中缓存的数据不存在,导致json解析异常,进行了双倍写入,导致了goroutine一直在增长,无法释放:
redisResult := redis.RedisHGet(finalKey, field)
//get key value from redis
common.InfoLogger.Infof("finalKey : %v ,filed : %v , result : %v ", finalKey, field, redisResult)
//防止redis中没有数据,
if redisResult == ""{
result <- JobResult{job.JobId, 0, []string{}, errors.New("redis 取不到数据")}
return
}
if err := json.Unmarshal([]byte(redisResult), &tempJson); err != nil {
common.ErrorLogger.Infof("json err : %v", err)
result <- JobResult{job.JobId, 0, []string{}, errors.New("redis 取不到数据")}
return
}
result <- JobResult{job.JobId, tempJson[hkey[2]], []string{finalKey}, nil}
心得:
pprof这个“性能大杀器”的称号不是白来的,能够非常方便的监控代码运行性能。当然pprof的使用方式还有很多种,不只是本文的这一种方式,更多使用方式,可以参考网上的示例。