我们编写了最简单的TCP服务器(带有少量日志记录),以检查内存占用情况(请参见下面的tcp-server.go)。
该服务器仅接受连接并不执行任何操作。它在运行Ubuntu 12.04.4 LTS服务器(内核3.2.0-61-generic)上,并使用Go版本go1.3 linux/amd64。
附加的基准测试程序(pulse.go)在此示例中创建10k个连接,30秒后断开它们,重复此循环三次,然后连续重复1k个连接/断开的小脉冲。用于测试的命令是./pulse -big=10000 -bs=30。
第一个附加图是通过记录每当客户端数量变化时的runtime.ReadMemStats而获得的,第二个图是“top”所看到的服务器进程的RES内存大小。
服务器从可忽略的1.6KB内存开始。然后,内存由10k个连接的“大”脉冲设置为约60MB(如top所示),或者为约16MB的ReadMemStats的“SystemMemory”。正如预期的那样,当10K个脉冲结束时,正在使用的内存会降低,最终程序会释放内存回到操作系统,这可以通过灰色的“Released Memory”线看出。
问题在于系统内存(相应地,“top”所看到的RES内存)从未显着下降(尽管在第二个图中有所下降)。
我们希望确保针对偶尔出现的峰值的不规则流量的可伸缩性,以及能够在同一台计算机上运行具有不同时间峰值的多个服务器。是否有一种方法可以在合理的时间范围内有效地确保释放尽可能多的内存回到系统中?
代码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)
}
}