前言
我在 blued 工作期间,负责过一个跟谷歌推送相关的子项目,原先是 node 写的,然后用 golang 重构,这个项目可以算是我第一个有一定并发量的程序,前前后后也碰到过不少问题。
过度的并发问题
内存泄漏
图上是内存泄漏时内存增长的情况
添加 pprof 模块
在这之前我还没使用过这个工具,看网上介绍又是这个函数又是那个函数的,处理好像挺麻烦的,实际上现在最新版本的 go tool 分析工具已经很人性化了,使用其实比我想象的方便。
首先在入口文件里添加代码引入 pprof 模块:
package main
import (
"log"
"net/http"
"os"
"runtime"
_ "github.com/mkevac/debugcharts" // 可选,添加后可以查看几个实时图表数据
_ "net/http/pprof" // 必须,引入 pprof 模块
"git.blued.cn/wangriyu/go-fcm/service"
)
func main() {
if os.Getenv("debugPProf") == "true" {
go func() {
// terminal: $ go tool pprof -http=:8081 http://localhost:6060/debug/pprof/heap
// web:
// 1、http://localhost:8081/ui
// 2、http://localhost:6060/debug/charts
// 3、http://localhost:6060/debug/pprof
log.Println(http.ListenAndServe("0.0.0.0:6060", nil))
}()
}
service.Start()
select {}
}
然后在 pm2 的启动文件里添加一个环节变量 debugPProf,方便使用时开启:
{
"name": "FCM",
"script": "main",
"error_file": "/data/logs/pm2/FCM/error.log",
"out_file": "/data/logs/pm2/FCM/out.log",
"log_date_format": "YYYY-MM-DD HH:mm:ss Z",
"instances": 1,
"exec_mode": "fork",
"merge_logs": true,
"max_memory_restart": "1024M",
"env": {
"fcm_env": "dev",
"debugPProf": false
}
}
使用 pprof
使用时将 debugPProf 设为 true,启动程序,通过上面设置的端口即可访问 pprof 的数据
打开 http://$HOSTIP:6060/debug/pprof/ : 这个页面包含总览信息,我不做介绍了,网上都有,我主要讲讲内存分析这块的
点击 heap 进入 http://$HOSTIP:6060/debug/pprof/heap?debug=1 界面,这里有内存分配信息,然后拉到最下面(mac 可以使用快捷键 CMD+↓ 跳到底部)
heapSys 是占用的物理内存大小,也就是我们直观上看到进程占用的大小;
heapIdle 是当中空闲的内存大小;heapInuse 才是此时程序真正使用的内存;nextGC 是下次触发 GC 的内存阀值
如果你添加了 “github.com/mkevac/debugcharts”,可以打开 http://$HOSTIP:6060/debug/charts
分析工具
go tool pprof http://$HOSTIP:6060/debug/pprof/heaptop
go tool pprof 可以带上参数 -inuse_space (分析应用程序的常驻内存占用情况) 或者 -alloc_space (分析应用程序的内存临时分配情况)
-http=:8081
$ go tool pprof -http=:8081 http://$HOSTIP:6060/debug/pprof/heap
如果 Could not execute dot,可能需要安装 graphviz 这个工具
之后打开 http://$HOSTIP:8081/ui 即可,里面包含 dot 格式的图、火焰图、top 列表、source 列表等
排查内存泄漏的过程
第一遍跑了一段时间程序后查看结果发现是 amqp 库的一个函数 recvContent 占用内存最高
我仔细看了下源码,发现这个函数使用用于解析消息体的,但是为什么这里泄漏内存不清楚,然后去 github issue 上搜了一下,发现有不少类似的情况,比如 https://github.com/streadway/amqp/issues/272
然后出现问题的共同点是我们用了 autoAck 机制,rabbitmq server 会自动做 ack 然后有多少消息都推给 cient,我那个项目因为要求处理尽可能快、消息不能延迟太多,有部分消息丢失是允许的,所以一开始选择的是 autoAck,发现这个问题后我改成了 manualAck;
改完这个后第二天一看发现还是有内存泄漏,但这回出问题不是 recvContent 函数,而是 notifyClose 函数,因为在代码中实现了一层重连机制,用到这个函数用于监听连接是否断开
最后我把 notifyClose 去掉用其他方法判断连接断开,这样才解决了泄漏问题
虽然解决了问题,但是根本原因还是在源代码里,当时因为时间原因就没深究下去了…