作者:LOKYIP2012_862 | 来源:互联网 | 2024-10-26 12:14
在Golang应用中,频繁出现的TIME_WAIT和ESTABLISHED状态可能会导致性能瓶颈。本文探讨了这些状态产生的原因,并提出了优化与解决策略。通过调整内核参数、优化连接管理和使用连接池技术,可以有效减少TIME_WAIT的数量,提高应用的并发处理能力。同时,对于ESTABLISHED状态,可以通过合理的超时设置和错误处理机制,确保连接的高效利用和快速释放。
一、产生大量ESTABLISHED
resp, err := getHttpClientIns().Get(url)
if err != nil {return
}if resp.StatusCode != 200 {return nil, fmt.Errorf(fmt.Sprintf("http code : %d", resp.StatusCode))
}bytes, err := ioutil.ReadAll(resp.Body)
if err != nil {return
}
defer resp.Body.Close()
以上代码看起来貌似没什么问题,但是当resp.StatusCode != 200 的时候,直接返回了,因此 defer resp.Body.Close() 这句代码并没有被调用,也就是产生的tcp连接并没有被关闭。由于代码中会并发的多次请求,如果请求全部都不是200,那么最终都不会Close,便会产生大量的ESTABLISHED状态。
那么我们只需要把defer resp.Body.Close()这一句提前即可,如下:
resp, err := getHttpClientIns().Get(url)
if err != nil {return
}
defer resp.Body.Close()if resp.StatusCode != 200 {return nil, fmt.Errorf(fmt.Sprintf("http code : %d", resp.StatusCode))
}bytes, err := ioutil.ReadAll(resp.Body)
if err != nil {return
}
当然,上面的代码其实依然有问题,后面再说。
二、产生大量的TIME_WAIT
前面说了,当时是并发的去大量的请求,代码类似下面:
for i := 0; i <10; i++{go func(){for i := 0; i <10000000; i++{resp, err := http.Get(url)}}()
}
即开多个线程并发的去请求,然后就发现TIME_WAIT的数量持续上升,很快就导致系统资源耗尽。经查,发现http client的参数MaxConnsPerHost比较小,默认为2,因此调大了连接池的数量。具体请求的代码改成类型下面的样子
timeoutContext, _ := context.WithTimeout(context.Background(), timeOut)req, err := http.NewRequestWithContext(timeoutContext, "GET", url, nil)if err != nil {return nil, err}resp, err := getHttpClientWithMaxPerlHost(maxPerlHostConnect).Do(req)if err != nil {return nil, err}defer resp.Body.Close()if resp.StatusCode != 200 {return nil, fmt.Errorf(fmt.Sprintf("http code : %d", resp.StatusCode))}bytes, err := ioutil.ReadAll(resp.Body)if err != nil {return}
其中getHttpClientWithMaxPerlHost()函数主要是得到一个http clien,我是这样配的:
client = &http.Client{Transport: &http.Transport{Proxy: http.ProxyFromEnvironment,DialContext: (&net.Dialer{Timeout: 5 * time.Second,KeepAlive: 30 * time.Second,}).DialContext,//MaxIdleConns: maxPerlHostConnect*10,IdleConnTimeout: 90 * time.Second,TLSHandshakeTimeout: 10 * time.Second,ExpectContinueTimeout: 1 * time.Second,ResponseHeaderTimeout: 30 * time.Second,MaxConnsPerHost: maxPerlHostConnect,MaxIdleConnsPerHost: maxPerlHostConnect / 2,},
正常情况下,确实可以把TIME_WAIT的数量将下来。但是,当请求全部404之后,我发现仍然会产生大量TIME_WAIT,与正常的有啥不同呢,正常情况下会读取Body里面的内容。
正好网上的一篇文章使用golang的`http.Client`容易出现TIME_WAIT上涨的几种情况和解决方案 - Go语言中文网 - Golang中文社区 (studygolang.com),其中第一点引起了我的注意,我试着当resp.StatusCode != 200的时候,也把Body里面的内容读出来,代码如下:
timeoutContext, _ := context.WithTimeout(context.Background(), timeOut)req, err := http.NewRequestWithContext(timeoutContext, "GET", url, nil)if err != nil {return nil, err}resp, err := getHttpClientWithMaxPerlHost(maxPerHost).Do(req)if err != nil {return nil, err}defer resp.Body.Close()if resp.StatusCode != 200 {io.Copy(ioutil.Discard, resp.Body)return nil, fmt.Errorf(fmt.Sprintf("http code : %d", resp.StatusCode))}bytes, err := ioutil.ReadAll(resp.Body)if err != nil {return}
果然,TIME_WAIT的数量降下来了。
总结:
1、如果单线程请求,默认的http client就够用了,不需要额外的参数配置。
2、如果多线程并发请求,那么需要配置参数,主要是MaxConnsPerHost和MaxIdleConnsPerHost
3、一定要记得调用resp.Body.Close(),关闭打开的Body.
4、一定要把Body里面的内容读出来,就算异常也要读出来丢掉。