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 命令修改。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM