每个持久连接的Go HTTP内存使用量

huangapple go评论88阅读模式
英文:

Go HTTP memory use per persistent connection

问题

我正在编写一个 Go Web 服务器,向大量客户端发送 Server-Sent Events。我希望它能够支持数万个同时连接。以下是我的代码(它只是保持连接打开并发送保持活动的事件):

func handleTest(w http.ResponseWriter, r *http.Request) {
	h := w.Header()
	h.Set("Content-Type", "text/event-stream; charset=utf-8")
	h.Set("Cache-Control", "no-cache, no-store, must-revalidate")
	h.Set("Connection", "keep-alive")

	flusher := w.(http.Flusher)
	notifier := w.(http.CloseNotifier)

	flusher.Flush()

	// Just send keep-alives.
	keepAliveTime := 5 * time.Second
	keepAlive := time.NewTimer(keepAliveTime)
	defer keepAlive.Stop()

	for {
		select {
		case <-notifier.CloseNotify():
			// The connection has been closed.
			return

		case <-keepAlive.C:
			if _, err := io.WriteString(w, "event: keep-alive\ndata: null\n\n"); err != nil {
				log.Println(err)
				return
			}
			flusher.Flush()
			keepAlive.Reset(keepAliveTime)
		}
	}
}

在有1000个连接的情况下,Windows 报告每个连接使用约70 kB 的 RAM。如果我加入我实际正在做的所有内容(还有另一个 goroutine 和一些次要的事件编码函数),它会增加到每个连接300 kB。这似乎很多。在有1000个连接的情况下,pprof heap 的输出如下:

14683.25kB of 14683.25kB total (100%)
Dropped 12 nodes (cum <= 73.42kB)
Showing top 10 nodes out of 23 (cum >= 512.19kB)
      flat  flat%   sum%        cum   cum%
11091.50kB 75.54% 75.54% 11091.50kB 75.54%  io.copyBuffer
    2053kB 13.98% 89.52%     2053kB 13.98%  net/http.newBufioWriterSize
     514kB  3.50% 93.02%      514kB  3.50%  net/http.newBufioReader
  512.56kB  3.49% 96.51%   512.56kB  3.49%  runtime.makeslice
  512.19kB  3.49%   100%   512.19kB  3.49%  net.newFD
         0     0%   100% 11091.50kB 75.54%  io.Copy
         0     0%   100%  1540.19kB 10.49%  main.main
         0     0%   100%   512.19kB  3.49%  net.(*TCPListener).AcceptTCP
         0     0%   100%   512.19kB  3.49%  net.(*netFD).accept
         0     0%   100%   512.19kB  3.49%  net.(*netFD).acceptOne

所以我有几个问题:

  1. 为什么内存使用量似乎如此高?我本来期望每个连接大约使用10 kB。
  2. 为什么 pprof 认为堆使用量为14 MB,而 Windows 报告的内存使用量为70 MB?剩下的是堆栈吗?
  3. 是否有任何方法可以将 HTTP 响应的控制权转移到一个中央 goroutine,并从 handleTest() 返回而不关闭连接?这样做是否会节省内存,或者内存使用量全部在 http.ResponseWriter 对象中?

编辑:对于第3个问题,看起来我可以使用 Hijacker

编辑2:我尝试使用 Hijacker 进行重新实现。它将内存使用量减少到每个连接约10 kB,这更加合理!

英文:

I'm writing a Go web server that sends Server-Sent Events to a load of clients. I'd like it to support tens of thousands of simultaneous connections. Here is my code (it just keeps the connection open and sends keep-alive events):

func handleTest(w http.ResponseWriter, r *http.Request) {
	h := w.Header()
	h.Set(&quot;Content-Type&quot;, &quot;text/event-stream; charset=utf-8&quot;)
	h.Set(&quot;Cache-Control&quot;, &quot;no-cache, no-store, must-revalidate&quot;)
	h.Set(&quot;Connection&quot;, &quot;keep-alive&quot;)

	flusher := w.(http.Flusher)
	notifier := w.(http.CloseNotifier)

	flusher.Flush()

	// Just send keep-alives.
	keepAliveTime := 5 * time.Second
	keepAlive := time.NewTimer(keepAliveTime)
	defer keepAlive.Stop()

	for {
		select {
		case &lt;-notifier.CloseNotify():
			// The connection has been closed.
			return

		case &lt;-keepAlive.C:
			if _, err := io.WriteString(w, &quot;event: keep-alive\ndata: null\n\n&quot;); err != nil {
				log.Println(err)
				return
			}
			flusher.Flush()
			keepAlive.Reset(keepAliveTime)
		}
	}
}

With 1000 connections Windows reports about 70 kB of RAM use per connection. If I add in all the stuff I am actually doing (there's another goroutine, and some minor event encoding functions) it balloons to 300 kB per connection. This seems like lots. With 1000 connections here is what pprof heap says:

14683.25kB of 14683.25kB total (  100%)
Dropped 12 nodes (cum &lt;= 73.42kB)
Showing top 10 nodes out of 23 (cum &gt;= 512.19kB)
      flat  flat%   sum%        cum   cum%
11091.50kB 75.54% 75.54% 11091.50kB 75.54%  io.copyBuffer
    2053kB 13.98% 89.52%     2053kB 13.98%  net/http.newBufioWriterSize
     514kB  3.50% 93.02%      514kB  3.50%  net/http.newBufioReader
  512.56kB  3.49% 96.51%   512.56kB  3.49%  runtime.makeslice
  512.19kB  3.49%   100%   512.19kB  3.49%  net.newFD
         0     0%   100% 11091.50kB 75.54%  io.Copy
         0     0%   100%  1540.19kB 10.49%  main.main
         0     0%   100%   512.19kB  3.49%  net.(*TCPListener).AcceptTCP
         0     0%   100%   512.19kB  3.49%  net.(*netFD).accept
         0     0%   100%   512.19kB  3.49%  net.(*netFD).acceptOne

So I have a few questions:

  1. Why is the memory use so seemingly high. I would have expected something like 10 kB per connection.
  2. Why does pprof think the heap is 14 MB, but Windows says the memory use is 70 MB? Is the rest the stack?
  3. Is there any way I can transfer control of the HTTP response to a central goroutine, and return from handleTest() without closing the connection? Would that save me memory or is the memory use all in the http.ResponseWriter object?

Edit: For 3. it looks like I can use Hijacker

Edit 2: I tried reimplementing it using Hijacker. It reduced memory usage to about 10 kB per connection, which is much more reasonable!

答案1

得分: 1

为什么pprof认为堆的大小是14MB,但Windows显示的内存使用量是70MB?剩下的是栈吗?

除了堆之外,还有Go运行时、栈、代码段。此外,操作系统可能分配的内存超过实际需要的量。另外,Windows报告的数量是指驻留内存还是操作系统分配的总内存?

英文:

> Why does pprof think the heap is 14 MB, but Windows says the memory use is 70 MB? Is the rest the stack?

Besides the heap, there's also the Go runtime, the stack, the code segment. Also the OS might allocate more than it's actually needed. Also, is the amount reported by Windows the resident memory or the total allocated by the OS memory?

huangapple
  • 本文由 发表于 2016年1月14日 16:45:33
  • 转载请务必保留本文链接:https://go.coder-hub.com/34785048.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定