Golang 内存泄漏了怎么办?

The Block研究总监:以太坊的交易费用占矿工总收入的百分比正在飙升

比特币日报讯,The Block研究总监Larry Cermak今日在推特上称:“以太坊的交易费用占矿工总收入的百分比正在飙升,远远超过了比特币,尽管比特币最近已完成了减半减半。”在比特币日报读懂区块链和数字货币,加入Telegram获得第一手区块链、加密货币…


如果很悲剧,代码上线后,服务发生了内存泄漏,亡羊补牢为时不晚。我们从以下几点去分析。

Golang 内存泄漏了怎么办?
Golang 内存泄漏了怎么办?


Golang 内存泄漏了怎么办?

分析goroutine 是否泄漏

Golang 内存泄漏了怎么办?


      从 pprof 的goroutine 分析,是否 goroutine 在持续增长。如果持续增长,那 goroutine 泄漏没跑了。我们用下面的例子来举例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package main

import (
"net/http"
_ "net/http/pprof"
"time"
)

type none struct{}

func main() {
go func() {
ch := make(chan none)
consumer(ch)
producer(ch)
}()
_ = http.ListenAndServe("0.0.0.0:8080", nil)
}

func consumer(ch chan none) {
for i := 0; i < 1000; i++ {
// 此处类似协程泄漏
go func() {
<-ch
}()

time.Sleep(3 * time.Microsecond)
}
}

func producer(ch chan none) {
time.Sleep(100 * time.Second)
for i := 0; i < 1000; i++ {
ch <- none{}

}
}
上述代码中,逐步创建了1k个goroutine(假定是泄漏的),我们可以通过http://127.0.0.1:8080/debug/pprof/ 访问查看goroutine的变化情况。
1. 在debug 中观察goroutine的数量变化,如果持续增长,那可以确定是goroutine 泄漏了。

Golang 内存泄漏了怎么办?

2. 之后访问 http://127.0.0.1:8080/debug/pprof/goroutine?debug=1查看各goroutine数量,查看持续增加的goroutine ,如果存在持续增长的goroutine,那从goroutine的堆栈代码短分析即可。下图中很明显可以看出1K的协程量。(当然是持续增长到达1K的)
Golang 内存泄漏了怎么办?


Golang 内存泄漏了怎么办?

数据泄漏怎么看

Golang 内存泄漏了怎么办?


数据泄漏出现的问题就比较多了,比如长的 string,slice 数据用切片的方式被引用,如果切片后的数据不释放,长的string,slice 是不会被释放的, 当然这种泄漏比较小。下面举一个前两天网友提供的一个案例。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package main

import (
"fmt"
"io/ioutil"
"net"
"net/http"
_ "net/http/pprof"
"time"
)

type None int64

func main() {
go func() {
singals := []int64{}
netListen, _ := net.Listen("tcp", ":30000")
defer netListen.Close()

for {
conn, err := netListen.Accept()
if err != nil {
fmt.Println("Accept Error")
}

singals = append(singals, 1)

go doSomething(conn)
}

for _ = range singals {
fmt.Println("Received")
}
}()

_ = http.ListenAndServe("0.0.0.0:8080", nil)
}

func doSomething(conn net.Conn) {
defer conn.Close()
time.Sleep(100 * time.Microsecond)
buf, err := ioutil.ReadAll(conn)
if err == nil {
fmt.Println(string(buf))
}
}
例子比较简单,从net Accept 数据,并开启一个goroutine 做数据处理。singals 呢,用于做事件处理,每接收一个链接,给singal 推一条数据。
为了从中查找内存泄漏,我们也增加了pprof。
为了能尽快发现问题,我这边用了一个简单的shell对服务施压(请求2w http 服务,不关心请求返回结果)。命令如下:
1
for i in `seq 0 20000`; do curl -m 1 "http://127.0.0.1:30000?abc=def" & done

从pprof 的 heap 中,我们能轻易的发现:

Golang 内存泄漏了怎么办?

内存分配中,mem_leak文件的26行(append) 操作 申请的内存排在了top 1,仔细看代码,发现我们slice中的数据从来没有释放,所以造成了上面的问题。
如何解决这个问题呢?其实比较简单。只需要将slice,修改成带cache的chan(作为一个队列来使用),当数据使用过后即可销毁。不仅不会再出现内存泄漏,也保证了功能上的一致性。(当然需要重新起一个协程, 由于上面的for 是阻塞的,不会断开,所以也导致了下面的slice 不工作)


Golang 内存泄漏了怎么办?

小结

Golang 内存泄漏了怎么办?


当然,上面的例子都是精简到不能再精简的小例子,实际中遇到的问题可能会要比这个复杂的多。但是万变不离其宗,找到正确的方法解决也不是什么难事。

除了上面的一些问题,还应该注意点什么,做了下面的总结:

  • 做一个服务进程内存监控的报警,这个很有必要,也是正常服务应该做的。

  • pprof 提供的是堆上的监控,栈内存很少会泄漏,也不容易被监控。

  • 尽量在方法返回时不要让使用者去操作Close,减少goroutine泄漏的可能。

  • 在用全局的Map,Slice 时要反复考虑导致内存泄漏。

  • slice 引用大切片时,考虑会不会有不释放的可能性。

end


才疏学浅,有问题请留言。谢谢!

推荐阅读


喜欢本文的朋友,欢迎关注“Go语言中文网

Golang 内存泄漏了怎么办?

Go语言中文网启用信学习交流群,欢迎加微信274768166,投稿亦欢迎

在比特币日报读懂区块链和数字货币,加入Telegram获得第一手区块链、加密货币新闻报道。

《窒风之中 第二季》全集/Karppi Season 2在线观看

导演: Rike Jokela编剧: Rike Jokela / Jari Isometsä / Kirsi Porkka主演: 菲拉·维塔拉 / 劳瑞·提卡宁 / 汤米·柯贝拉 / 卡里·希耶塔拉赫蒂 / Rami Peltonen / 亚尼·沃拉宁 / 皮…

Click to rate this post!
[Total: 0 Average: 0]

人已赞赏
Go语言技术开发

【成都】100课堂 | 招聘Golang高级开发工程师,期待你的加入!

2020-7-7 18:48:54

Go语言技术开发

在 Golang 中使用 -w 和 -s 标志

2020-7-7 18:50:11

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
有新消息 消息中心
搜索