Golang中net/http包导致too many open files问题如何解决
Golang中net/http包导致too many open files问题如何解决 我正在开发一个基于SAAS的项目,该项目使用REST API构建,可以拥有多个商户账户。我们需要为每个商户每10分钟运行一次定时任务。这些定时任务的URL是我们服务器自身的API。因此,我使用了Golang的net/http包来满足这个需求。
我遇到的错误: http: Accept error: accept tcp [::]:8080: accept4: too many open files; retrying in 1s 以及 connection() error occured during connection handshake: dial tcp IP:27017: socket: too many open files.
我已经尝试过的:
我尝试过使用 connection: close 请求头,移除 defer 并显式调用 req.Body.Close(),以及复用HTTP客户端来避免连接数超限。
请看一下代码:
package main
import (
"gopkg.in/robfig/cron.v3"
)
func main() {
RunCron()
}
func RunCron() {
c := cron.New()
c.AddFunc("@every 0h10m0s", SendMsg)
c.Start()
}
func SendMsg() {
RunCronBatch()
}
func RunCronBatch() {
businessNames := []string{"bsName1", "bsName2","bsName3","bsName4"}
client := &http.Client{}
for _, businessName := range businessNames {
url := "http://" + businessName + ".maim-domain.com"
go func(client *http.Client, url string) {
CallHttpRqstScheduler(client, "GET", url, false)
}(client, url)
}
}
func CallHttpRqstScheduler(client *http.Client, method, url string, checkResp bool, body ...io.Reader) (error, interface{}) {
var reqBody io.Reader
if len(body) > 0 {
reqBody = body[0]
} else {
reqBody = nil
}
req, err := http.NewRequest(method, url, reqBody)
if err != nil {
fmt.Print(err.Error())
return err, nil
}
req.Header.Set("Connection", "close")
if method == "POST" {
req.Header.Add("Content-Type", "application/json")
}
resp, err := client.Do(req)
if err != nil {
fmt.Print(err.Error())
return err, nil
}
if checkResp {
bodyBytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Print(err.Error())
req.Close = true
resp.Body.Close()
return err, nil
}
var response APIResponseObj
err = json.Unmarshal(bodyBytes, &response)
if err != nil {
fmt.Print(err.Error())
req.Close = true
resp.Body.Close()
return err, nil
}
req.Close = true
resp.Body.Close()
return nil, response.Response.Data
}
req.Close = true
resp.Body.Close()
return nil, nil
}
但是这种结构仍然给我带来了上述错误。
历史情况: 在实现这种结构之前,我也曾将curl与sh命令结合使用。但那种结构同样会耗尽资源,因此遇到了资源暂时不可用的问题。
package main
func main() {
businessNames := []string{"bsName1", "bsName2","bsName3","bsName4"}
for _, businessName := range businessNames {
url := "http://" + businessName + ".main-domain.com"
command := "curl '"+url+"'"
ExecuteCommand(command)
}
}
func ExecuteCommand(command string) error {
cmd := exec.Command("sh", "-c", command)
var out bytes.Buffer
var stderr bytes.Buffer
cmd.Stdout = &out
cmd.Stderr = &stderr
err := cmd.Run()
if err != nil {
fmt.Println(fmt.Sprint(err) + ": " + stderr.String())
return err
}
fmt.Println("Result: " + out.String())
return nil
}
有人能指导我一下,在HTTP方法中我做错了什么或者遗漏了什么吗?
更多关于Golang中net/http包导致too many open files问题如何解决的实战教程也可以访问 https://www.itying.com/category-94-b0.html
是的,企业名称可能有成千上万个。我在每个返回语句前都使用了 resp.Close()(也尝试过使用 defer)。同样尝试过使用工作池,但这个错误似乎仍未解决。https://play.golang.org/p/pMePF8zDvSH。
更多关于Golang中net/http包导致too many open files问题如何解决的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
我猜这个错误来自操作系统。也许你是在一个默认的Linux安装环境下,理论上任何连接都会使用一个来自65535的文件描述符。在这种情况下,即使你正确地关闭了连接,系统也需要一段时间来释放描述符(请记住连接处于僵尸状态)。 所以,如果发生这种情况,你可以限制你的请求,或者调整你的操作系统以接受更多打开的文件(查找’ulimit’命令)。
businessNames 有多长?如果它有数千个或更多,你可能会耗尽可同时打开的连接数。如果是这种情况,不要为每个请求创建新的 goroutine,而是创建一个工作池(可能是 10 个、100 个等),通过通道发送业务名称,然后让一个消费者 goroutine 将结果取回。
我还建议在 client.Do(req) 之后检查 err != nil 后立即使用 defer resp.Close,以确保响应体不会泄漏并保持连接打开。
问题出在HTTP客户端没有正确复用和连接管理上。虽然你创建了单个http.Client,但在并发请求时没有控制并发数量,且没有配置连接池参数。以下是解决方案:
package main
import (
"net/http"
"time"
"golang.org/x/net/http2"
"gopkg.in/robfig/cron.v3"
)
func main() {
RunCron()
}
func RunCron() {
c := cron.New()
c.AddFunc("@every 10m", SendMsg)
c.Start()
select {} // 保持程序运行
}
func SendMsg() {
RunCronBatch()
}
func RunCronBatch() {
businessNames := []string{"bsName1", "bsName2", "bsName3", "bsName4"}
// 创建配置了连接池的HTTP客户端
client := &http.Client{
Transport: &http.Transport{
MaxIdleConns: 100,
MaxIdleConnsPerHost: 100,
MaxConnsPerHost: 100,
IdleConnTimeout: 90 * time.Second,
DisableKeepAlives: false, // 保持连接复用
},
Timeout: 30 * time.Second,
}
// 使用HTTP/2提升性能
http2.ConfigureTransport(client.Transport.(*http.Transport))
// 使用带缓冲的channel控制并发
semaphore := make(chan struct{}, 50) // 限制最大50个并发
for _, businessName := range businessNames {
url := "http://" + businessName + ".maim-domain.com"
semaphore <- struct{}{}
go func(url string) {
defer func() { <-semaphore }()
CallHttpRqstScheduler(client, "GET", url, false)
}(url)
}
// 等待所有goroutine完成
for i := 0; i < cap(semaphore); i++ {
semaphore <- struct{}{}
}
}
func CallHttpRqstScheduler(client *http.Client, method, url string, checkResp bool, body ...io.Reader) (error, interface{}) {
var reqBody io.Reader
if len(body) > 0 {
reqBody = body[0]
}
req, err := http.NewRequest(method, url, reqBody)
if err != nil {
return err, nil
}
// 不需要手动设置Connection头,Transport会处理
if method == "POST" {
req.Header.Set("Content-Type", "application/json")
}
resp, err := client.Do(req)
if err != nil {
return err, nil
}
defer resp.Body.Close() // 确保body被关闭
if !checkResp {
// 即使不检查响应也需要读取并关闭body
io.Copy(io.Discard, resp.Body)
return nil, nil
}
bodyBytes, err := io.ReadAll(resp.Body)
if err != nil {
return err, nil
}
var response APIResponseObj
if err := json.Unmarshal(bodyBytes, &response); err != nil {
return err, nil
}
return nil, response.Response.Data
}
同时需要调整系统文件描述符限制:
# 查看当前限制
ulimit -n
# 临时提高限制
ulimit -n 65536
# 永久修改(Linux)
# 在/etc/security/limits.conf中添加:
# * soft nofile 65536
# * hard nofile 65536
对于MongoDB连接问题,同样需要配置连接池:
// MongoDB连接示例
clientOptions := options.Client().
ApplyURI("mongodb://localhost:27017").
SetMaxPoolSize(100). // 最大连接数
SetMinPoolSize(10). // 最小连接数
SetMaxConnIdleTime(30 * time.Second)
client, err := mongo.Connect(context.Background(), clientOptions)
关键点:
- 正确配置
http.Transport的连接池参数 - 使用
defer resp.Body.Close()确保资源释放 - 通过channel控制并发数量
- 即使不读取响应体也需要消耗并关闭
- 调整系统文件描述符限制

