這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
首先,串連池失效,問題產生背景是高頻agent,agent 會發起大量的http 請求,但是,本想net/http 是支援長串連的,但是,幾種情況,都產生了大量的time_wait,這裡予以總結。
第一種情況是誤用transport ,為了設定代理,為每個請求,都new 了一個transport 。
client := &http.Client{ CheckRedirect: redirectPolicyFunc, Timeout: time.Duration(10)*time.Second,//設定逾時}client.Transport = &http.Transport{ Proxy: http.ProxyURL(proxyUrl),} //設定代理ip
失效的原因,是client 是安全執行緒的,golang串連池的維度是transport, 在transport 裡面維護了兩個map,暫存串連。
第二種情況是沒設定 MaxIdleConnsPerHost, 和串連的timeout, 一旦高頻的串連超過MaxIdleConnsPerHost 的數目,同時超過逾時,串連就會釋放。正確的設定是執行個體化transport 的時候,評估好 connsPerHost, 如下:
var DefaultTransport RoundTripper = &Transport{ ... MaxIdleConnsPerHost: 1000, IdleConnTimeout: 90 * time.Second, ... }
第三種情況是resp.body 忘了讀取,直接導致新請求會直接建立串連。其實可以理解,沒read body 的socket, 如果直接複用,會產生什麼樣後果?所有使用這個通訊端的串連都會錯亂。 樣本如下,
package mainimport ( "fmt" "html" "log" "net" "net/http" "time")func startWebserver() { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path)) }) go http.ListenAndServe(":8080", nil)}func startLoadTest() { count := 0 for { resp, err := http.Get("http://localhost:8080/") if err != nil { panic(fmt.Sprintf("Got error: %v", err)) } resp.Body.Close() log.Printf("Finished GET request #%v", count) count += 1 }}func main() { // start a webserver in a goroutine startWebserver() startLoadTest()}
這裡可以使用ss -s 查看串連數,如果不關心返回body ,可以直接丟棄
io.Copy(ioutil.Discard, resp.Body) //Discard 是一個 io.Writer,對它進行的任何 Write 調用都將無條件成功