zoukankan      html  css  js  c++  java
  • golang使用pprof检查goroutine泄露

    有一段时间,我们的推送服务socket占用很不正常,我们自己统计的同时在线就10w的用户,但是占用的socket竟然达到30w,然后查看goroutine的数量,发现已经60w+。

    每个用户占用一个socket,而一个socket,有read和write两个goroutine,简化的代码如下:

    c, _ := listerner.Accept()
    
    go c.run()
    
    func (c *conn) run() {
        go c.onWrite()
        c.onRead()
    }
    
    func (c *conn) onRead() {
        stat.AddConnCount(1)
    
        //on something
    
        stat.AddConnCount(-1)
    
        //clear
        //notify onWrite to quit
    }
    

    当时我就怀疑,用户同时在线的统计是正确的,也就是之后的clear阶段出现了问题,导致两个goroutine都无法正常结束。在检查代码之后,我们发现了一个可疑的地方,因为我们不光有自己的统计,还会将一些统计信息发送到我们公司的统计平台,代码如下:

    ch = make([]byte, 100000)
    func send(msg []byte) {
        ch <- msg
    }
    
    //在另一个goroutine的地方,
    msg <- msg
    httpsend(msg)
    

    我们channel的缓存分配了10w,如果公司统计平台出现了问题,可能会导致channel阻塞。但到底是不是这个原因呢?

    幸运的是,我们先前已经在代码里面内置了pprof的功能,通过pprof goroutine的信息,发现大量的goroutine的当前运行函数在httpsend里面,也就是说,公司的统计平台在大并发下面服务不可用,虽然我们有http超时的处理,但是因为发送的数据量太频繁,导致整体阻塞。

    临时的解决办法就是关闭了统计信息的发送,后续我们会考虑将其发送到自己的mq上面,虽然也可能会出现mq服务不可用的问题,但是说句实话,比起自己实现的mq,公司的统计平台更让我不可信。

    这同时也给了我一个教训,访问外部服务一定要好好处理外部服务不可用的情况,即使可用,也要考虑压力问题。

    对于pprof如何查看了goroutine的问题,可以通过一个简单的例子说明:

    package main
    
    import (
        "net/http"
        "runtime/pprof"
    )
    
    var quit chan struct{} = make(chan struct{})
    
    func f() {
        <-quit
    }
    
    func handler(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Content-Type", "text/plain")
    
        p := pprof.Lookup("goroutine")
        p.WriteTo(w, 1)
    }
    
    func main() {
        for i := 0; i < 10000; i++ {
            go f()
        }
    
        http.HandleFunc("/", handler)
        http.ListenAndServe(":11181", nil)
    }
    

    这上面的例子中,我们启动了10000个goroutine,并阻塞,然后通过访问http://localhost:11181/,我们就可以得到整个goroutine的信息,仅列出关键信息:

    goroutine profile: total 10004
    
    10000 @ 0x186f6 0x616b 0x6298 0x2033 0x188c0
    #   0x2033  main.f+0x33 /Users/siddontang/test/pprof.go:11
    

    可以看到,在main.f这个函数中,有10000个goroutine正在执行,符合我们的预期。

    在go里面,还有很多运行时查看机制,可以很方便的帮我们定位程序问题,不得不赞一下。

  • 相关阅读:
    第 15 章 标签页和工具提示插件
    第 14 章 下拉菜单和滚动监听插件
    第 13 章 模态框插件
    第 12 章 列表组面板和嵌入组件
    第 11 章 进度条媒体对象和 Well 组件
    第 10 章 巨幕页头缩略图和警告框组件
    第 9 章 路径分页标签和徽章组件
    lock()与lockInterruptibly()的区别
    MySQL中Innodb的聚簇索引和非聚簇索引
    MySQL使用可重复读作为默认隔离级别的原因
  • 原文地址:https://www.cnblogs.com/xiaowangba/p/6313755.html
Copyright © 2011-2022 走看看