【问题标题】:Limited concurrent connections in GoGo 中的有限并发连接
【发布时间】:2017-11-20 22:49:59
【问题描述】:

我在 Go 中有以下基本的 http 服务器。对于每个传入请求,它都会发布 5 个传出 http 请求。他们每个人大约需要3-5秒。我无法在 8 gig Ram 四核机器上实现超过 200 个请求/秒。

package main

import (
    "flag"
    "fmt"
    "net/http"
    _"net/url"
    //"io/ioutil"
    "time"
    "log"
    "sync"
    //"os"
    "io/ioutil"
)

// Job holds the attributes needed to perform unit of work.
type Job struct {
    Name  string
    Delay time.Duration
}

func requestHandler(w http.ResponseWriter, r *http.Request) {
    // Make sure we can only be called with an HTTP POST request.
    fmt.Println("in request handler")
    if r.Method != "POST" {
        w.Header().Set("Allow", "POST")
        w.WriteHeader(http.StatusMethodNotAllowed)
        return
    }

    // Set name and validate value.
    name := r.FormValue("name")
    if name == "" {
        http.Error(w, "You must specify a name.", http.StatusBadRequest)
        return
    }

    delay := time.Second * 0

    // Create Job and push the work onto the jobQueue.
    job := Job{Name: name, Delay: delay}
    //jobQueue <- job

    fmt.Println("creating worker")
    result := naiveWorker(name, job)
    fmt.Fprintf(w, "your task %s has been completed ,here are the results : %s", job.Name, result)

}

func naiveWorker(id string, job Job) string {
    var wg sync.WaitGroup
    responseCounter := 0;
    totalBodies := "";
    fmt.Printf("worker%s: started %s\n", id, job.Name)

    var urls = []string{
        "https://someurl1",
        "https://someurl2",
        "https://someurl3",
        "https://someurl4",
        "https://someurl5",
    }

    for _, url := range urls {
        // Increment the WaitGroup counter.

        wg.Add(1)
        // Launch a goroutine to fetch the URL.
        go func(url string) {

            // Fetch the URL.
            resp, err := http.Get(url)
            if err != nil {
                fmt.Printf("got an error")
                //  panic(err)

            } else {
                defer resp.Body.Close()
                body, err := ioutil.ReadAll(resp.Body)
                if err != nil {
                    totalBodies += string(body)
                }
            }
            responseCounter ++
            // Decrement the counter when the goroutine completes.
            defer wg.Done()

        }(url)
    }
    wg.Wait()
    fmt.Printf("worker%s: completed %s with %d calls\n", id, job.Name, responseCounter)
    return totalBodies
}

func main() {
    var (
        port = flag.String("port", "8181", "The server port")
    )
    flag.Parse()

    // Start the HTTP handler.
    http.HandleFunc("/work", func(w http.ResponseWriter, r *http.Request) {
        requestHandler(w, r)
    })
    log.Fatal(http.ListenAndServe(":" + *port, nil))
}

我有以下问题:

  1. 当并发线程数超过 1000 时,http 连接会被重置。这是可接受/预期的行为吗?

  2. 如果我写 go requestHandler(w,r) 而不是 requestHandler(w,r) 我得到 http:多个响应。WriteHeader 调用

【问题讨论】:

  • 检查 ulimit、maxfiles 和 somaxconn。可能是系统资源不足。
  • 我同意 Eugene:1024 是典型的基于 Linux 的商品操作系统上打开文件(在 UNIX 上还包括套接字)数量的典型限制。
  • 只是一个建议.. 您的用例实际上可以毫无问题地使用通道而不是等待组。这也将防止您由于竞争条件而获得的任何损坏/格式错误的字符串作为输出。此外,处理程序已经在 goroutine 中处理,以回答您的问题,因此将其放在另一个中无济于事
  • 如果您使用的是 Linux,方法是提高 /etc/security/limits.confnofile 参数的所谓“硬”限制 — 为用户和/或组设置它的用户,其中包括用于运行您的服务器的用户。然后,在启动服务器之前,在 shell 中发出 ulimit -n hard — 将当前的“软”限制(将有一个合理的默认值,通常为 1024,如前所述)提升到“硬”限制。之后生成的服务器将继承此设置并使用它。致电ulimit -nulimit -a 以查看当前设置。
  • @biosckon 测试了多达 10K 个并发请求,它运行良好。令人惊讶的是,使用的内存仅为~120 MB。但是 CPU 一直保持在 50-60 %。

标签: go


【解决方案1】:

http 处理程序应该同步运行,因为处理程序函数的返回表示请求结束。在处理程序返回后访问http.Requesthttp.ResponseWriter 是无效的,因此没有理由在goroutine 中调度处理程序。

正如 cmets 所指出的,您不能打开比进程 ulimit 允许的更多的文件描述符。除了适当增加 ulimit 之外,您还应该限制一次可以分派的并发请求的数量。

如果您要与同一主机建立多个连接,您还应该相应地调整您的http.Transport。每个主机的默认空闲连接只有 2 个,因此如果您需要与该主机的 2 个以上并发连接,则不会重用新连接。见Go http.Get, concurrency, and "Connection reset by peer"

如果您连接到许多不同的主机,设置Transport.IdleConnTimeout 是摆脱未使用连接的好主意。

和往常一样,在长时间运行的服务上,您需要确保为所有内容设置超时,以便缓慢或断开的连接不会占用不必要的资源。

【讨论】:

    【解决方案2】:

    Q2:多个 response.WriteHeader 调用: 如果你不设置你的标题去为你做。当您启动一个 goroutine 时,服务器会看到尚未设置标头,然后自动设置,但之后您的 goroutine 会再次执行此操作。

    Q1:当并发线程数超过 1000 时,http 连接会被重置: Go 例程不是系统线程,这意味着您可以运行比系统通常可以运行的线程更多的例程。在最坏的情况下,您的请求同时运行而不是并行运行。我没有在您的代码中看到任何错误,这让我觉得您发出请求的服务器会限制您并丢弃您的请求,因为您可能超过服务器允许一个 ip 的最大连接数。

    您还可以在请求中修改http.Transport 参数(请参阅docs),看看这是否有助于您处理内存消耗和并发连接的情况。

    tr := &http.Transport{
        MaxIdleConns:       10,
        IdleConnTimeout:    30 * time.Second,
        DisableCompression: true,
    }
    client := &http.Client{Transport: tr}
    resp, err := client.Get("https://example.com")
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2010-10-01
      • 1970-01-01
      • 2019-05-16
      • 1970-01-01
      • 2019-09-09
      • 2019-05-09
      • 1970-01-01
      • 2020-04-07
      相关资源
      最近更新 更多