【问题标题】:net/http server: too many open files errornet/http 服务器:打开的文件太多错误
【发布时间】:2016-05-26 07:28:18
【问题描述】:

我正在尝试使用一些查询它的工作人员开发一个简单的作业队列服务器,但我的 net/http 服务器遇到了问题。我肯定做错了什么,但大约 3 分钟后我的服务器开始显示:

http: Accept error: accept tcp [::]:4200: accept4: too many open files; 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)

谁能解释我为什么会出现这个问题以及如何在我的代码中解决它?

非常感谢您的宝贵时间

编辑:

编辑 2:Martin 在评论中说我没有关闭 Body,我试图关闭它(没有延迟),它解决了这个问题。谢谢马丁!我以为 continue 会执行我的 defer,我错了。

【问题讨论】:

  • 如果您使用的是 linux,请使用 ps aux | grep {program-name} 获取您的进程 ID,然后在出现此错误时使用 ls -l /proc/{process-id}/fd查看打开的文件。将输出添加到您的问题中。谢谢
  • 我相信你无论如何都应该更新系统属性。试试这个:echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p
  • 您的 defer res.Body.Close() 正在排队等待所有资源释放,直到您的 worker main() 返回。检查http.Get 错误后立即显式调用res.Body.Close(),看看它是否表现更好。
  • 是的,Martin 是正确的:你从不关闭任何 Request.Body。

标签: go


【解决方案1】:

我找到了一个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 的另一个原因是它是一个单例(包级别变量),这意味着垃圾收集器不会尝试清理它,这将使空闲的后续流/套接字保持活动状态。 em>

改为创建您自己的 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 是单例,我们可以轻松更改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
}

【讨论】:

  • 默认超时 10 分钟可以吗?也许 10 秒?
【解决方案2】:

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

【讨论】:

    【解决方案3】:

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

    由于 TCP 连接保持打开状态,可能会导致此错误。

    【讨论】:

      【解决方案4】:

      临时解决办法,只是增加打开文件的数量:

      ulimit -Sn 10000
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-10-03
        • 2010-12-07
        • 1970-01-01
        • 1970-01-01
        • 2014-06-23
        相关资源
        最近更新 更多