V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
chenqh
V2EX  ›  Go 编程语言

golang http 内存泄漏的问题

  •  1
     
  •   chenqh · 2021-03-06 22:13:20 +08:00 · 3624 次点击
    这是一个创建于 1387 天前的主题,其中的信息可能已经有所发展或是发生改变。

    代码

    package main
    
    import (
    	"crypto/tls"
    	"io/ioutil"
    	"log"
    	"net/http"
    	"os"
    	"path"
    	"strings"
    	"time"
    )
    
    func panicIfNotNil(e error) {
    	if e != nil {
    		panic(e)
    	}
    }
    
    func loop() {
    	ex, err := os.Executable()
    	panicIfNotNil(err)
    
    	ex = path.Dir(ex)
    	for {
    		now := time.Now()
    		tr := &http.Transport{
    			TLSClientConfig:   &tls.Config{InsecureSkipVerify: true},
    			DisableKeepAlives: true, // 可以关闭链接
    		}
    		req, err := http.NewRequest("GET", "https://httpbin.org/ip", nil)
    		panicIfNotNil(err)
    		client := &http.Client{
    			Timeout:   time.Second * 8,
    			Transport: tr,
    		}
    
    		resp, err := client.Do(req)
    
    		if resp != nil {
    			bodyByteArr, err := ioutil.ReadAll(resp.Body)
    			panicIfNotNil(err)
    
    			log.Printf("status_code:%d, body:%s, cost:%v", resp.StatusCode, string(bodyByteArr), time.Since(now))
    			defer resp.Body.Close()
    		}
    		if err != nil && strings.Index(err.Error(), "Client.Timeout exceeded while awaiting headers") > -1 {
    			continue
    		}
    		panicIfNotNil(err)
    
    		time.Sleep(time.Second * 3)
    
    	}
    }
    
    func main() {
    	loop()
    }
    
    

    一开始只有 4-m 的样子,过了两分钟就到 10M 左右了,这是为什么呀,我可能确保链接时关闭了的 求大佬指点下

    第 1 条附言  ·  2021-03-06 22:46:38 +08:00
    现在内存变成了 ·10.56m·, 感觉还是有内存泄漏


    golang 的版本

    ```
    [vagrant@localhost simple]$ go version
    go version go1.15.3 linux/amd64
    ```

    系统 centos7
    第 2 条附言  ·  2021-03-07 14:02:24 +08:00
    pprof 了一下

    ```
    Showing nodes accounting for 2097.87kB, 100% of 2097.87kB total
    Showing top 10 nodes out of 14
    flat flat% sum% cum cum%
    1195.29kB 56.98% 56.98% 1195.29kB 56.98% compress/flate.(*compressor).init
    902.59kB 43.02% 100% 2097.87kB 100% compress/flate.NewWriter
    0 0% 100% 2097.87kB 100% compress/gzip.(*Writer).Write
    0 0% 100% 2097.87kB 100% net/http.(*ServeMux).ServeHTTP
    0 0% 100% 2097.87kB 100% net/http.(*conn).serve
    0 0% 100% 2097.87kB 100% net/http.HandlerFunc.ServeHTTP
    0 0% 100% 2097.87kB 100% net/http.serverHandler.ServeHTTP
    0 0% 100% 2097.87kB 100% net/http/pprof.Index
    0 0% 100% 2097.87kB 100% net/http/pprof.handler.ServeHTTP
    0 0% 100% 2097.87kB 100% runtime/pprof.(*Profile).WriteTo
    ```
    第 3 条附言  ·  2021-03-08 16:50:56 +08:00
    我现在也感觉问题,几分钟才增加 200K,不管了,我是菜逼,管那么多干嘛呢,学就是了
    21 条回复    2021-03-08 16:25:27 +08:00
    jworg
        1
    jworg  
       2021-03-06 22:16:37 +08:00
    jinliming2
        2
    jinliming2  
       2021-03-06 22:27:12 +08:00   ❤️ 1
    defer resp.Body.Close() 只会在 func 推出后执行,你这里一直在死循环,函数不推出,resp.Body.Close() 永远不会执行。
    你这里直接把 defer 去掉应该就好了。
    Mohanson
        3
    Mohanson  
       2021-03-06 22:29:01 +08:00 via Android   ❤️ 1
    for 循环里面 defer,楼上正解
    chenqh
        4
    chenqh  
    OP
       2021-03-06 22:31:01 +08:00
    @jinliming2 去掉了 defer 内存也在涨
    chenqh
        5
    chenqh  
    OP
       2021-03-06 22:31:24 +08:00
    @Mohanson 去掉了也在涨呀
    chenqh
        6
    chenqh  
    OP
       2021-03-06 22:44:16 +08:00
    [vagrant@localhost simple]$ go version
    go version go1.15.3 linux/amd64
    jinliming2
        7
    jinliming2  
       2021-03-06 22:53:28 +08:00
    emmmm,看上去应该没有其他泄露的地方了,你 keepAlive 关了,应该不用手动 CloseIdleConnections 了。
    建议你再观察观察,因为你这个是一直在循环,你多观察一会,它是会一直增长,还是会停留在一定程度?
    如果涨到一定程度就不涨了,那可能就跟 go 的垃圾回收机制有关了。
    chenqh
        8
    chenqh  
    OP
       2021-03-06 22:58:12 +08:00
    @jinliming2 现在已经 11m 了

    ```
    Every 2.0s: ~/soft/pstat --name="simple" --exclude="pstat" Sat Mar 6 22:57:49 2021

    name count mem open_files net_connections
    ---- ----- --- ---------- ---------------
    simple 1 11.03m 20 0

    ```
    chenqh
        9
    chenqh  
    OP
       2021-03-06 22:59:50 +08:00
    @jinliming2 难道大佬们都是全局 client 的吗?
    jworg
        10
    jworg  
       2021-03-06 23:04:29 +08:00
    我还是直接把 Transport 中注释贴出来吧,你看看你创建了多少个,正常应该 reused 的

    // Transport is an implementation of RoundTripper that supports HTTP,
    // HTTPS, and HTTP proxies (for either HTTP or HTTPS with CONNECT).
    //
    // By default, Transport caches connections for future re-use.
    // This may leave many open connections when accessing many hosts.
    // This behavior can be managed using Transport's CloseIdleConnections method
    // and the MaxIdleConnsPerHost and DisableKeepAlives fields.
    //
    // Transports should be reused instead of created as needed.
    // Transports are safe for concurrent use by multiple goroutines.
    //
    // A Transport is a low-level primitive for making HTTP and HTTPS requests.
    // For high-level functionality, such as cookies and redirects, see Client.
    chenqh
        11
    chenqh  
    OP
       2021-03-06 23:23:17 +08:00
    @jworg


    ```
    [vagrant@localhost simple]$ sudo lsof -p 9575 -a -i 4
    COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
    simple 9575 vagrant 6u IPv4 6958222 0t0 TCP localhost.localdomain:38460->ec2-34-231-30-52.compute-1.amazonaws.com:https (ESTABLISHED)
    ```

    只有一个
    chenqh
        12
    chenqh  
    OP
       2021-03-06 23:51:28 +08:00
    现在好像卡在 11.39 不动了

    ```
    Every 2.0s: ~/soft/pstat --name="simple" --exclude="pstat" Sat Mar 6 23:50:46 2021

    name count mem open_files net_connections
    ---- ----- --- ---------- ---------------
    simple 1 11.39m 20 0

    ```

    明天在看一下
    jworg
        13
    jworg  
       2021-03-06 23:58:13 +08:00
    @chenqh 我之前回复弄错了,没有仔细看代码。我这边是在 11M 到 12M 波动,应该如 7 楼所说。另外建议安装代码检查工具,可以提示那个 defer 以及 resp.Body.Close()的返回值的检查。
    Immortal
        14
    Immortal  
       2021-03-07 00:01:27 +08:00
    pprof 打开观察下就好了 何必在这里猜
    代码看起来没啥问题 defer 的问题前面也有人说了
    nanmu42
        15
    nanmu42  
       2021-03-08 08:07:57 +08:00 via Android
    你再测久一些试试看,12M 还太少了。
    比如 10 个小时后到了 100M,那才是真的漏了。

    Go 1.16 之前,不用的内存还给操作系统的时机都比较延后,你也可以更新版本后试试: https://github.com/golang/go/issues/42330
    binbinyouliiii
        16
    binbinyouliiii  
       2021-03-08 09:50:13 +08:00
    Go 不是带 GC 的吗?没触发 GC ?
    xkeyideal
        17
    xkeyideal  
       2021-03-08 09:57:17 +08:00   ❤️ 1
    go 1.15 版本的内存回收机制导致的,升级至 1.16 版本试试吧
    另外 defer 的问题,每次 new http.Client 的方式也是错的
    jitongxi
        18
    jitongxi  
       2021-03-08 10:25:52 +08:00
    是这样的, 只要你用了 go 的标准 http 库,内存就会一直多 5m 左右,应该有很多初始化的上下文, 估计要看源码才知道
    cheng6563
        19
    cheng6563  
       2021-03-08 10:49:14 +08:00
    @binbinyouliiii GC 了也不会把内存还给操作系统,1.16 之前尤其如此
    DollarKiller
        20
    DollarKiller  
       2021-03-08 11:05:19 +08:00
    是长链接问题,你看下源码就知道了, 外婆卖瓜下 github.com/dollarkillerx/urllib 这个库有效的解决了这个问题
    xx6412223
        21
    xx6412223  
       2021-03-08 16:25:27 +08:00
    开一夜试试。我感觉问题不大
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   4417 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 23ms · UTC 04:06 · PVG 12:06 · LAX 20:06 · JFK 23:06
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.