golang net/http httpclient連接池參數的坑位


http是我們最常見的客戶端/服務端傳輸協議,在golang中,默認的net/http包有一些坑位,需要調整以獲得更加性能。

在golang程序中,我也遇到因為不合理使用 http client導致的程序崩潰問題。


坑:1:默認的HttpClient

默認的HttpClient不包含請求超時時間,如果你使用http.Get(url)或者&Client{}, 這將會使用http.DefaultClient,這個結構體內no timeout

假如發出請求的服務端API有問題:沒有及時響應httpclient請求但是保持了連接, 在高並發情況下,打開的連接數會持續增長,最終導致客戶端服務器資源到達瓶頸。

解決方案:不要使用默認的HTTPClient, 總是為HttpClient指定Timeout

	client := &http.Client{
		Timeout: 10 * time.Second,
	}

HttpClient Timeout包括連接、重定向(如果有)、從Response Body讀取的時間,內置定時器會在Get,Head、Post、Do 方法之后繼續運行,直到讀取完Response.Body。

這里有個有關HttpClient Timeout的排障問題,你可參考。


.NET HttpClient Timeout: The default value is 100,000 milliseconds (100 seconds).

坑2:默認的Http Transport池化機制

目前常見的HttpClient(.NET Core,golang) 都會有連接池的概念, 客戶端會盡量復用池中已經建立的tcp連接 (sqlclient連接池也是復用的tcp連接)。

之前我有個誤區,認為連接池是預置連接(因為有個開源作者實現的redis庫是預置連接),其實不是的,連接池強調的是復用已創建的連接,連接池的創建是由首次請求來驅動的。

golang的Transport用於連接池化。

DefaultTransport is the default implementation of Transport and is
used by DefaultClient. It establishes network connections as needed
and caches them for reuse by subsequent calls. It uses HTTP proxies
as directed by the $HTTP_PROXY and $NO_PROXY (or $http_proxy and
$no_proxy) environment variables.

MaxIdleConns  int
MaxIdleConnsPerHost int    //  如果是0, 使用DefaultMaxIdleConnsPerHost=2

MaxConnsPerHost int         // 0意味無限制
IdleConnTimeout time.Duration  // 0 意味無限制

默認值:
MaxIdleConns =100, MaxIdleConnsPerHost=2
沒有定義MaxConns字段,golang其實創建conn是無限制的,MaxConnsPerHost=0

var DefaultTransport RoundTripper = &Transport{
	Proxy: ProxyFromEnvironment,
	DialContext: (&net.Dialer{
		Timeout:   30 * time.Second,
		KeepAlive: 30 * time.Second,
	}).DialContext,
	ForceAttemptHTTP2:     true,
	MaxIdleConns:          100,
	IdleConnTimeout:       90 * time.Second,     // 空閑(keep-alive)連接在關閉之前保持空閑的時長
	TLSHandshakeTimeout:   10 * time.Second,
	ExpectContinueTimeout: 1 * time.Second,
}

http連接池化 ,是公共連接池, 能創建的連接是無限制的(雖然沒字段,但是代碼分析是無限制的), 每個Host能創建的連接MaxConnsPerHost=0 , 也是無限制的;

有坑位的是DefaultMaxIdleConnsPerHost=2:字面含義是連接池中每個主機的空閑連接數是2個,其實也就是每個主機能復用的連接數就是2個。


發現問題了嗎? 能無限制創建,但是能復用的只有2個。

這意味着:如果你的請求是高並發持續請求, 一開始請求能無限制創建, 但是由於不能復用tcp連接(2個,聊勝於無), 造成客戶端主動關閉tcp連接,time_wait狀態(2min)會占用大量端口, 之后就不能發起tcp連接了。

有些同學不知道威力,我畫個圖理解一下。

在並發持續請求host的情況下, 因為不能復用tcp連接,就會頻繁銷毀連接, 這樣會累積很多time_wait狀態的不可用連接, 沒過多久就創建不了了。

這個問題的本質是 主動關閉的TCP連接,服務器會產生大量的time-wait狀態連接(2min),占用了可用的網絡文件描述符。

解決方案:不要使用默認Transport,增加MaxIdleConnsPerHost


--- 本人回顧了.NET HttpClient,不用刻意關閉這個值。

實際上,.NET也存在這個MaxIdleConnectionPerServer配置,但是.NET Core這個PerServer被設置為int.maxvalue,所以我們無需關注,.NET真香。


我的收獲

通過本文,我們談到了golang HttpClient的2個坑位、由坑位導致的現象和排障思路,各位看官,有則改之無則加勉。

坑1是不限制請求超時,在高並發的客戶端,很大可能會形成雪崩。

其中坑2的池化機制(tcp連接復用)很有意思,結合tcp原理,我們可以認定MaxIdleConnsPerHost=2 基本就告別了連接復用


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM