go语言服务端内存持续增长,求解惑!

wuyingsong · 2015-02-20 12:50:38 · 5612 次点击 · 大约8小时之前 开始浏览    置顶
这是一个创建于 2015-02-20 12:50:38 的主题,其中的信息可能已经有所发展或是发生改变。

最近用golang开发了一个socket服务端,主要逻辑就是接收客户端发来的数据解析后存入opentsdb中,但是跑一段时间后发现内存就会增长,而且不会被gc回收,初步判定是由于大量make []byte 导致内存不释放,代码如下,求高人解惑?

func StartTCP() error {
    tcp_addr, _ := net.ResolveTCPAddr("tcp4", tcp_listen)
    listener, err := net.ListenTCP("tcp4", tcp_addr)
    if err != nil {
        return err
    }
    defer listener.Close()
    log.Info("start tcp listen on %s", tcp_listen)
    for {
        conn, err := listener.AcceptTCP()
        if err != nil {
            log.Error("accept error %s", err.Error())
            continue
        }
        log.Info("new session create %s", conn.RemoteAddr().String())
        go HandlerConn(conn)
    }
}


func ReadPacket(conn *net.TCPConn) []byte {
    head := make([]byte, 4)
    _, err := io.ReadFull(conn, head)
    if err != nil {
        return nil
    }
    size := binary.BigEndian.Uint32(head)
    data := make([]byte, size)
    _, err = io.ReadFull(conn, data)
    if err != nil {
        return nil
    }
    return data
}


func HandlerConn(conn *net.TCPConn) {
    addr := conn.RemoteAddr().String()
    defer func() {
        if err := conn.Close(); err != nil {
            log.Error("close session error %s", err.Error())
        } else {
            log.Info("session closed %s", addr)
        }
    }()
    data := ReadPacket(conn)
    if len(data) == 0 {
        log.Warn("empty data")
        return
    }
    go HandlerMsg(conn, data, t)
}

附pprof信息

(pprof) top
9.92GB of 9.93GB total (  100%)
Dropped 359 nodes (cum <= 0.05GB)
  flat  flat%   sum%        cum   cum%
9.92GB   100%   100%     9.92GB   100%  main.ReadPacket
     0     0%   100%     9.92GB   100%  main.HandlerConn
     0     0%   100%     9.92GB   100%  runtime.goexit

(pprof) list main.ReadPacket
Total: 9.93GB
ROUTINE ======================== main.ReadPacket in /tmp/td-        server/tcp_server.go
9.92GB     9.92GB (flat, cum)   100% of Total
     .          .     30:       go HandlerConn(conn)
     .          .     31:   }
     .          .     32:}
     .          .     33:
     .          .     34:func ReadPacket(conn *net.TCPConn) []byte {
     .          .     35:   head := make([]byte, 4)
     .          .     36:   _, err := io.ReadFull(conn, head)
     .          .     37:   if err != nil {
     .          .     38:       return nil
     .          .     39:   }
     .          .     40:   size := binary.BigEndian.Uint32(head)
9.92GB     9.92GB     41:   data := make([]byte, size)
     .          .     42:   _, err = io.ReadFull(conn, data)
     .          .     43:   if err != nil {
     .          .     44:       return nil
     .          .     45:   }
     .          .     46:   return data
     .          .     47:}

上次重启过后运行了一周目前很稳定,pprof发现内存占用和之前完全不一样,很奇怪

最新pprof

(pprof) top
3201.70kB of 3201.70kB total (  100%)
Dropped 362 nodes (cum <= 16.01kB)
Showing top 10 nodes out of 19 (cum >= 1536.14kB)
      flat  flat%   sum%        cum   cum%
 1024.11kB 31.99% 31.99%  1024.11kB 31.99%  main.(*Device).getIndex
  641.34kB 20.03% 52.02%   641.34kB 20.03%  main.init_config
  512.14kB 16.00% 68.01%   512.14kB 16.00%  mcommoninit
  512.08kB 15.99% 84.01%   512.08kB 15.99%  database/sql.(*DB).addDepLocked
  512.03kB 15.99%   100%   512.03kB 15.99%  bufio.(*Reader).ReadString
         0     0%   100%   512.08kB 15.99%  database/sql.(*DB).Query
         0     0%   100%   512.08kB 15.99%  database/sql.(*DB).QueryRow
         0     0%   100%   512.08kB 15.99%  database/sql.(*DB).conn
         0     0%   100%   512.08kB 15.99%  database/sql.(*DB).query
         0     0%   100%  1536.14kB 47.98%  main.(*Device).CollectHW
(pprof)

有疑问加站长微信联系(非本文作者)

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

5612 次点击  ∙  2 赞  
加入收藏 微博
14 回复  |  直到 2015-03-25 07:48:30
blov
blov · #1 · 10年之前

你所说的持续增长,程序运行了多久?

selina
selina · #2 · 10年之前

上海乐焰网络科技有限公司长期招聘Golang工程师 有意者联系 QQ 2781023865 人事李小姐

ckrissun
ckrissun · #3 · 10年之前

初步判定是由于大量make []byte 导致内存不释放?是不是其他地方导致的,另外,ReadPacket 里面,没有对size 值 进行 限制么

baiyuxiong
baiyuxiong · #4 · 10年之前

