最近用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)
有疑问加站长微信联系(非本文作者)

你所说的持续增长,程序运行了多久?
上海乐焰网络科技有限公司长期招聘Golang工程师 有意者联系 QQ 2781023865 人事李小姐
初步判定是由于大量make []byte 导致内存不释放?是不是其他地方导致的,另外,ReadPacket 里面,没有对size 值 进行 限制么
每来一次请求,你都data := make([]byte, size)一次,这个data又被外部的HandlerMsg继续使用,不太清楚你HandlerMsg里是怎么使用data的。 具体原因我也不清楚, 不过正确的思路应该是,开两个协程,一个接受数据并放在channel里,另一个读channel并处理。go中不同协程间数据传递最好使用channel
程序运行几周的时间,正常情况下会维持在150M左右,异常的时候会突增到好几个GB。
size值目前没有做限制,从pprof里面来看就是这个函数引起的。
HandlerMsg里面会根据不通的Packet Type 来进行对应的处理,基本没有耗时操作,之前看过一些其他库大致也是你这个思路,后期有时间尝试修改下,谢谢!
上次重启过后运行了一周目前很稳定,pprof发现内存占用和之前完全不一样,很奇怪
最新pprof
ReadPacket 方法中 make([]byte, size) 分配的内存不会立即释放,而是由gc进行回收。而gc回收间隔是2分钟。所以,在2次gc回收间隔期间,如果有大量请求发过来,会导致内存飙升。 手动调用gc会严重拖慢性能,建议使用sync.Pool 内存池管理内存分配。
客户端都是长连接,不会有突然大量请求过来的情况,而且内存持续几天都不会被回收,重启一次后发现就没有这种情况了,很奇怪,回头看下sync.Pool,感谢!
有种情况可能是,如果接收到的数据包,不是合法的封包数据。看到你代码里面,数据包头是4个字节,也就是说,非法数据包时,这4个字节数据并不是数据长度,那么会导致一种情况是,这个值非常大,那么接下来的 make([]byte, size),会分配很大的内存。你看这样会不会有问题呢
4字节无符号整数,最大值为 4294967295
也可以看看这个 资源池 https://github.com/youtube/vitess/tree/master/go/pools,或者 https://github.com/taruti/pool,https://github.com/stretchr/hoard
sync.Pool 使用起来还是有些坑的, http://www.reddit.com/r/golang/comments/2ap67l/when_to_use_syncpool_and_when_not_to/