Golang 产生大量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里面的内容读出来,就算异常也要读出来丢掉。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值