Go中的有限并发连接

Go中的有限并发连接,go,Go,我在Go中有以下基本http服务器。对于每个传入的请求,它都会发出5个传出http请求。每一次大约需要3-5秒。我无法在8G内存的四核机器上实现超过200个请求/秒 package main import ( "flag" "fmt" "net/http" _"net/url" //"io/ioutil" "time" "log" "sync" //"os" "io/ioutil" ) // Job holds

我在Go中有以下基本http服务器。对于每个传入的请求,它都会发出5个传出http请求。每一次大约需要3-5秒。我无法在8G内存的四核机器上实现超过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))
}
我有以下问题:

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

如果我写go-requestHandlerw,r而不是requestHandlerw,r,我得到 http:multiple response.WriteHeader调用

问题2:多个response.WriteHeader调用: 如果你不设置你的标题,去为你做。当您启动go例程时,服务器会看到还没有设置头,然后会自动设置,但之后您的go例程会再次设置头

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

您还可以修改请求中的http.Transport参数,看看这是否有助于解决内存消耗和并发连接的问题

tr := &http.Transport{
    MaxIdleConns:       10,
    IdleConnTimeout:    30 * time.Second,
    DisableCompression: true,
}
client := &http.Client{Transport: tr}
resp, err := client.Get("https://example.com")
问题2:多个response.WriteHeader调用: 如果你不设置你的标题,去为你做。当您启动go例程时,服务器会看到还没有设置头,然后会自动设置,但之后您的go例程会再次设置头

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

您还可以修改请求中的http.Transport参数,看看这是否有助于解决内存消耗和并发连接的问题

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

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

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

如果要与同一主机建立多个连接,还应该相应地调整http.Transport。每个主机的默认空闲连接只有2个,因此,如果需要到该主机的2个以上并发连接,则不会重用新连接。看

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


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

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

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

如果要与同一主机建立多个连接,还应该相应地调整http.Transport。每个主机的默认空闲连接只有2个,因此,如果需要到该主机的2个以上并发连接,则不会重用新连接。看

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


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

检查ulimit、maxfiles和somaxconn。可能系统资源不足。我同意Eugene的观点:1024是打开文件数量的典型限制,在UNIX上,它还包括基于Linux的典型商品操作系统上的套接字。这只是一个建议。。您的用例实际上可以使用没有问题的通道,而不是等待组。这也可以防止任何伤害
由于竞态条件,您可能会得到一个异常/格式错误的字符串作为输出。此外,为了回答您的问题,处理程序已经在goroutine中处理,因此如果您使用Linux,将其放在另一个goroutine中也不会有帮助,方法是提高/etc/security/limits.conf中nofile参数的所谓硬限制,即为用户和/或一组用户(包括用于运行服务器的用户)设置该参数。然后,在启动服务器之前,在shell中发出ulimit-n hard命令,以使当前的软限制具有合理的默认值,通常为1024,正如前面讨论的硬限制一样。aftwerwards生成的服务器将继承并使用此设置。调用ulimit-n或ulimit-a查看当前设置。@biosckon测试了多达10K个并发请求,效果良好。令人惊讶的是,使用的内存只有约120MB。然而,CPU始终保持在50-60%之间。请检查ulimit、maxfiles和somaxconn。可能系统资源不足。我同意Eugene的观点:1024是打开文件数量的典型限制,在UNIX上,它还包括基于Linux的典型商品操作系统上的套接字。这只是一个建议。。您的用例实际上可以使用没有问题的通道,而不是等待组。这还可以防止由于竞争条件而导致输出的字符串出现任何损坏/格式错误。此外,为了回答您的问题,处理程序已经在goroutine中处理,因此如果您使用Linux,将其放在另一个goroutine中也不会有帮助,方法是提高/etc/security/limits.conf中nofile参数的所谓硬限制,即为用户和/或一组用户(包括用于运行服务器的用户)设置该参数。然后,在启动服务器之前,在shell中发出ulimit-n hard命令,以使当前的软限制具有合理的默认值,通常为1024,正如前面讨论的硬限制一样。aftwerwards生成的服务器将继承并使用此设置。调用ulimit-n或ulimit-a查看当前设置。@biosckon测试了多达10K个并发请求,效果良好。令人惊讶的是,使用的内存只有约120MB。然而,CPU始终保持在50-60%。