Golang线上内存爆掉问题排查(pprof)

news2024/11/30 12:41:11

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

  1. 编写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()
	go func() {
		http.ListenAndServe(":80", nil)
	}()
	//readAll
	app.Get("/readAll", testReadAll)
	//io.Copy
	app.Get("/ioCopy", func(ctx *context.Context) {
		file, err := os.Open("/Users/ziyi2/GolandProjects/MyTest/demo_home/io_copy_demo/xx.zip")
		if err != nil {
			panic(err)
		}
		defer file.Close()
		_, err = io.Copy(ctx.ResponseWriter(), file)
		if err != nil {
			panic(err)
		}
	})
	app.Listen(":8080", nil)
}

func testReadAll(ctx *context.Context) {
	file, err := os.Open("/Users/ziyi2/GolandProjects/MyTest/demo_home/io_copy_demo/xx.zip")
	if err != nil {
		panic(err)
	}
	defer file.Close()
	//simulate onLine err
	bytes, err := ioutil.ReadAll(file)
	if err != nil {
		panic(err)
	}
	_, err = ctx.Write(bytes)
	if err != nil {
		panic(err)
	}
}
  1. 打开资源监视器,同时发起readAll请求,观察内存占用
  • 发起readAll请求前
    在这里插入图片描述
  • 发送readAll请求
localhost:8080/readAll

我本地是模拟读取差不多1G左右的文件,可以看到ioutil.ReadAll直接一次性将内容读取到了内存。(一旦并发量上来,或者图片文件超大,后果不敢想象)
在这里插入图片描述
3. 再观察io.Copy方法

  • 发送ioCopy请求
localhost:8080/ioCopy
  • 流式传输,最后程序内存并没有暴涨
    在这里插入图片描述
    在这里插入图片描述

结论:

ioutil.ReadAll:会将数据一次性加载到内存。
io.Copy:流式拷贝,不会导致内存暴涨
因此对于大文件或者数据量不确定的场景推荐使用io.Copy

拓展:pprof使用

① 引入pprof

  1. 引入pprof包
  2. 开启一个协程监听
package main

import (
	"github.com/kataras/iris/v12"
	"github.com/kataras/iris/v12/context"
	"net/http"
	_ "net/http/pprof"
	"os"
)

func main() {
	app := iris.New()
	go func() {
		http.ListenAndServe(":80", nil)
	}()
	app.Listen(":8080", nil)
}

②查看分析报告

1 浏览器直接访问
http://IP:Port/debug/pprof

在这里插入图片描述

2 go tool 命令行直接分析
# 查看堆内存信息
go tool pprof http://IP:Port/debug/pprof/heap

# 查看cpu信息
go tool pprof http://IP:Port/debug/pprof/profile

## -seconds=5设置采样时间为5s
# go tool pprof -seconds=5 http://IP:Port/debug/pprof/profile

# 查看协程信息
go tool pprof http://IP:Port/debug/pprof/goroutine

# 查看代码阻塞信息
go tool pprof http://IP:Port/debug/pprof/block

# 需要查看什么信息将URL默认的Type更换为对应类型即可

-seconds=30 设置采样30s,也可以自定义时间范围。需要注意的是,对于profile而言,总是需要采样一段时间,才可以看到数据。而其他历史累计的数据,则可以直接获取从程序开始运行到现在累积的数据,也可以设置-seconds来获取一段时间内的累计数据。而其他实时变化的指标,设置这个参数没什么用,只会让你多等一会。

  1. allocs: A sampling of all past memory allocations【所有内存分配,历史累计】
  2. block: Stack traces that led to blocking on synchronization primitives【导致阻塞同步的堆栈,历史累计】
  3. cmdline: The command line invocation of the current program【当前程序命令行的完整调用路径】
  4. goroutine: Stack traces of all current goroutines. Use debug=2 as a query parameter to export in the same format as an unrecovered panic.【当前所有运行的goroutine堆栈信息,实时变化】
  5. heap: A sampling of memory allocations of live objects. You can specify the gc GET parameter to run GC before taking the heap sample.【查看活动对象的内存分配情况,实时变化】
  6. mutex: Stack traces of holders of contended mutexes【导致互斥锁竞争持有者的堆栈跟踪,历史累计】
  7. profile: CPU profile. You can specify the duration in the seconds GET parameter. After you get the profile file, use the go tool pprof command to investigate the profile.【默认进行30s的CPU Profing,用于观察CPU使用情况】
  8. threadcreate: Stack traces that led to the creation of new OS threads【查看创建新OS线程的堆栈跟踪信息】
  9. trace: A trace of execution of the current program. You can specify the duration in the seconds GET parameter. After you get the trace file, use the go tool trace command to investigate the trace.【当前程序执行链路】

