【发布时间】:2014-08-14 03:16:15
【问题描述】:
我们编写了最简单的 TCP 服务器(带有少量日志记录)来检查内存占用(参见下面的 tcp-server.go)
服务器只接受连接并且什么都不做。它正在使用 Go 版本 go1.3 linux/amd64 的 Ubuntu 12.04.4 LTS 服务器(内核 3.2.0-61-generic)上运行。
附加的基准测试程序 (pulse.go) 在此示例中创建 10k 连接,在 30 秒后断开它们,重复此循环 3 次,然后连续重复 1k 连接/断开的小脉冲。用于测试的命令是 ./pulse -big=10000 -bs=30。
附上第一张图是通过记录runtime.ReadMemStats当客户端数量发生500倍变化时得到的,第二张图是“top”看到的服务器进程的RES内存大小。
服务器以可忽略不计的 1.6KB 内存启动。然后内存由 10k 连接的“大”脉冲设置为 ~60MB(如顶部所示),或大约 16MB“SystemMemory”,如 ReadMemStats 所示。正如预期的那样,当 10K 脉冲结束时,正在使用的内存下降,最终程序开始将内存释放回操作系统,灰色的“已释放内存”线就是证明。
问题在于系统内存(以及相应地,“top”看到的 RES 内存)从未显着下降(尽管它下降了一点,如第二张图所示)。
我们预计在 10K 脉冲结束后,内存将继续释放,直到 RES 大小达到处理每个 1k 脉冲所需的最小值(如“top”所示为 8m RES,由运行时.ReadMemStats)。相反,RES 保持在 56MB 左右,并且在使用中从未从最高值 60MB 下降。
我们希望确保偶尔出现峰值的不规则流量的可扩展性,并能够在同一机器上运行多个在不同时间出现峰值的服务器。有没有办法有效地确保在合理的时间范围内将尽可能多的内存释放回系统?
代码https://gist.github.com/eugene-bulkin/e8d690b4db144f468bc5:
server.go:
package main
import (
"net"
"log"
"runtime"
"sync"
)
var m sync.Mutex
var num_clients = 0
var cycle = 0
func printMem() {
var ms runtime.MemStats
runtime.ReadMemStats(&ms)
log.Printf("Cycle #%3d: %5d clients | System: %8d Inuse: %8d Released: %8d Objects: %6d\n", cycle, num_clients, ms.HeapSys, ms.HeapInuse, ms.HeapReleased, ms.HeapObjects)
}
func handleConnection(conn net.Conn) {
//log.Println("Accepted connection:", conn.RemoteAddr())
m.Lock()
num_clients++
if num_clients % 500 == 0 {
printMem()
}
m.Unlock()
buffer := make([]byte, 256)
for {
_, err := conn.Read(buffer)
if err != nil {
//log.Println("Lost connection:", conn.RemoteAddr())
err := conn.Close()
if err != nil {
log.Println("Connection close error:", err)
}
m.Lock()
num_clients--
if num_clients % 500 == 0 {
printMem()
}
if num_clients == 0 {
cycle++
}
m.Unlock()
break
}
}
}
func main() {
printMem()
cycle++
listener, err := net.Listen("tcp", ":3033")
if err != nil {
log.Fatal("Could not listen.")
}
for {
conn, err := listener.Accept()
if err != nil {
log.Println("Could not listen to client:", err)
continue
}
go handleConnection(conn)
}
}
pulse.go:
package main
import (
"flag"
"net"
"sync"
"log"
"time"
)
var (
numBig = flag.Int("big", 4000, "Number of connections in big pulse")
bigIters = flag.Int("i", 3, "Number of iterations of big pulse")
bigSep = flag.Int("bs", 5, "Number of seconds between big pulses")
numSmall = flag.Int("small", 1000, "Number of connections in small pulse")
smallSep = flag.Int("ss", 20, "Number of seconds between small pulses")
linger = flag.Int("l", 4, "How long connections should linger before being disconnected")
)
var m sync.Mutex
var active_conns = 0
var connections = make(map[net.Conn] bool)
func pulse(n int, linger int) {
var wg sync.WaitGroup
log.Printf("Connecting %d client(s)...\n", n)
for i := 0; i < n; i++ {
wg.Add(1)
go func() {
m.Lock()
defer m.Unlock()
defer wg.Done()
active_conns++
conn, err := net.Dial("tcp", ":3033")
if err != nil {
log.Panicln("Unable to connect: ", err)
return
}
connections[conn] = true
}()
}
wg.Wait()
if len(connections) != n {
log.Fatalf("Unable to connect all %d client(s).\n", n)
}
log.Printf("Connected %d client(s).\n", n)
time.Sleep(time.Duration(linger) * time.Second)
for conn := range connections {
active_conns--
err := conn.Close()
if err != nil {
log.Panicln("Unable to close connection:", err)
conn = nil
continue
}
delete(connections, conn)
conn = nil
}
if len(connections) > 0 {
log.Fatalf("Unable to disconnect all %d client(s) [%d remain].\n", n, len(connections))
}
log.Printf("Disconnected %d client(s).\n", n)
}
func main() {
flag.Parse()
for i := 0; i < *bigIters; i++ {
pulse(*numBig, *linger)
time.Sleep(time.Duration(*bigSep) * time.Second)
}
for {
pulse(*numSmall, *linger)
time.Sleep(time.Duration(*smallSep) * time.Second)
}
}
【问题讨论】:
-
还询问(并回答)here。 (Alec,在制作交叉帖子时提供指向他们自己的交叉帖子的链接是一个很好的网友的责任。)
标签: memory memory-management go