美文网首页golang笔记
go (*persistConn).writeLoop 导致go

go (*persistConn).writeLoop 导致go

作者: smoke_zl | 来源:发表于2020-10-29 00:22 被阅读0次

    十一长假,由于服务好几天没有发布上线,监控显示goroutine的数量一直在持续增长,初步判断是goroutine泄漏。
    使用 go pprof 排查后发现泄漏的 goroutine 信息

    1201 @ 0x438dfa 0x43411a 0x433797 0x4f76ab 0x4f772d 0x4f858d 0x58922f 0x59bb4a 0x6a3006 0x53d36e 0x53d4ba 0x6a3ac5 0x4666e1
    #   0x433796    internal/poll.runtime_pollWait+0x56 /home/go/src/runtime/netpoll.go:173
    #   0x4f76aa    internal/poll.(*pollDesc).wait+0x9a /home/go/src/internal/poll/fd_poll_runtime.go:85
    #   0x4f772c    internal/poll.(*pollDesc).waitRead+0x3c /home/go/src/internal/poll/fd_poll_runtime.go:90
    #   0x4f858c    internal/poll.(*FD).Read+0x17c      /home/go/src/internal/poll/fd_unix.go:157
    #   0x58922e    net.(*netFD).Read+0x4e          /home/go/src/net/fd_unix.go:202
    #   0x59bb49    net.(*conn).Read+0x69           /home/go/src/net/net.go:176
    #   0x6a3005    net/http.(*persistConn).Read+0x135  /home/go/src/net/http/transport.go:1453
    #   0x53d36d    bufio.(*Reader).fill+0x11d      /home/go/src/bufio/bufio.go:100
    #   0x53d4b9    bufio.(*Reader).Peek+0x39       /home/go/src/bufio/bufio.go:132
    #   0x6a3ac4    net/http.(*persistConn).readLoop+0x184  /home/go/src/net/http/transport.go:1601
    
    1201 @ 0x438dfa 0x448b10 0x6a508b 0x4666e1
    #   0x6a508a    net/http.(*persistConn).writeLoop+0x14a /home/go/src/net/http/transport.go:1822
    

    发现有大量的 net/http.(*persistConn).writeLoop
    百度谷歌一下,发现了可能造成泄漏的原因:没有主动关闭http.Response.Body,官方文档也写的很清楚

    // The client must close the response body when finished with it:
    
    resp, err := http.Get("http://example.com/")
    if err != nil {
        // handle error
    }
    defer resp.Body.Close()
    body, err := ioutil.ReadAll(resp.Body)
    // ...
    

    但对代码排查一番过后,发现所有网络请求的地方,都调用了 defer resp.Body.Close()。
    原文地址

    继续排查,使用 netstat -antl 查看连接占用情况,发现请求某个ip,有大量的 ESTABLISHED,找出其中一个

    tcp        0      0 10.10.1.2:18555           10.10.10.11:80          ESTABLISHED
    

    查看对应端口18555的使用情况
    lsof -i:18555

    COMMAND     PID    USER   FD   TYPE     DEVICE      SIZE/OFF NODE   NAME
    xxxxx      16218   user  3119u IPv4     42480944        0t0  TCP 10.10.1.2:18555->10.10.10.11:http (ESTABLISHED)
    

    根据 FD=3119,找到对应的文件
    ls -l /proc/16218/fd/3119

    lrwx------ 1 user user 64 10月 10 13:02 /proc/16218/fd/3119 -> socket:[42480944]
    

    看了下当前时间,已经下午5点多了,连接存在了 4个多小时,那就说明,连接一直都没有断掉。
    再排查每个http调用的地方,发现有个调用每次请求都是短链接,但是使用了连接池(Transport)的配置,而且没有指定连接空闲断开时间(Transport.IdleConnTimeout),也没有禁用长连接(设置 Transport.DisableKeepAlives = true),导致连接变成了长连接,对方服务端不主动断开的话,连接会一直存在。

    client := &http.Client{
        Transport: &http.Transport{
            DialContext: (&net.Dialer{
                Timeout: 5 * time.Second,
            }).DialContext,
        },
        Timeout: 5 * time.Second,
    }
    

    最后,将配置修改,大功告成

    client := &http.Client{
        Transport: &http.Transport{
            DialContext: (&net.Dialer{
                Timeout: 5 * time.Second,
            }).DialContext,
            DisableKeepAlives: true,
        },
        Timeout: 5 * time.Second,
    }
    

    参照:
    https://sanyuesha.com/2019/09/10/go-http-request-goroutine-leak/
    https://github.com/docker/distribution/issues/473

    相关文章

      网友评论

        本文标题:go (*persistConn).writeLoop 导致go

        本文链接:https://www.haomeiwen.com/subject/gxihvktx.html