注意:默认情况下是不追踪block和mutex的信息的,如果想要看这两个信息,需要在代码中加上两行:

runtime.SetBlockProfileRate(1) // 开启对阻塞操作的跟踪,block  
runtime.SetMutexProfileFraction(1) // 开启对锁调用的跟踪,mutex
3 导出为.out文件+命令行分析(推荐)

推荐使用导出文件方式,虽然步骤繁琐,但是有文件落地,保证重要数据不会丢失

//导出为文件
curl -o heap.out http://IP:Port/debug/pprof/heap

//解析文件并进入命令行交互
go tool pprof heap.out
//后续操作就和命令行直接分析如出一辙

//top 10 
//list funcName

在这里插入图片描述

③参数解析

1 采样类型
allocs:所有内存分配,历史累计

allocs: A sampling of all past memory allocations【所有内存分配,历史累计】

block:导致阻塞同步的堆栈信息,历史累计(每发生一次阻塞取样一次)

block: Stack traces that led to blocking on synchronization primitives【导致阻塞同步的堆栈,历史累计】

  • Block Goroutine阻塞事件的记录 默认每发生一次阻塞事件时取样一次
cmdline:程序命令行的完整调用路径

cmdline: The command line invocation of the current program【当前程序命令行的完整调用路径】

goroutine:当前程序运行的所有goroutine,实时变化(获取时取样一次)

goroutine: Stack traces of all current goroutines. Use debug=2 as a query parameter to export in the same format as an unrecovered panic.【当前所有运行的goroutine堆栈信息,实时变化】

  • 活跃Goroutine信息的记录 仅在获取时取样一次
heap:查看堆内存分配情况,实时变化(每分配512K取样一次)

heap: A sampling of memory allocations of live objects. You can specify the gc GET parameter to run GC before taking the heap sample.【查看活动对象的内存分配情况,实时变化】

  • Heap 堆内存分配情况的记录 默认每分配512K字节时取样一次
mutex:导致互斥锁竞争的堆栈跟踪,历史累计

mutex: Stack traces of holders of contended mutexes【导致互斥锁竞争持有者的堆栈跟踪,历史累计】

profile:CPU使用情况

profile: CPU profile. You can specify the duration in the seconds GET parameter. After you get the profile file, use the go tool pprof command to investigate the profile.【默认进行30s的CPU Profing,用于观察CPU使用情况】

threadcreate:创建新线程的堆栈信息(获取时取样)

threadcreate: Stack traces that led to the creation of new OS threads【查看创建新OS线程的堆栈跟踪信息】

  • 系统线程创建情况的记录 仅在获取时取样一次
trace:程序整个执行链路

trace: A trace of execution of the current program. You can specify the duration in the seconds GET parameter. After you get the trace file, use the go tool trace command to investigate the trace.【当前程序执行链路】

注意:默认情况下是不追踪block和mutex的信息的,如果想要看这两个信息,需要在代码中加上两行:

runtime.SetBlockProfileRate(1) // 开启对阻塞操作的跟踪,block  
runtime.SetMutexProfileFraction(1) // 开启对锁调用的跟踪,mutex
2 统计维度(以内存取样为例)

如果是初次接触pprof,可能会疑惑flat、sum、cum代表什么意思

