我想开发一种软​​件,以使用GoLang处理来自多个tcp连接的请求,并在具有10Gb-nic的服务器上运行。

似乎性能不足以在单核上接收/发送数据。因此,我想实现该软件以在多个CPU内核上接收/发送数据。

然后,我做了一个简单的测试服务器,以检查GoLang是否可以在多个CPU内核上接收/发送数据。它启动多个(16)goroutine,以在同一侦听器上启动http服务器,并使用ab(Apache Benchmark)作为客户端。

服务器启动后,我只看到一个线程调用EpollWait,但是服务器启动了18个线程,而当我使用16个并发启动ab进行测试时,服务器仅占用一个内核。

因此,问题是:有什么方法可以启动多个线程来处理GoLang中来自多个tcp连接的数据接收/发送。还是我必须调用syscall.EpollWait来创建网络框架,以自己完成?

服务器的测试代码:

package main

import (
  "io"
  "log"
  "net"
  "net/http"
  "runtime"
)

type HandlerFunction struct{}

func (self HandlerFunction) ServeHTTP(w http.ResponseWriter, req *http.Request) {
  data := "Hello"
  //fmt.Printf("data_len=%d\n", len(data))
  io.WriteString(w, string(data))
}

func RoutineFunction(hs *http.Server, l net.Listener) {
  runtime.LockOSThread()
  err := hs.Serve(l)
  if err != nil {
    log.Fatalf("serve fail, err=[%s]", err)
  }
}

func main() {
  runtime.GOMAXPROCS(16)

  l, err := net.Listen("tcp", "0.0.0.0:12345")
  if err != nil {
    log.Fatalf("listen fail, err=[%s]", err)
  }

  for i := 0; i < 15; i++ {
    hs := http.Server{}
    hs.Handler = HandlerFunction{}
    go RoutineFunction(&hs, l)
  }

  hs := http.Server{}
  hs.Handler = HandlerFunction{}
  RoutineFunction(&hs, l)
}

最佳答案

不完全是。

Go运行时(从go1.5开始)使用单个网络轮询器。当您在服务器上完成实际工作时,这很少成为瓶颈,并且运行goroutine的线程将保持繁忙。但是在某些情况下,无论是具有足够的内核还是足够的吞吐量,Go运行时都会开始受到影响,尤其是因为轮询程序通常与执行IO的线程位于不同的NUMA节点中。

如果需要以这种规模运行,我目前建议将Go服务器限制为单个NUMA节点,并运行该服务器的多个实例。

异常(exception)是,如果将套接字置于阻塞模式,则该套接字上的IO将绑定(bind)到单个OS线程。我没有对此方法进行任何吞吐量测试,以查看是否有任何好处,但是,如果您同时使用相对较少的套接字,那么尝试它不会有任何伤害。

07-24 09:46
查看更多