Go net/http服务器:打开的文件太多错误
我正试图开发一个简单的作业队列服务器,其中有一些工作人员查询它,但我的net/http服务器遇到了一个问题。我确实做了一些不好的事情,但大约3分钟后,我的服务器开始显示: http:Accept错误:Accept tcp[::]:4200:Accept 4:打开的文件太多;在1秒内重试 在我的测试用例中,它每秒接收10个请求 这里有两个文件可以重现此错误:Go net/http服务器:打开的文件太多错误,go,Go,我正试图开发一个简单的作业队列服务器,其中有一些工作人员查询它,但我的net/http服务器遇到了一个问题。我确实做了一些不好的事情,但大约3分钟后,我的服务器开始显示: http:Accept错误:Accept tcp[::]:4200:Accept 4:打开的文件太多;在1秒内重试 在我的测试用例中,它每秒接收10个请求 这里有两个文件可以重现此错误: // server.go package main import ( "net/http" ) func main() {
// 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响应中的主体,正如您所看到的那样
第二个响应是更改我的系统的文件描述符ulimit,但由于我无法控制我的应用程序的运行位置,因此我宁愿不使用此解决方案(但请注意,在我的系统上它设置为1024)
有人能解释一下为什么会出现这个问题,以及我如何在代码中修复它吗
非常感谢你抽出时间
编辑:
编辑2:在评论中,马丁说我没有关闭身体,我试图关闭它(没有延迟),它解决了这个问题。谢谢你,马丁!我认为continue将执行我的延迟,我错了。正如Martin在评论中所说的,在Get请求之后,我并没有真正关闭身体。我使用了
defer res.Body.Close()
,但由于我处于for循环中,所以没有执行它。因此,请注意,在某些情况下,/etc/sysctl.conf中的设置
net.ipv4.tcp_tw_recycle=1
可能会导致此错误,因为TCP连接保持打开状态。我找到了一个更详细的方法来解释根本问题。
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
的另一个原因是它是一个单例(包级别变量),这意味着垃圾收集器不会尝试清理它,这将使空闲的后续流/套接字保持活动状态
而是创建您自己的http.Client
实例,并记住始终指定一个正常的超时
:
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
是一个单例,我们可以轻松更改超时设置,以确保遗留代码不会导致空闲连接保持打开状态
我发现最好在init
函数中的package main
文件中设置:
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
}
临时解决方案,只需增加打开文件的数量:
ulimit -Sn 10000
如果您在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后立即显式调用res.Body.Close()
。获取错误并查看其性能是否更好。是的,Martin是正确的:您从未关闭任何请求。Body.Default timeout为10分钟可以吗?也许10秒钟?