在改动旧代码的时候把, 一个使用全局 http.Client
的代码弄成了每一个请求会新 New
一个 http.Client
导致下游的 nginx 的连接数暴涨.
处理多个请求的时候, 是否需要为每个请求 New 一个 Client
在 StackOverflow 发现的相关答案
How to release http.Client in Go?
给的答案是建议复用 Client
The Client’s Transport typically has internal state (cached TCP connections), so Clients should be reused instead of created as needed. Clients are safe for concurrent use by multiple goroutines.
http.Client 的结构体
1
2
3
4
5
6
7
8
9
10
| type Client struct {
Transport RoundTripper
CheckRedirect func(req *Request, via []*Request) error
Jar CookieJar
Timeout time.Duration
}
|
在 RoundTripper
中实现了连接复用的逻辑
1
2
3
| type RoundTripper interface {
RoundTrip(*Request) (*Response, error)
}
|
中定义了 RoundTrip
方法, 提供客户端请求的时候调用.
调用地址
查看一下 Golang Transport 的基本实现
1
2
3
4
5
6
7
8
9
10
11
12
13
| type Transport struct {
idleMu sync.Mutex
closeIdle bool // user has requested to close all idle conns
idleConn map[connectMethodKey][]*persistConn // most recently used at end
idleConnWait map[connectMethodKey]wantConnQueue // waiting getConns
idleLRU connLRU
connsPerHostMu sync.Mutex
connsPerHost map[connectMethodKey]int
connsPerHostWait map[connectMethodKey]wantConnQueue // waiting getConns
// 还有其他字段略
}
|
结构体中间有很多连接存储相关的字段.
在 http 请求调用 Transport 中间有一个关键方法 getConn 获取一个连接
方法声明一个想要的连接地址, wantConn 推入到 queueForDial
QueueForDial 方法会判断时候connsPerHost
中间是否有当前的请求的缓存连接
- 如果有直接拿来重复使用
- 如果没有, 就需要重新进行拨号
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| w.beforeDial()
if t.MaxConnsPerHost <= 0 {
go t.dialConnFor(w)
return
}
t.connsPerHostMu.Lock()
defer t.connsPerHostMu.Unlock()
if n := t.connsPerHost[w.key]; n < t.MaxConnsPerHost {
if t.connsPerHost == nil {
t.connsPerHost = make(map[connectMethodKey]int)
}
t.connsPerHost[w.key] = n + 1
go t.dialConnFor(w)
return
}
if t.connsPerHostWait == nil {
t.connsPerHostWait = make(map[connectMethodKey]wantConnQueue)
}
q := t.connsPerHostWait[w.key]
q.cleanFront()
q.pushBack(w)
t.connsPerHostWait[w.key] = q
|
重复使用 http.Client 可以达到 TCP 连接复用的效果