go http client
net/http 与 TIME_WAIT
golang http client 连接池
http.DefaultTransport.(http.Transport).MaxIdleConnsPerHost = 1000
http.DefaultTransport.(http.Transport).MaxIdleConns = 1000
var netTransport = &http.Transport{
Dial: (&net.Dialer{
Timeout: 10 * time.Second,
KeepAlive: 30 * time.Second,
}).Dial,
TLSHandshakeTimeout: 5 * time.Second,
ResponseHeaderTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}
var netClient = &http.Client{
Timeout: time.Second * 30,
Transport: netTransport,
}
// get
response, _ := netClient.Get("http://www.golangnote.com/")
defer response.Body.Close()
if response.StatusCode == 200 {
body, _ := ioutil.ReadAll(response.Body)
fmt.Println(string(body))
}
长连接与短连接
前面我们已经讲过net/http默认使用HTTP/1.1协议,也就是默认发送Connections: keep-alive的头,让服务端保持连接,就是所谓的长连接。
再看DefaultTransport的值:
// 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.
var DefaultTransport RoundTripper = &Transport{
Proxy: ProxyFromEnvironment, //代理使用
DialContext: (&net.Dialer{
Timeout: 30 * time.Second, //连接超时时间
KeepAlive: 30 * time.Second, //连接保持超时时间
DualStack: true, //
}).DialContext,
MaxIdleConns: 100, //client对与所有host最大空闲连接数总和
IdleConnTimeout: 90 * time.Second, //空闲连接在连接池中的超时时间
TLSHandshakeTimeout: 10 * time.Second, //TLS安全连接握手超时时间
ExpectContinueTimeout: 1 * time.Second, //发送完请求到接收到响应头的超时时间
}
当我们使用DefaultTransport时,就是默认使用的长连接。但是默认的连接池MaxIdleConns为100, MaxIdleConnsPerHost为2,当超出这个范围时,客户端会主动关闭到连接。
如果我们想设置为短连接,有几种方法:
设置 DisableKeepAlives = true: 这时就会发送Connections:close给server端,在server端响应后就会主动关闭连接。
设置 MaxIdleConnsPerHost < 0: 当MaxIdleConnsPerHost < 0时,连接池是无法放置空闲连接的,所以无法复用,连接直接会在client端被关闭。
Server端出现大量的TIME_WAIT
当我们在实际使用时,会发现Server端出现了大量的TIME_WAIT,要想深入了解其原因,我们首先先回顾一下TCP三次握手和四次分手的过程:
图中可以看出,TIME_WAIT只会出现在主动关闭连接的一方,也就是server端出现了大量的主动关闭行为。
默认我们是使用长连接的,只有在超时的情况下server端才会主动关闭连接。前面也讲到,如果超出连接池的部分就会在client端主动关闭连接,连接池的连接会复用,看着似乎没有什么问题。问题出在我们每次请求都会new一个新的client,这样每个client的连接池里的连接并没有得到复用,而且这时client也不会主动关闭这个连接,所以server端出现了大量的keep-alive但是没有请求的连接,就会主动发起关闭。
todo:补充tcpdump的分析结果
要解决这个问题以下几个方案:
client 实现复用,
也就是我们尽量复用client,来保证client连接池里面的连接得到复用,而减少出现超时关闭的情况。
设置MaxIdleConnsPerHost < 0
这样每次请求后都会由client发起主动关闭连接的请求,server端就不会出现大量的TIME_WAIT修改server内核参数:
当出现大量的TIME_WAIT时危害就是导致fd不够用,无法处理新的请求。我们可以通过设置/etc/sysctl.conf文件中的#表示开启重用。允许将TIME-WAIT sockets重新用于新的TCP连接,默认为0,表示关闭 net.ipv4.tcp_tw_reuse = 1 #表示开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭 net.ipv4.tcp_tw_recycle = 1
达到快速回收和重用的效果,不影响其对新连接的处理。
另外需要注意的是,虽然DisableKeepAlives = true也能满足连接池中不放空闲连接,但是这时候会发送Connections: close,这时server端还是会主动关闭连接,导致大量的TIME_WAIT出现,所以这种方法行不通。