golang 解决 socket: too many open files, 以及 too many open files

同事写的一段代,码业务场景:需要多次GET请求一个三方服务的http 接口,获取数据后写入文件。发现有部分文件没有写入。查看日志出现了报错“socket: too many open files”、“too many open files”。
在此记录一下解决办法。这也是新写Go的人很常见的问题。

示例代码:

package main

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"path/filepath"
)

func main()  {
	requestAndWriteFile()
}

// 从接口获取数据并且写入文件
func requestAndWriteFile()  {
	// 约有5千个元素
	params := []string{
		"001",
		"002",
		"003",
		"004",
	}
	hostname := "https://test.com/api"
	for _, val := range params {
		url := hostname + "?=code" + val
		byteData, err := Get(url)
		if err != nil{
			fmt.Println(err)
			continue
		}
          fileFullPath := "/var/" + val + ".txt"
		path := filepath.Dir(fileFullPath)
		_, err = os.Stat(path)
		// 不存在则创建
		if err != nil {
			err = os.MkdirAll(path, 0755)
			if err != nil {
				fmt.Println("创建目录错误", err)
				continue
			}
		}
		file, e := os.OpenFile(fileFullPath, os.O_RDWR|os.O_CREATE, 0766)
          // 关闭文件
		defer file.Close()
		if e != nil {
			fmt.Println("打开目录错误", e)
		}
		_, er := file.Write(byteData)
		if er != nil {
			fmt.Println("写入文件错误", er)
		}
	}

}

// 发送GET请求
func Get(url string) ([]byte, error) {
	response, err := http.Get(url)
	if err != nil {
		return nil, err
	}
     // 关闭响应
	defer response.Body.Close()
	return ioutil.ReadAll(response.Body)
}

上面的代码中使用了“defer response.Body.Close()” 关闭了http响应体,打开的文件也用“defer file.Close()”关闭了。乍一看似乎没有问题。但是Go的HTTP请求本身是有坑的,释放不及时,会造成同时有多个socket连接。第二个问题就是“defer file.Close()” 写在for 循环中,那么按照defer的特性,将在函数requestAndWriteFile return之前执行多个defer,越先出现的defer越后执行。多次循环后打开的文件数就超过了系统限制,就会报错“too many open files”。

解决办法是:对于http请求导致“socket: too many open files”,采用公用的 http.Transport;对于“too many open files”,写入文件的操作,封装成函数,在函数中打开关闭文件,就可以避免。修改后的示例代码:

package main

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"path/filepath"
)

// 全局 transport
var globalTransport *http.Transport

func init() {
	globalTransport = &http.Transport{}
}

func main()  {
	requestAndWriteFile()
}

func requestAndWriteFile()  {
	// 约有5千个元素
	params := []string{
		"001",
		"002",
		"003",
		"004",
	}
	hostname := "https://test.com/api"
	for _, val := range params {
		url := hostname + "?=code" + val
		byteData, err := Get(url)
		if err != nil{
			fmt.Println(err)
			continue
		}
		fileFullPath := "/var/" + val + ".txt"
		writeFile(fileFullPath, byteData)
	}

}


func writeFile(fileFullPath string, byteData []byte) error {
	path := filepath.Dir(fileFullPath)
	_, err := os.Stat(path)
	// 不存在则创建
	if err != nil {
		err = os.MkdirAll(path, 0755)
		if err != nil {
			fmt.Println("创建目录错误")
			return err
		}
	}
	file, e := os.OpenFile(fileFullPath, os.O_RDWR|os.O_CREATE, 0766)
     // 一定要close
	defer file.Close()
	if e != nil {
		fmt.Println("打开目录错误")
	}
	_, er := file.Write(byteData)
	if er != nil {
		fmt.Println("写入文件错误")
		return er
	}
     
	return nil
}

// 发送get请求
func Get(uri string) ([]byte, error) {
	client := http.Client{
		Transport: globalTransport,
	}
	res, err := client.Get(uri)
	if err != nil {
		return nil, err
	}
	defer res.Body.Close()
	body, err := ioutil.ReadAll(res.Body)
	if err != nil {
		return nil, err
	}

	return body, nil

}

如果确实有必要同时打开超过系统限制的多个文件,那么可以使用ulimit 命令修改。

posted @   熊先生不开玩笑  阅读(1743)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
点击右上角即可分享
微信分享提示