net / http server:打开文件太多错误

时间:2016-05-26 07:28:18

标签: go

我正在尝试开发一个简单的作业队列服务器,其中有一些工作人员查询它,但我遇到了net / http服务器的问题。我肯定会做一些不好的事情但是在服务器开始显示约3分钟之后:

  

http:接受错误:接受tcp [::]:4200:accept4:打开文件太多;在1s重试

有关信息,我的测试用例每秒会收到10个请求。

此处有两个文件重现此错误:

// server.go
package main

import (
    "net/http"
)

func main() {
    http.HandleFunc("/get", func(rw http.ResponseWriter, r *http.Request) {
        http.Error(rw, "Try again", http.StatusInternalServerError)
    })
    http.ListenAndServe(":4200", nil)
}

// worker.go
package main

import (
    "net/http"
    "time"
)

func main() {
    for {
        res, _ := http.Get("http://localhost:4200/get")
        defer res.Body.Close()

        if res.StatusCode == http.StatusInternalServerError {
            time.Sleep(100 * time.Millisecond)
            continue
        }

        return
    }
}

我已经对此错误进行了一些搜索,我发现了一些有趣的回复,但这些都没有解决我的问题。

我看到的第一个响应是在http.Get响应中正确关闭Body,你可以看到我做到了。

第二个响应是更改系统的文件描述符ulimit但由于我无法控制应用程序的运行位置,我宁愿不使用此解决方案(但是对于我在系统上设置为1024的信息)

有人可以解释一下为什么会出现这个问题以及如何在我的代码中修复它?

非常感谢你的时间

编辑: Asked by comment

编辑2:评论马丁说我没有关闭身体,我试图关闭它(没有推迟),它解决了问题。谢谢马丁!我以为继续执行我的推迟,我错了。

4 个答案:

答案 0 :(得分:12)

我发现post更详细地解释了根本问题。 Nathan Smith甚至解释了如何在需要时控制TCP级别的超时。 以下是我可以在这个特定问题上找到的所有内容的摘要,以及将来避免此问题的最佳做法。

问题

无论是否需要响应主体,当收到响应时,连接将保持活动状态,直到响应主体流关闭为止。因此,如此线程中所述,始终关闭响应主体。即使您不需要使用/阅读身体内容:

func Ping(url string) (bool) {
    // simple GET request on given URL
    res, err := http.Get(url)
    if err != nil {
        // if unable to GET given URL, then ping must fail
        return false
    }

    // always close the response-body, even if content is not required
    defer res.Body.Close()

    // is the page status okay?
    return res.StatusCode == http.StatusOK
}

最佳实践

正如Nathan Smith从未在生产系统中使用http.DefaultClient所述,这包括http.Get之类的调用,因为它在其基础上使用http.DefaultClient

避免http.DefaultClient的另一个原因是它是一个Singleton(包级变量),这意味着垃圾收集器不会尝试清理它,这将使后续的流/套接字空闲。

而是创建自己的http.Client实例并记住始终指定一个合理的Timeout

func Ping(url string) (bool) {
    // create a new instance of http client struct, with a timeout of 2sec
    client := http.Client{ Timeout: time.Second * 2 }

    // simple GET request on given URL
    res, err := client.Get(url)
    if err != nil {
        // if unable to GET given URL, then ping must fail
        return false
    }

    // always close the response-body, even if content is not required
    defer res.Body.Close()

    // is the page status okay?
    return res.StatusCode == http.StatusOK
}

安全网

安全网是针对团队中的新手,他不知道http.DefaultClient使用的不足。甚至是那个非常有用但不那么活跃的开源库,它仍然充斥着http.DefaultClient次调用。

由于http.DefaultClient是Singleton,我们可以轻松更改Timeout设置,只是为了确保遗留代码不会导致空闲连接保持打开状态。

我发现最好将其设置在package main函数的init文件中:

package main

import (
    "net/http"
    "time"
)

func init() {
    /*
    Safety net for 'too many open files' issue on legacy code.
    Set a sane timeout duration for the http.DefaultClient, to ensure idle connections are terminated.
    Reference: https://stackoverflow.com/questions/37454236/net-http-server-too-many-open-files-error
    */
    http.DefaultClient.Timeout = time.Minute * 10
}

答案 1 :(得分:7)

正如马丁在评论中所说,在Get请求之后,我并没有真正关闭Body。我使用了defer res.Body.Close(),但由于我停留在for循环中,因此没有执行。因此continue不要触发defer

答案 2 :(得分:2)

请注意在某些情况下/etc/sysctl.conf中的设置 net.ipv4.tcp_tw_recycle = 1

可能导致此错误,因为TCP连接仍保持打开状态。

答案 3 :(得分:1)

一个临时解决方案,只需增加打开的文件数:

ulimit -Sn 10000