我使用 Go 的内置 http.Client
和 net
进行了快速基准测试。使用 DialContext
时与不使用它时相比,会导致一些明显的性能问题。
我基本上是在尝试模仿我们公司的一个用例,其中当用于相同的事情时,http.Client
设置的性能远低于默认配置。我注意到注释 DialContext
部分可以让它运行得更快。
基准测试只是打开一个线程池(示例中为 8 个)来创建到简单 URL 的连接,使用大小与线程数 (8) 相同的缓冲 channel 。
这是带有 DialContext
的代码(2.266333805s):
func main() {
var httpClient *http.Client
httpClient = &http.Client{
Transport: &http.Transport{
DialContext: (&net.Dialer{
Timeout: 3 * time.Second,
KeepAlive: 30 * time.Second,
DualStack: true,
}).DialContext,
},
}
url := "https://stackoverflow.com/"
wg := sync.WaitGroup{}
threads := 8
wg.Add(threads)
c := make(chan struct{}, threads)
start := time.Now()
for i := 0; i < threads; i++ {
go func() {
for range c {
req, _ := http.NewRequest(http.MethodGet, url, nil)
resp, err := httpClient.Do(req)
if err == nil {
resp.Body.Close()
}
}
wg.Done()
}()
}
for i := 0; i < 200; i++ {
c <- struct{}{}
}
close(c)
wg.Wait()
fmt.Println(time.Since(start))
}
输出时间为2.266333805s
这是没有 DialContext
的代码(731.154103ms):
func main() {
var httpClient *http.Client
httpClient = &http.Client{
Transport: &http.Transport{
},
}
url := "https://stackoverflow.com/"
wg := sync.WaitGroup{}
threads := 8
wg.Add(threads)
c := make(chan struct{}, threads)
start := time.Now()
for i := 0; i < threads; i++ {
go func() {
for range c {
req, _ := http.NewRequest(http.MethodGet, url, nil)
resp, err := httpClient.Do(req)
if err == nil {
resp.Body.Close()
}
}
wg.Done()
}()
}
for i := 0; i < 200; i++ {
c <- struct{}{}
}
close(c)
wg.Wait()
fmt.Println(time.Since(start))
}
输出时间为731.154103ms
多次运行程序后结果之间的差异是一致的。
有人知道为什么会发生这种情况吗?
谢谢!
编辑:所以我尝试了net/http/httptrace
,并确保响应的正文已完全读取并关闭:
go func() {
for range c {
req, _ := http.NewRequest(http.MethodGet, url, nil)
req = req.WithContext(httptrace.WithClientTrace(req.Context(), &httptrace.ClientTrace{
GotConn: t.gotConn,
}))
resp, _ := httpClient.Do(req)
ioutil.ReadAll(resp.Body)
resp.Body.Close()
}
wg.Done()
}()
使用 DialContext
与不使用它时的发现很有趣。
不使用 DialContext:
time taken to run 200 requests: 5.639808793s
new connections: 1
reused connections: 199
使用 DialContext:
time taken to run 200 requests: 5.682882723s
new connections: 8
reused connections: 192
速度更快...但为什么一个打开 8 个新连接,而另一个只打开 1 个?
最佳答案
获得如此大差异的唯一方法是一种传输正在重用连接,而另一种则没有。为了确保您可以重用连接,您必须始终读取响应正文。在某些情况下,连接可能会在不显式读取正文的情况下被重用,但这永远无法保证,并且取决于许多因素,例如远程服务器关闭连接、响应是否由 Transport
完全缓冲>,以及请求是否有上下文。
net/http/httptrace
包可以让您深入了解许多请求的内部结构,包括连接是否被重用。请参阅https://blog.golang.org/http-tracing例如。
设置DisableKeepAlive
将始终阻止连接被重用,从而使两者同样变慢。始终像这样阅读响应将使两者同样快速:
req, _ := http.NewRequest(http.MethodGet, url, nil)
resp, err := httpClient.Do(req)
if err != nil {
// handle error
continue
}
io.Copy(ioutil.Discard, resp.Body)
resp.Body.Close()
如果您想在断开连接之前限制可以读取的数据量,您可以简单地将正文包裹在 io.LimitedReader
中。
关于http - 在 Go 中使用 DialContext 的性能问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62071857/