Go, tcp too many open files debug

Go, tcp too many open files debug

这是一个简单的 Go http (tcp) 连接测试脚本

func main() {
    ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintln(w, "Hello, client")
    }))
    defer ts.Close()
    var wg sync.WaitGroup
    for i := 0; i < 2000; i++ {
        wg.Add(1)
        go func(i int) {
            defer wg.Done()
            resp, err := http.Get(ts.URL)
            if err != nil {
                panic(err)
            }
            greeting, err := ioutil.ReadAll(resp.Body)
            resp.Body.Close()
            if err != nil {
                panic(err)
            }
            fmt.Printf("%s", i, greeting)
        }(i)
    }
    wg.Wait()
}

如果我 运行 这个在 Ubuntu 我得到:

panic: Get http://127.0.0.1:33202: dial tcp 127.0.0.1:33202: too many open files

其他帖子说要确保 Close 连接,我在这里都这样做了。 其他人说用 ulimit 增加最大连接数限制或尝试 sudo sysctl -w fs.inotify.max_user_watches=100000 但仍然不起作用。

如何在单个服务器中 运行 数百万个 tcp 连接 goroutine? 它仅在 2,000 个连接时崩溃。

谢谢,

我认为您需要更改最大文件描述符。我之前 运行 在我的一个开发虚拟机上遇到过同样的问题,需要更改文件描述符最大值,而不是任何带有 inotify 设置的内容。

FWIW,你的程序 运行 在我的 VM 上没问题。

·> ulimit -n
120000

但是在我运行

之后
·> ulimit -n 500
·> ulimit -n
500

我得到:

panic: Get http://127.0.0.1:51227: dial tcp 127.0.0.1:51227: socket: too many open files

** 不要落入 Praveen 的圈套**

注意ulimit != ulimit -n.

➜  cmd git:(wip-poop) ✗ ulimit -a
-t: cpu time (seconds)              unlimited
-f: file size (blocks)              unlimited
-d: data seg size (kbytes)          unlimited
-s: stack size (kbytes)             8192
-c: core file size (blocks)         0
-v: address space (kbytes)          unlimited
-l: locked-in-memory size (kbytes)  unlimited
-u: processes                       1418
-n: file descriptors                4864

我还必须手动设置关闭连接 header 以避免文件描述符问题:

r, _ := http.NewRequest(http.MethodDelete, url, nil)
r.Close = true
res, err := c.Do(r)
res.Body.Close();

没有 r.Close = true 和 res.Body.Close() 我达到了文件描述符限制。有了这两者,我可以根据需要开火。

更改 ulimit 以避免错误 "too many open files" 默认情况下,linux 的最大 ulimit 为 4096,mac 的最大 ulimit 为 1024, 您可以通过键入将 ulimit 更改为 4096 ulimit -n 4096 对于超过 4096,您需要修改 etc/security 文件夹中的 limits.conf 以获取 linux 并通过添加此行“* hard core 100000”

将硬限制设置为 100000
HTTP/1.1 uses persistent connections by default:
A significant difference between HTTP/1.1 and earlier versions of HTTP is that persistent connections are the default behavior of any HTTP connection.
http://www.w3.org/Protocols/rfc2616/rfc2616-sec8.html
The solution was to inform the server that the client wants to close the connection after the transaction is complete. This can be done by setting the Connection header,
req.Header.Set("Connection", "close") or by setting the Close property to true on the http.Request:
req.Close = true After doing that, the “too many open files” issue went away as the program was no longer keeping HTTP connections open and thus not using up file descriptors.

我通过添加 req.Close = true 和 req.Header.Set("Connection", "close") 解决了这个问题。我认为这比更改 ulimit 更好。

来源:http://craigwickesser.com/2015/01/golang-http-to-many-open-files/

如果你想要 运行 数百万个 open/read/close 套接字的例程,那么你最好设置你的 ulimit,或者 open/read/close 套接字并将读入的值传递给go-routine,但我会使用缓冲通道来控制您希望能够打开多少个文件描述符。

const (
    // this is where you can specify how many maxFileDescriptors
    // you want to allow open
    maxFileDescriptors = 100
)

func main() {
    ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintln(w, "Hello, client")
    }))
    defer ts.Close()
    var wg sync.WaitGroup
    maxChan := make(chan bool, maxFileDescriptors)
    for i := 0; i < 1000; i++ {
        maxChan <- true
        wg.Add(1)
        go func(url string, i int, maxChan chan bool, wg *sync.WaitGroup) {
            defer wg.Done()
            defer func(maxChan chan bool) { <-maxChan }(maxChan)
            resp, err := http.Get(url)
            if err != nil {
                panic(err)
            }
            greeting, err := ioutil.ReadAll(resp.Body)
            if err != nil {
                panic(err)
            }
            err = resp.Body.Close()
            if err != nil {
                panic(err)
            }
            fmt.Printf("%d: %s", i, string(greeting))
        }(ts.URL, i, maxChan, &wg)
    }
    wg.Wait()
}

Go 的 http 包默认不指定请求超时。您应该始终在服务中包含超时。如果客户没有关闭他们的会话怎么办?您的进程将使达到 ulimits 的旧会话保持活动状态。坏人可能会故意打开数以千计的会话,对您的服务器进行 DOS 攻击。重载服务也应该调整 ulimits 但逆止器超时。

确保指定超时:

http.DefaultClient.Timeout = time.Minute * 10

您可以通过监控进程打开的文件来验证之前和之后:

lsof -p [PID_ID]

临时允许打开更多文件:

通过运行查看您当前的设置:

sudo launchctl limit maxfiles

通过 运行 以下命令将限制增加到 65535 个文件。如果您的网站文件较少,您可以选择设置较低的软 (65535) 和硬 (200000) 限制。

sudo launchctl limit maxfiles 65535 200000
ulimit -n 65535
sudo sysctl -w kern.maxfiles=200000
sudo sysctl -w kern.maxfilesperproc=65535

请注意,您可能需要为每个新 shell 设置这些限制。

https://www.google.com/search?q=mac+os+launchctl+limit+maxfiles+site%3Aapple.stackexchange.com&oq=mac+os+launchctl+limit+maxfiles+site%3Aapple.stackexchange.com