Go中有限的并发连接

时间:2017-11-20 05:43:02

标签: go

我在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:多个response.WriteHeader调用

2 个答案:

答案 0 :(得分:1)

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

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

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

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

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

答案 1 :(得分:0)

Q2:多个response.WriteHeader调用: 如果您没有设置标题,请随身携带。当你启动一个go例程时,服务器发现还没有设置头文件,然后自动设置,但之后你的例程会再次执行。

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")