Client 是 http 包内部发起请求的组件,使用它,我们才可以去控制请求的超时、重定向和其他的设置。以下是 Client 的定义:
type Client struct {
Transport RoundTripper
CheckRedirect func(req *Request, via []*Request) error
Jar CookieJar
Timeout time.Duration
}
要管理 HTTP 客户端的头域、重定向策略和其他设置, 需要创建一个 Client;要管理代理、TLS 配置、keep-alive、压缩和其他设置,需要创建一个 Transport。Client 和 Transport 类型都可以安全的被多个 go 程同时使用。出于效率考虑,应该一次建立、尽量重用。
1. 默认的 http.Client
默认的 http.Client 不包含请求超时时间,如果你使用 http.Get(url) 或者 &Client{} , 这将会使用 http.DefaultClient,这个结构体内 no timeout 。
假如发出请求的服务端 API 有问题:没有及时响应 httpclient 请求但是保持了连接, 在高并发情况下,打开的连接数会持续增长,最终导致客户端服务器资源到达瓶颈。
解决方案:不要使用默认的 http.Client , 总是为 http.Client 指定 Timeout 。
client := &http.Client{
Timeout: 10 * time.Second,
}
http.Client Timeout 包括连接、重定向(如果有)、从 Response Body 读取的时间,内置定时器会在 Get,Head、Post、Do 方法之后继续运行,直到读取完 Response.Body。
2. 默认的 http.Transport DefaultMaxIdleConnsPerHost 值
http.Transport 用于连接池化,客户端会尽量复用池中已经建立的 tcp 连接。连接池的创建是由首次请求来驱动的。
默认 DefaultTransport:
var DefaultTransport RoundTripper = &Transport{
Proxy: ProxyFromEnvironment,
DialContext: defaultTransportDialContext(&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}),
ForceAttemptHTTP2: true,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}
其中已赋值的字段:
MaxIdleConns: 100, // 最大空闲连接数。默认 100。
IdleConnTimeout: 90 * time.Second, // 空闲 keep-alive 连接在关闭之前保持空闲的时长。
及未显示赋值的其他字段取默认值:
DisableKeepAlives bool // 默认 false 开启 keep-alive 。
MaxIdleConnsPerHost int // 限制每个主机保留的最大空闲(保持活跃)连接数。默认 0 则使用 DefaultMaxIdleConnsPerHost = 2。
MaxConnsPerHost int // 限制每个主机的连接总数,包括处于拨号、活动和空闲状态的连接。默认 0 没有限制。
http.Client 连接池化,能创建的连接是无限制的,每个 Host 能创建的连接 MaxConnsPerHost = 0 ,也是无限制的;有问题的是 DefaultMaxIdleConnsPerHost = 2,即连接池中每个主机的空闲连接数是2个,其实也就是每个主机能复用的连接数就是2个。
发现问题了吗?能无限制创建,但是能复用的只有2个。
这意味着:如果你的请求是高并发持续请求,一开始请求能无限制创建,但是由于不能复用 tcp 连接(2个,聊胜于无),造成客户端主动关闭 tcp 连接,time_wait 状态(2min)会占用大量端口,之后就不能发起 tcp 连接了。
解决方案:不要使用默认 Transport ,增加 MaxIdleConnsPerHost 。
t := http.DefaultTransport.(*http.Transport).Clone()
t.MaxIdleConnsPerHost = 100
httpClient := &http.Client{
Timeout: 10 * time.Second,
Transport: t,
}
References
www.loginradius.com/blog/engine…
xujiahua.github.io/posts/20200…
tonybai.com/2021/04/02/…
tonybai.com/2021/01/08/…