在Go语言中出现“Lookup [HOST]: no such host”错误

16

我有一个测试程序,可以并行获取URL,但当我将并行数增加到约1040时,我开始收到lookup www.httpbin.org: no such host错误。

经过一些谷歌搜索,我发现其他人说没有关闭响应将导致问题,但我使用了res.Body.Close()关闭了响应。

这里的问题是什么?非常感谢。

package main

import (
    "fmt"
    "net/http"
    "io/ioutil"
)

func get(url string) ([]byte, error) {

    client := &http.Client{}
    req, _ := http.NewRequest("GET", url, nil)

    res, err := client.Do(req)

    if err != nil {
        fmt.Println(err)
        return nil, err
    } 

    bytes, read_err := ioutil.ReadAll(res.Body)
    res.Body.Close()

    fmt.Println(bytes)

    return bytes, read_err
}

func main() {
    for i := 0; i < 1040; i++ {
        go get(fmt.Sprintf("http://www.httpbin.org/get?a=%d", i))
    }
}

3
我赞同dystroy和simonmenke的回答,因为他们都是正确的。他们只是从两个不同的角度来解决这个问题。问题在于同时向主机打开超过1000个连接会超出FD限制。Dystroy的答案是通过减少并发连接使用较少的FD,而simonmenke的答案则是增加FD限制。对于你的具体情况,我建议采用Dystroy的答案。如果别人遇到相同的问题,则更有意义地使用simonmenke的答案。 - Stephen Weinberg
2个回答

16

从技术上讲,您的进程被内核限制为大约1000个打开文件描述符。根据上下文,您可能需要增加此数字。

在您的shell中运行以下命令(请注意最后一行):

$ 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 (kb)         unlimited
-l: locked-in-memory size (kb) unlimited
-u: processes                  709
-n: file descriptors           2560

临时提高方法:

$ ulimit -n 5000
(no output)

然后验证fd限制:

$ ulimit -n
5000

我的编号是1024,但为什么会存在超过1024个文件描述符?我已经在处理完它们后将每一个都关闭了。 - wong2
2
这是因为当您使用go关键字时,函数会并发运行。您正在使用不同的连接一次性获取所有1040个URL。因此,您一次打开了1040个(大于1024个)FD。 - Stephen Weinberg

12

这是因为您的代码可能有多达1040个并发调用,因此您很可能处于有1040个body标签打开但尚未关闭的状态。

您需要限制使用的goroutine数量。

下面是一种可能的解决方案,最多允许100个并发调用:

func getThemAll() {
    nbConcurrentGet := 100
    urls :=  make(chan string, nbConcurrentGet)
    for i := 0; i < nbConcurrentGet; i++ {
        go func (){
            for url := range urls {
                get(url)
            }
        }()
    }
    for i:=0; i<1040; i++ {
        urls <- fmt.Sprintf("http://www.httpbin.org/get?a=%d", i)
    }
}

如果你在程序的主函数中调用它,可能会在所有任务完成之前停止。你可以使用 sync.WaitGroup 来防止这种情况:

func main() {
    nbConcurrentGet := 100
    urls :=  make(chan string, nbConcurrentGet)
    var wg sync.WaitGroup
    for i := 0; i < nbConcurrentGet; i++ {
        go func (){
            for url := range urls {
                get(url)
                wg.Done()
            }
        }()
    }
    for i:=0; i<1040; i++ {
        wg.Add(1)
        urls <- fmt.Sprintf("http://www.httpbin.org/get?a=%d", i)
    }
    wg.Wait()
    fmt.Println("Finished")
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接