Golang net/http 效能優化
Golang net/http 效能優化
Go 語言內建net\http
包十分優秀,我們通過這個包可以很方便的去實現 HTTP 的客戶端和服務端。
但是在高併發的情況下,如果我們使用預設的配置,會引發一些問題,嚴重的話可能會使伺服器崩潰。這裡講述以下兩種預設配置情況下帶來的一些問題。
- #### 使用 DefaultClient
_, err := http.Get("http://www.baidu.com")
if err != nil {
log.Fatal(err)
}
var DefaultClient = &Client{}
如果我們直接使用預設的 http,那麼它是沒有超時時間的。這樣就會帶來如下問題:
假設我們向服務端發起請求,但是服務端因為某些情況沒有及時返回或者說連線中斷了,那麼客戶端就會很長時間得不到服務端的 response。所以這個時候客戶端為這一個 tcp 連線申請的資源就得不到釋放,造成資源的浪費。如果在高併發的情況下,客戶端可能會因為資源的限制使得伺服器崩潰,比如達到最大檔案描述符或者達到埠號限制等等。
解決辦法是自己設定超時時間:
client := http.Client{
Timeout: 10 * time.Second,
}
- #### 使用預設的 DefaultTransport
如果我們在 http client 中沒有設定 transport 屬性,那麼它就會使用預設的 transport:
var DefaultTransport RoundTripper = &Transport{
Proxy: ProxyFromEnvironment,
DialContext: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
DualStack: true,
}).DialContext,
ForceAttemptHTTP2: true,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}
從這個的配置中我們可以看到,http 使用了預設的連線池,關鍵的兩個屬性:
MaxIdleConns:最大空閒連線數量,預設為 100
IdleConnTimeout:空閒連線超時時間,預設為 90s
當一個 request 請求完成後,這個連線會保留,直到達到 IdleConnTimeout 設定的超時時間。如果沒有達到,那麼下一個請求就會複用這個連線。
這樣的空閒連線最大數量是 100 個,超過 100 的還是會建立新的連線。
建立連線池的好處是能夠儘可能減少伺服器的資源。這個配置看上去很好啊,那為什麼還是說會有問題呢?
檢視原始碼,它還有另外一個預設配置:
// DefaultMaxIdleConnsPerHost is the default value of Transport's
// MaxIdleConnsPerHost.
const DefaultMaxIdleConnsPerHost = 2
DefaultMaxIdleConnsPerHost 為每個 host 的設定的空閒連線數量為 2。
DefaultMaxIdleConnsPerHost 設定的太小就會導致一個問題,在大量請求的情況下去訪問特定的 host 的時候,長連線會退化成短連結。看如下原始碼:
idles := t.idleConn[key]
if len(idles) >= t.maxIdleConnsPerHost() {
return errTooManyIdleHost
}
for _, exist := range idles {
if exist == pconn {
log.Fatalf("dup idle pconn %p in freelist", pconn)
}
}
t.idleConn[key] = append(idles, pconn)
t.idleLRU.add(pconn)
從原始碼中我們可以看出,如果當併發量大的情況下,連線池會建立較多的 TCP 連線,並且在請求完成以後連線池嘗試通過 tryPutIdleConn 歸還空閒連線,對於超出 maxIdleConnsPerHost 數量的空閒長連線都不能再放回連線池了,這些連線會進入 TIME_WAIT 狀態,這些 TIME_WAIT 的連線在達到 2MSL 時間後就會自動關閉。
在這種情況下,我們在伺服器上就會看到大量的 TIME_WAIT 狀態的 tcp 連線。在極限的情況下,伺服器也可能會崩潰。
解決辦法是自己設定 DefaultMaxIdleConnsPerHost
t := http.DefaultTransport.(*http.Transport).Clone()
t.MaxIdleConns = 100
t.MaxConnsPerHost = 100
t.MaxIdleConnsPerHost = 100
client := http.Client{
Timeout: 10 * time.Second,
Transport: t,
}
程式碼演示
這裡我用程式碼演示使用 DefaultTransport 和不使用兩者的 tcp 連線狀態的區別,從而來驗證這個邏輯。
客戶端同時向服務端發起 100 個請求。
- ###### server
這裡我用 gin web 框架快速起了一個服務端
package main
import (
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
})
})
r.Run()
}
- ###### client1 使用預設的 DefaultTransport
package main
import (
"fmt"
"io/ioutil"
"log"
"net/http"
)
func main() {
client := http.Client{
}
for i := 0; i < 100; i++ {
go func() {
resp, err := client.Get("http://127.0.0.1:8080/ping")
if err != nil {
log.Fatal(err)
}
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
fmt.Printf(string(b))
}()
}
select {}
}
- ###### client2 不使用預設的 DefaultTransport
package main
import (
"fmt"
"io/ioutil"
"log"
"net/http"
"time"
)
func main() {
t := http.DefaultTransport.(*http.Transport).Clone()
t.MaxIdleConns = 100
t.MaxIdleConnsPerHost = 100
client := http.Client{
Timeout: 10 * time.Second,
Transport: t,
}
for i := 0; i < 100; i++ {
go func() {
resp, err := client.Get("http://127.0.0.1:8080/ping")
if err != nil {
log.Fatal(err)
}
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
fmt.Printf(string(b))
}()
}
select {}
}
使用 DefaultTransport 的 tcp 連線情況:
如圖所示,在請求發出之後,我們可以看到只有兩個 tcp 連線是處於 ESTABLISHED 狀態,其他的都是處於 TIME_WAIT 狀態。且兩個處於 ESTABLISHED 狀態的 tcp 連線會在 90s 之後變成 TIME_WAIT 狀態。
90s 後:
使用 DefaultTransport 的 tcp 連線情況:
如圖所示,可以看出所有的 100 個 tcp 連線都是處於 ESTABLISHED 狀態,這些狀態在 90s 之後全部變成 TIME_WAIT 狀態。
90s 之後:
總結:
雖然我們平時開發中使用預設的配置也沒有遇到什麼問題,但是在高併發的條件下還是會帶來很多問題。
所以我們在高併發的情況下,儘量不要使用預設的配置,通過更改 HTTP 客戶端的一些預設設定,以達到高效能的目的。
- 加微信實戰群請加微信(註明:實戰群):gocnio
相關文章
- Golang效能分析與優化Golang優化
- golang 效能優化之 bitset 代替 hashsetGolang優化
- golang 效能優化之累加雜湊Golang優化
- 前端效能優化之HTTP快取策略前端優化HTTP快取
- 介紹 golang net/http 原始碼GolangHTTP原始碼
- HTTP前端效能優化(壓縮與快取)HTTP前端優化快取
- 轉載golang中net/http包用法GolangHTTP
- [譯]谷歌Web效能優化系列:HTTP 快取(中英)谷歌Web優化HTTP快取
- ASP.NET Core 效能優化最佳實踐ASP.NET優化
- 【前端效能優化】vue效能優化前端優化Vue
- .Net效能調優-ArrayPool
- .Net效能調優-MemoryPool
- Golang優化-優雅退出Golang優化
- HTTP2.0,HTTP1.1,HTTP1.0三者在通性效能上的優化方法HTTP優化
- 效能優化優化
- Golang pprof 效能調優實戰,效能提升 3 倍!Golang
- golang 效能調優分析工具 pprof(下)Golang
- golang 效能調優分析工具 pprof (上)Golang
- 【http】https加速優化HTTP優化
- 前端效能優化(JS/CSS優化,SEO優化)前端優化JSCSS
- Android效能優化——效能優化的難題總結Android優化
- [效能優化]DateFormatter深度優化探索優化ORM
- 前端效能優化 --- 圖片優化前端優化
- 效能優化|Tomcat 服務優化優化Tomcat
- Android 效能優化 ---- 啟動優化Android優化
- Android效能優化----卡頓優化Android優化
- Golang效能最佳化實踐Golang
- Javascript 效能優化JavaScript優化
- java效能優化Java優化
- react效能優化React優化
- Canvas效能優化Canvas優化
- UI效能優化UI優化
- mongodb效能優化MongoDB優化
- Android效能優化Android優化
- EF效能優化優化
- TableView效能優化View優化
- web效能優化Web優化
- mysql效能優化MySql優化