Golang线上内存爆掉问题排查(pprof)
1 问题描述
某天,售后同事反馈,我们服务宕掉了,客户无法预览我们的图片了。
我们预览图片是读取存储在我们S3服务的数据,然后返回给前端页面展示。
因为客户存在几百M的图片,所以一旦请求并发一上来,很容易就把内存打爆。
2 pprof分析
声明:以下代码是我模拟线上故障的一个情况。
好在我们程序都有添加pprof监控,于是直接通过go tool pprof分析:
①获取堆内存分配情况:go tool pprof http://xx/debug/pprof/heap
# localhost直接改成自己程序的IP:端口
go tool pprof http://localhost:80/debug/pprof/heap
②过滤出占用堆内存前10的方法:top 10
# 过滤占用堆内存排名前10方法
top 10
参数解析:
flat:表示此函数分配、并由该函数持有的内存空间。
cum:表示由这个函数或它调用堆栈下面的函数分配的内存总量。
③查看方法详情:list testReadAll
可以看到我们自己程序的方法是main包下面的testAll方法占用了875MB多内存。
# 查看方法详情
list testReadAll
最后定位到ioutil.ReadAll这个方法占用了太多内存。
熟悉的朋友都清楚,ioutil.ReadAll是直接将文件或者流数据一次性读取到内存里。如果文件过大或者多个请求同事读取多个文件,会直接将服务器内存打爆。
因为我们的客户有几百M的图片,所以一旦并发以上来很可能打爆。因此这里需要改成流式的io.Copy。
3 解决:改用流式io.Copy()
定位到问题后,直接改用流式方式给前端返回。
_, err = io.Copy(ctx.ResponseWriter(), file)
由于这次的失误,加上测试数据量不够大,导致出现线上问题,所以大家以后还是要多review代码+增加压力测试。
4 本地测试io.Copy与ioutil.ReadAll
编写demo代码
package main
import (
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/context"
"io"
"io/ioutil"
"net/http"
_ "net/http/pprof"
"os"
)
func main() {
app := iris.New(