最近有一個壓測的任務,首先使用gin寫了一個http server,將請求數據寫入到mysql,寫入qps需要達到20K,當然,為了保險起見,使用了自己寫的一個http client進行了壓力測試,qps可以達到23K-25K的樣子,然后就把這個http server部署到測試環境進行測試。
對於golang的http client,設置的MaxConnsPerHost為3000,使用九個client進行測試,結果發現http server的端口占用很高,然后查看了client端的端口占用,發現處於ESTABLISHED狀態的都遠遠超過3000,之后就使用dlv進行測試,然后從golang官網下載了go1.15.6,在調試過程中,發現調試展示的代碼為dlv所在的golang的版本,而運行的程序基於我本地的go1.12.7,所以顯示的結果有很多出乎意料的地方,例如Transport.connsPerHost map中的value為負數的情況,這個,也讓我覺得go1.12.7可能在MaxConnsPerHost設置效果方面存在問題。但是,無論如何,都需要先把程序編譯的golang版本和dlv調試用的版本對齊,go1.15已經發布了大約5個月, 改動也沒有很多,穩定性比較可靠,就把編譯用的golang版本改為go1.15.6。重新編譯運行之后,client端處於ESTABLISHED的數量一直都低於3000,這個問題解決了。
雖然處於ESTABLISEHD的端口號占用一直不超過3000,但是netstat -antp中顯示與client相關的端口號卻經常遠遠超過3000,其中很多是如下顯示:
對tcp四次揮手協議比較了解的,應該知道,這很有可能是client發起了關閉tcp連接的請求,但是server端沒有關閉tcp連接導致的。查看http server,發現了有大量的CLOSE_WAIT,與設想相同。但是查看MaxConnsPerHost的描述:
// MaxConnsPerHost optionally limits the total number of // connections per host, including connections in the dialing, // active, and idle states. On limit violation, dials will block. // // Zero means no limit. MaxConnsPerHost int // Go 1.11
MaxConnsPerHost會限制處於dialing,active,idle狀態的tcp連接數。如果連接數已經達到限制,就會阻塞。也就是說,MaxConnsPerHost並不會導致很多的tcp連接關閉。然后,猜測,這個會不會與Transport.DialContext.KeepAlive的時間有關,測試的時候,我們調大了KeepAlive的時間,也猜測可能與Transport.IdleConnTimeout有關,於是也調大了這個參數。發現還是會有很多的FIN_WAIT2狀態出現。於是,繼續使用dlv進行調試,因為FIN_WAIT2的出現必定和tcp關閉有關,於是我們在關閉連接相關的地方加上斷點,發現了如下的調用堆棧結果:
按照上述調用堆棧,可以知道,在一個請求被取消的時候,對應的tcp連接會被關閉。這里請求被取消是因為超時,超時說明http server太繁忙,而http server端出現很多的CLOSE_WAIT,也說明服務端太繁忙,這兩點比較契合。但,很重要的一點是,就算設置了MaxConnsPerHost,如果出現很多的請求超時,一樣會導致客戶端的端口號占用增加,以及服務端需要打開的文件數變多。