在这里插入图片描述
官网解析:

  • The first two columns show the number of samples in which the function was running (as opposed to waiting for a called function to return), as a raw count and as a percentage of total samples.

  • The third column shows the running total during the listing.

  • The fourth and fifth columns show the number of samples in which the function appeared (either running or waiting for a called function to return). To sort by the fourth and fifth columns, use the -cum (for cumulative) flag.

  • 官网地址:https://go.dev/blog/pprof

以获取内存为例:

flat:当前函数分配的内存,不包含它调用其他函数造成的内存分配
flat%:当前函数分配内存占比
sum%:自己和前面所有的flat%累积值
cum:当前函数及当前函数调用其他函数的分配内存的汇总
cum%:这个函数分配的内存,以及它调用其他函数分配的内存之和

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1555799.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

《HelloGitHub》第 96 期

兴趣是最好的老师,HelloGitHub 让你对编程感兴趣! 简介 HelloGitHub 分享 GitHub 上有趣、入门级的开源项目。 https://github.com/521xueweihan/HelloGitHub 这里有实战项目、入门教程、黑科技、开源书籍、大厂开源项目等,涵盖多种编程语言 …

在遭受攻击时如何有效监测服务器流量峰值——实战指南

引言 在网络安全领域,分布式拒绝服务攻击(DDoS)是一种常见的针对服务器及网络资源的恶意行为,它通过短时间内发送大量无效请求,导致服务器不堪重负而无法正常服务合法用户。当服务器遭受攻击时,快速识别并…

kubernetes(K8S)学习(五):K8S进阶(Lifecycle......偏理论)

K8S进阶(Lifecycle......偏理论) 一、Pod进阶学习之路1.1 Lifecycle1.2 重启策略1.3 静态Pod1.4 健康检查1.5 ConfigMap1.6 Secret1.7 指定Pod所运行的Node 二、Controller进阶学习之路2.1 Job & CronJob2.2 StatefulSet2.3 DaemonSet2.4 Horizontal…

拖拉拽做游戏?轻松打造个人掌机世界 | 开源日报 No.176

chrismaltby/gb-studio Stars: 7.8k License: MIT gb-studio 是一个快速、易于使用的拖放式复古游戏创建器,适用于您最喜爱的掌上游戏机系统。 该项目主要功能、关键特性和核心优势包括: 快速且易于使用使用 Electron 游戏构建应用程序和基于 C 的 GBD…

无尘布的多重应用:保持洁净,细致无遗

在现代社会中,随着科技的不断进步和人们对卫生环境要求的提高,无尘布作为一种多功能的擦拭材料,正被广泛应用于各种需要高洁净度环境的领域。其多重应用不仅为电子行业、医疗行业、生物工程和光学仪器等专业领域提供了便利,同时也…

在项目中缓存如何优化?SpringCache接口返回值的缓存【CachePut、CacheEvict、Cacheable】

