转到每个持久连接的HTTP内存使用

转到每个持久连接的HTTP内存使用,http,memory,go,profile,Http,Memory,Go,Profile,我正在编写一个Go web服务器,它向大量客户端发送数据。我希望它能够支持数以万计的同时连接。这是我的代码(它只是保持连接打开并发送保持活动的事件): 所以我有几个问题: 为什么内存使用率如此之高。我希望每个连接大约有10KB 为什么pprof认为堆是14MB,而Windows说内存使用是70MB?剩下的是一堆吗 是否有任何方法可以将HTTP响应的控制权转移到中央goroutine,并从handleTest()返回而不关闭连接?这会节省我的内存吗?还是内存全部用在http.ResponseWri

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

所以我有几个问题:

  • 为什么内存使用率如此之高。我希望每个连接大约有10KB
  • 为什么pprof认为堆是14MB,而Windows说内存使用是70MB?剩下的是一堆吗
  • 是否有任何方法可以将HTTP响应的控制权转移到中央goroutine,并从
    handleTest()
    返回而不关闭连接?这会节省我的内存吗?还是内存全部用在
    http.ResponseWriter
    对象中
  • 编辑:用于3。看起来我可以用

    编辑2:我尝试使用
    劫持者重新实现它。它将每个连接的内存使用量减少到10 kB左右,这更加合理

    为什么pprof认为堆是14MB,而Windows说内存使用是70MB?剩下的是一堆吗


    除了堆,还有Go运行时、堆栈和代码段。此外,操作系统可能会分配比实际需要更多的资源。另外,Windows报告的内存量是驻留内存还是操作系统内存分配的总量?

    对于2:除了堆,还有堆栈、用于GC的堆位图、用于分配的可用空间(大致与堆大小相同)。内存也不会立即返回操作系统。您是否运行过一些严重的负载测试?基本内存消耗可能会扭曲结果。我会增加并发连接的数量,比如说预期负载的50%(可能需要一个客户机网格来生成负载),然后看看内存消耗是如何的。侧节点:即使是14MB/1k连接,考虑到今天的RAM大小,我也不会在过早优化方面投入太多精力。70k+连接/GB对我来说似乎是个不错的选择。只要确保你能水平伸缩,鲍勃是你叔叔。是的,我已经连接了10k。基本消耗量只有3MB,因此不会对其产生太大影响。pprof是错误的——它从来都不是每1k连接14 MB。windows报告使用的内容和您的程序使用的内容可能会大不相同。操作系统可以以它认为最有效的方式分配内存,并且通常不会释放内存,除非有压力这样做。将windows总内存使用量进行划分并不能真正告诉您服务器在每个连接上实际执行的操作。“专用工作集”是其他应用程序无法使用的内存,它不是缓存或未使用的虚拟内存或其他任何东西。Go分配的内存可能超出了它的需要,但我不认为是这样,因为内存使用量与连接的数量成正比——它不会像您所期望的那样逐步增加。无论如何,在这种情况下,Windows值比Go的自我报告更相关。它说的是“私有工作集”,所以我假设它是常驻的。另外,我不认为这是因为过度分配,因为我逐渐添加连接,内存使用呈线性增长,而不是逐步增长。基本内存使用量只有3MB,所以我认为Go运行时或代码使用量不大。
    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)
            }
        }
    }
    
    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