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》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

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

硅谷产品:36讲直通世界级产品经理

硅谷产品:36讲直通世界级产品经理

曲晓音 / 电子工业出版社 / 2018-10 / 59

《硅谷产品:36讲直通世界级产品经理》是Facebook资深产品经理曲晓音撰写的产品实战教程,立足于作者在Facebook、Instagram、Microsoft、Atlassian等硅谷科技公司的工作经验,分享硅谷先进的产品思维和方法论,用实际案例带领读者了解硅谷产品经理的所想所做。 《硅谷产品:36讲直通世界级产品经理》适合产品经理从业者、想要提升产品理解能力的技术运营人员、刚刚入行或者......一起来看看 《硅谷产品:36讲直通世界级产品经理》 这本书的介绍吧!

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

MD5 加密
MD5 加密

MD5 加密工具

SHA 加密
SHA 加密

SHA 加密工具