首先,鏈接池失效,問題產生背景是高頻agent,agent 會發起大量的http 請求,可是,本想net/http 是支持長鏈接的,可是,幾種狀況,都產生了大量的time_wait,這裏予以總結。html
該文章後續仍在不斷的更新修改中, 請移步到原文地址http://dmwan.ccgolang
第一種狀況是誤用transport ,爲了設置代理,爲每一個請求,都new 了一個transport 。web
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, 以下:socket
var DefaultTransport RoundTripper = &Transport{ ... MaxIdleConnsPerHost: 1000, IdleConnTimeout: 90 * time.Second, ... }
第三種狀況是resp.body 忘了讀取,直接致使新請求會直接新建鏈接。其實能夠理解,沒read body 的socket, 若是直接複用,會產生什麼樣後果?全部使用這個套接字的鏈接都會錯亂。 示例以下,線程
package main import ( "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 調用都將無條件成功