我在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))
}
我有以下问题:
当并发线程数超过1000时,http连接会重置。这是可接受/预期的行为吗?
如果我写go requestHandler(w,r)而不是requestHandler(w,r)我得到 http:多个response.WriteHeader调用
答案 0 :(得分:1)
希望http处理程序同步运行,因为处理程序函数的返回表示请求结束。在处理程序返回后访问http.Request
和http.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")