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

Uhs*_*sac 19 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
    }
}
Run Code Online (Sandbox Code Playgroud)

我已经对这个错误做了一些搜索,我发现了一些有趣的回复,但这些都没有解决我的问题.

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

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

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

非常感谢你的时间

编辑: 提问评论

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

jus*_*rza 16

我发现了一篇帖子,详细解释了根本问题.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
}
Run Code Online (Sandbox Code Playgroud)

最佳实践

正如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
}
Run Code Online (Sandbox Code Playgroud)

安全网

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

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

我发现最好package maininit函数中的文件上设置它:

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
}
Run Code Online (Sandbox Code Playgroud)


Uhs*_*sac 7

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