Go HTTP Proxy: Too many Open files

栏目: Go · 发布时间: 5年前

内容简介:最近在用go写一个HTTP代理服务,集成测试的时候发现访问量增大到一定程度它就会抛出”Too many open files”异常,简单的通过ulimit增加最大文件描述符数量并不能彻底解决问题。使用lsof定位问题组件–HTTP代理服务使用netstat查看连接信息,果然发现有大量的由代理发起的ESTABLISHED连接

起因

最近在用 go 写一个HTTP代理服务,集成测试的时候发现访问量增大到一定程度它就会抛出”Too many open files”异常,简单的通过ulimit增加最大文件描述符数量并不能彻底解决问题。

定位

使用lsof定位问题组件–HTTP代理服务

使用netstat查看连接信息,果然发现有大量的由代理发起的ESTABLISHED连接

分析

http请求的处理函数大概长这样:

func handleHTTP(w http.ResponseWriter, req *http.Request) {
    resp, err := http.DefaultTransport.RoundTrip(req)
    if err != nil {
        http.Error(w, err.Error(), http.StatusServiceUnavailable)
        return
    }
    defer resp.Body.Close()
    copyHeader(w.Header(), resp.Header)
    w.WriteHeader(resp.StatusCode)
    io.Copy(w, resp.Body)
}

每次handleHTTP函数被调用后,都会新建一个TCP连接, 如果对端不先关闭该连接(对端发FIN包)的话,即便是调用了resp.Body.Close()函数仍然不会改变这些处于ESTABLISHED状态的连接 。这种现象会涉及到源码:

transport层会首先找与该请求相关的已经缓存的连接,如果已经有可以复用的旧连接,就会在这个旧连接上发送和接受该HTTP请求,否则会新建一个TCP连接,然后在这个连接上读写数据。

在函数handleHTTP()的每次调用中,我们都会新创建transport和RoundTrip结构,当HTTP请求完成并且接收到响应后,如果对端的HTTP服务器没有关闭连接,那么这个连接会一直处于ESTABLISHED状态。

不幸的是,HTTP1.1默认都会启用Keep Alive,正常情况下服务端客户端都会根据首部字段Conent-Length或者Transfer-Encoding来判断是否接收完成,在完成之前,连接会一直保持。

(貌似)知道了原因就可以实施解决方案了。比较容易实现的有

1. 减少keep alive超时时间

2. 经过代理时,通过把首部字段Connection设置成false等手段以禁止长连接。

3. 增大文件句柄上限

4. 使用全局RoundTrip,每次都通过它发送

5. 使用nginx来作为网关,把问题丢给它处理

考虑到我的项目只是通过代理拉一些静态资源,所以采用了方案2。禁用Keep Alive后,连接就不会被丢到缓存了。修改后的代码长这样:

MyTransport := &http.Transport{
         Proxy: ProxyFromEnvironment,
         DialContext: (&net.Dialer{
                 Timeout:   10 * time.Second,
                 KeepAlive: -1,
                 DualStack: true,
         }).DialContext,
         MaxIdleConns:          100,
         IdleConnTimeout:       30 * time.Second,
         TLSHandshakeTimeout:   10 * time.Second,
         ExpectContinueTimeout: 1 * time.Second,
     }
     resp, err := MyTransport.RoundTrip(req)

编译执行!单元测试通过!提交集成测试!果然没有大量的ESTABLISHED了!

转折

本来以为这个BUG已经修了,然而测试的小伙伴又给我打了回来。不过这次的报错变成了Cannot assign requested address。经过分析,发现在测试环境上出现了大量TIME_WAIT连接,导致端口耗尽无法创建套接字

无论是设置net.ipv4.tcp_tw_recycle=1还是net.ipv4.tcp_tw_resue=1都没有作用,所以初步分析不是程序的问题,而是环境的问题

通过分析环境,发现代理和测试客户端部署到了一台终端机上,而且终端机开启了全局系统代理。由于Go的DefaultTransport会读取代理相关环境变量,导致发送时又提交给了自己,形成了代理回环

解决方案很简单,让Transport不使用代理,或者启动时加入env -i参数就可以了。修改后的代码长这样:

MyTransport := &http.Transport{
         Proxy: nil,
         DialContext: (&net.Dialer{
                 Timeout:   10 * time.Second,
                 KeepAlive: -1,
                 DualStack: true,
         }).DialContext,
         MaxIdleConns:          100,
         IdleConnTimeout:       30 * time.Second,
         TLSHandshakeTimeout:   10 * time.Second,
         ExpectContinueTimeout: 1 * time.Second,
     }
     resp, err := MyTransport.RoundTrip(req)

完美解决

参考

  1. Golang HTTP to many open files
  2. How to close Golang’s HTTP connection
  3. Time Wait
  4. RoundTripper

Post Views: 9


以上所述就是小编给大家介绍的《Go HTTP Proxy: Too many Open files》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

单元测试之道Java版

单元测试之道Java版

David Thomas、Andrew Hunt / 陈伟柱、陶文 / 电子工业 / 2005-1 / 25.00元

程序员修炼三部曲丛书包含了四本书,介绍了每个注重实效的程序员和成功团队所必备的一些工具。 注重实效的程序员都会利用反馈来指导开发,并驱动个人的开发流程。编码的时候,最有用的反馈来自于“单元测试”。 为了测试一座桥梁,不会只在晴朗的天气,开一辆汽车从桥中间穿过,就认为已经完成了对桥梁的测试。然而许多程序员却正在使用这种测试方法——把这种一次顺利通过称为“测试”。事实上,注重实效的程序员应......一起来看看 《单元测试之道Java版》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具