每来一次请求,你都data := make([]byte, size)一次,这个data又被外部的HandlerMsg继续使用,不太清楚你HandlerMsg里是怎么使用data的。 具体原因我也不清楚, 不过正确的思路应该是,开两个协程,一个接受数据并放在channel里,另一个读channel并处理。go中不同协程间数据传递最好使用channel

wuyingsong
wuyingsong · #5 · 10年之前
blovblov #1 回复

你所说的持续增长,程序运行了多久?

程序运行几周的时间,正常情况下会维持在150M左右,异常的时候会突增到好几个GB。

wuyingsong
wuyingsong · #6 · 10年之前
ckrissunckrissun #3 回复

初步判定是由于大量make []byte 导致内存不释放?是不是其他地方导致的,另外,ReadPacket 里面,没有对size 值 进行 限制么

size值目前没有做限制,从pprof里面来看就是这个函数引起的。

wuyingsong
wuyingsong · #7 · 10年之前
baiyuxiongbaiyuxiong #4 回复

每来一次请求,你都data := make([]byte, size)一次,这个data又被外部的HandlerMsg继续使用,不太清楚你HandlerMsg里是怎么使用data的。 具体原因我也不清楚, 不过正确的思路应该是,开两个协程,一个接受数据并放在channel里,另一个读channel并处理。go中不同协程间数据传递最好使用channel

HandlerMsg里面会根据不通的Packet Type 来进行对应的处理,基本没有耗时操作,之前看过一些其他库大致也是你这个思路,后期有时间尝试修改下,谢谢!

wuyingsong
wuyingsong · #8 · 10年之前

上次重启过后运行了一周目前很稳定,pprof发现内存占用和之前完全不一样,很奇怪

最新pprof

(pprof) top
3201.70kB of 3201.70kB total (  100%)
Dropped 362 nodes (cum <= 16.01kB)
Showing top 10 nodes out of 19 (cum >= 1536.14kB)
      flat  flat%   sum%        cum   cum%
 1024.11kB 31.99% 31.99%  1024.11kB 31.99%  main.(*Device).getIndex
  641.34kB 20.03% 52.02%   641.34kB 20.03%  main.init_config
  512.14kB 16.00% 68.01%   512.14kB 16.00%  mcommoninit
  512.08kB 15.99% 84.01%   512.08kB 15.99%  database/sql.(*DB).addDepLocked
  512.03kB 15.99%   100%   512.03kB 15.99%  bufio.(*Reader).ReadString
         0     0%   100%   512.08kB 15.99%  database/sql.(*DB).Query
         0     0%   100%   512.08kB 15.99%  database/sql.(*DB).QueryRow
         0     0%   100%   512.08kB 15.99%  database/sql.(*DB).conn
         0     0%   100%   512.08kB 15.99%  database/sql.(*DB).query
         0     0%   100%  1536.14kB 47.98%  main.(*Device).CollectHW
(pprof) 
minghaoxi
minghaoxi · #9 · 10年之前

ReadPacket 方法中 make([]byte, size) 分配的内存不会立即释放,而是由gc进行回收。而gc回收间隔是2分钟。所以,在2次gc回收间隔期间,如果有大量请求发过来,会导致内存飙升。 手动调用gc会严重拖慢性能,建议使用sync.Pool 内存池管理内存分配。

wuyingsong
wuyingsong · #10 · 10年之前
minghaoximinghaoxi #9 回复

ReadPacket 方法中 make([]byte, size) 分配的内存不会立即释放,而是由gc进行回收。而gc回收间隔是2分钟。所以,在2次gc回收间隔期间,如果有大量请求发过来,会导致内存飙升。 手动调用gc会严重拖慢性能,建议使用sync.Pool 内存池管理内存分配。

客户端都是长连接,不会有突然大量请求过来的情况,而且内存持续几天都不会被回收,重启一次后发现就没有这种情况了,很奇怪,回头看下sync.Pool,感谢!

ckrissun
ckrissun · #11 · 10年之前
wuyingsongwuyingsong #6 回复

#3楼 @ckrissun size值目前没有做限制,从pprof里面来看就是这个函数引起的。

有种情况可能是,如果接收到的数据包,不是合法的封包数据。看到你代码里面,数据包头是4个字节,也就是说,非法数据包时,这4个字节数据并不是数据长度,那么会导致一种情况是,这个值非常大,那么接下来的 make([]byte, size),会分配很大的内存。你看这样会不会有问题呢

ckrissun
ckrissun · #12 · 10年之前
ckrissunckrissun #11 回复

#6楼 @wuyingsong 有种情况可能是,如果接收到的数据包,不是合法的封包数据。看到你代码里面,数据包头是4个字节,也就是说,非法数据包时,这4个字节数据并不是数据长度,那么会导致一种情况是,这个值非常大,那么接下来的 make([]byte, size),会分配很大的内存。你看这样会不会有问题呢

4字节无符号整数,最大值为 4294967295

ckrissun
ckrissun · #13 · 10年之前
wuyingsongwuyingsong #10 回复

#9楼 @minghaoxi 客户端都是长连接,不会有突然大量请求过来的情况,而且内存持续几天都不会被回收,重启一次后发现就没有这种情况了,很奇怪,回头看下sync.Pool,感谢!

ckrissun
ckrissun · #14 · 10年之前
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传