SpringCache 介绍(不同的缓存技术有不同的CacheManager)注解入门程序环境准备数据库准备环境准备注入CacheManager引导类上加EnableCaching CachePut注解(缓存方法返回值)1). 在save方法上加注解CachePut2). 测试 CacheEvict注解(清理指定缓存…

系统资源紧缺?不用担心,Linux命令和Shell脚本帮你搞定

在之前的文章中介绍了如何申请AWS免费主机使用WordPress搭建自己的个人网站,但是在我使用过程中发现了一个问题,由于陆陆续续安装了好几个插件,偶尔在访问网站时会出现数据库连接出错的异常情况,导致页面无法访问。稍等一会儿刷新…

想做跨境电商测评自养号需要满足什么条件?

测评对于卖家来说算是一种低成本,高回报的推广营销方式,对于商品流量,转化率,关键词质量分,链接权重等都起到了一定的辅助作用,但凡事都有好坏,倘若处理不得当,很容易影响店铺&#…

如何将Maven与TestNG集成

我们已经讨论了如何在maven中执行单元测试用例,但那些是JUnit测试用例,而不是TestNG。当maven使用“mvn test”命令进入测试阶段时,这些用例被执行。 本文将介绍如何将Maven与TestNG集成,并在maven进入测试阶段时执行TestNG测试。…

聚酰亚胺PI材料难于粘接,用什么胶水粘接?那么让我们先一步步的从认识它开始(十三): 聚酰亚胺PI纤维的缺点

聚酰亚胺PI纤维的缺点 聚酰亚胺PI纤维虽然是一种具有许多优异特性的高性能纤维,但也存在一些缺点和局限性,这些缺点可能会影响其在某些应用领域的适用性。主要缺点包括: 1.成本高:聚酰亚胺PI纤维的生产过程复杂,原料成…

无药可医还能怎么办?越没本事的人,越喜欢从别人身上找原因!——早读(逆天打工人爬取热门微信文章解读)

无药可医的病该怎么办呢? 引言Python 代码第一篇 洞见 《骆驼祥子》:越没本事的人,越喜欢从别人身上找原因第二篇 人民日报 来啦 新闻早班车要闻社会政策 结尾 “吾日三省吾身,而后深知自助者天助之。” 在人生的迷宫中 遭遇困境时…

基于Springboot+vue的鲜花销售商城网站

摘 要 随着科学技术的飞速发展,社会的方方面面、各行各业都在努力与现代的先进技术接轨,通过科技手段来提高自身的优势,鲜花销售商城当然也不能排除在外。鲜花销售商城是以实际运用为开发背景,运用软件工程原理和开发方法&#x…

【Spring】SpringMvc项目当中,页面删除最后一条数据,页面不跳转并且数据为空。

期待您的关注 在之前学习SpringMvc的时候遇到过这样一个BUG,当我在一个页面删除该页面的最后一条数据的时候,一旦我删除成功,那么这个页面不会进行跳转,而是还停留在这个本不应该存在的页面,而且数据什么都没有。如下…

Java基础之自增自减运算符

Java基础之自增自减运算符 基本用法 int a 10;a; System .out.prinln(a);//a11int a 10;a; System .out.prinln(a);//a11tip: 第一次运算之后a的值会更新 然后再进行下面的运算!!! 练习: 代码呈现: 结果: tip: x的值是最新的x的值.

【Node.js】大文件上传

概述 大文件上传通常采用分片上传。如果因为某些原因上传突然中断,解决问题之后可以接着之前的分片上传,而不需要从头开始上传,也就是断点续传。此外还可以利用多个网络连接并行上传多个分片,提高上传速度。 注:前端不…

汽车电子与软件架构概述

汽车电子与软件架构概述 我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师 (Wechat:gongkenan2013)。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 本就是小人物,输了就是输了,不要在意别人怎么看自己。江湖一碗茶,喝完再挣扎,出门靠自己…

N9010A安捷伦N9010A信号分析仪

181/2461/8938产品概述: Keysight N9010A EXA 信号分析仪是最大限度提高生产线吞吐量的最快方法。从测量速度到代码兼容性,它让每一毫秒都很重要,并帮助您降低总体测试成本。 我们无法预测未来,但安捷伦可以利用我们面向未来的测…

零基础教程:R语言lavaan结构方程模型(SEM)

查看原文>>>最新基于R语言lavaan结构方程模型(SEM)实践技术应用 基于R语言lavaan程序包,通过理论讲解和实际操作相结合的方式,由浅入深地系统介绍结构方程模型的建立、拟合、评估、筛选和结果展示的全过程。我们筛选大量…

马斯克旗下xAI发布Grok-1.5,相比较开源的Grok-1,各项性能大幅提升,接近GPT-4!

本文原文来自DataLearnerAI官方网站:马斯克旗下xAI发布Grok-1.5,相比较开源的Grok-1,各项性能大幅提升,接近GPT-4! | 数据学习者官方网站(Datalearner) 继Grok-1开源之后,xAI宣布了Grok-1.5的内测消息&…

Linux ssh免密登录配置

步骤 在本地机器上生成公钥和私钥对。将本地公钥复制到远程机器的~/.ssh/authorized_keys文件中。 实现1 在服务器上生成SSH密钥对 ssh-keygen -t rsa -f /home/id_rsa1ssh-keygen: 这是一个用于生成、管理和转换 SSH 密钥的 OpenSSH 工具。-t rsa: 用于指定要生成的密钥类…