误用一:忘记读取响应的body
由于忘记读取响应的body导致创建大量处于TIME_WAIT状态的连接(同时产生大量处于transport.go的readLoop和writeLoop的协程)
在linux下运行下面的代码:
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() {
startWebserver()
startLoadTest()
}
在程序运行时另外开一个终端运行下面的命令:
netstat -n | grep -i 8080 | grep -i time_wait | wc -l
你会看到TIME_WAIT数量在持续增长
root@myhost:/# netstat -n | grep -i 8080 | grep -i time_wait | wc -l
166
root@myhost:/# netstat -n | grep -i 8080 | grep -i time_wait | wc -l
231
root@myhost:/# netstat -n | grep -i 8080 | grep -i time_wait | wc -l
293
root@myhost:/# netstat -n | grep -i 8080 | grep -i time_wait | wc -l
349
解决办法: 读取响应的body
更改startLoadTest()函数,添加下面的代码:
func startLoadTest() {
for {
...
if err != nil {
panic(fmt.Sprintf("Got error: %v", err))
}
io.Copy(ioutil.Discard, resp.Body) // <-- add this line
resp.Body.Close()
...
}
}
现在再次运行netstat -n | grep -i 8080 | grep -i time_wait | wc -l,你会发现TIME_WAIT状态的连接数为0
误用二:空闲连接最大数量设置太小,实际连接数量超过连接池的限制
连接的数量超过连接池的限制导致出现大量TIME_WAIT状态的连接
这种情况时由于持续超过连接池导致许多短连接被打开。
请看下面的代码:
package main
import (
"fmt"
"html"
"io"
"io/ioutil"
"log"
"net/http"
"time"
)
func startWebserver() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
time.Sleep(time.Millisecond * 50)
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))
}
io.Copy(ioutil.Discard, resp.Body)
resp.Body.Close()
log.Printf("Finished GET request #%v", count)
count += 1
}
}
func main() {
// start a webserver in a goroutine
startWebserver()
for i := 0; i < 100; i++ {
go startLoadTest()
}
time.Sleep(time.Second * 2400)
}
在另外一个终端运行netstat,尽管响应已经被读取,TIME_WAIT的连接数还是持续增加
root@ myhost:/# netstat -n | grep -i 8080 | grep -i time_wait | wc -l
166
root@ myhost:/# netstat -n | grep -i 8080 | grep -i time_wait | wc -l
231
root@ myhost:/# netstat -n | grep -i 8080 | grep -i time_wait | wc -l
293
root@ myhost:/# netstat -n | grep -i 8080 | grep -i time_wait | wc -l
349
什么是TIME_WAIT状态呢?
就是当我们创建大量短连接时,linux内核的网络栈保持连接处于TIME_WAIT状态,以避免某些问题。
例如:避免来自一个关闭的连接延迟的包被后来的连接所接收。并发连接被用地址,端口,序列号等其他机制所隔离开。
为什么这么多的TIME_WAIT端口?
默认情况下,Golang的http client会做连接池。他会在完成一个连接请求后把连接加到一个空闲的连接池中。如果你想在这个连接空闲超时前发起另外一个http请求,它会复用现有的连接。
这会把总socket连接数保持的低一些,直到连接池满。如果连接池满了,它会创建一个新的连接来发起http请求。
那这个连接池有多大呢?看看transport.go:
var DefaultTransport RoundTr

最低0.47元/天 解锁文章
615

被折叠的 条评论
为什么被折叠?



