Go http.Get、并发和“连接被对等方重置”

27

我需要从一个服务器下载1000到2000个网页,使用go协程和通道来实现高效率。问题是每次运行程序时,最多有400个请求失败,并出现“connection reset by peer”的错误。很少情况下(也许10分之1的概率),没有请求失败。

我该怎么做才能防止这种情况发生?

有趣的一点是,当我在与托管网站的服务器在同一个国家的服务器上运行此程序时,0个请求失败,所以我猜测存在延迟问题(因为它现在在不同大陆的服务器上运行)。

我使用的代码基本上只是一个简单的http.Get(url)请求,没有额外的参数或自定义客户端。


所有页面都来自同一服务器吗?您最多同时发起的请求次数是多少? - JimB
所有页面都来自同一台服务器(编辑问题以反映这一点)。我不确定有多少个同时进行。我只是启动与要下载的网页数量相同的go例程,然后让CPU/Golang强制执行并发限制。 - fgblomqvist
并发性没有明确定义的限制,需要自己处理。 - JimB
4个回答

40

connection reset by peer这条消息表示远程服务器发送了一个 RST 以强制关闭连接,无论是出于有意限制连接的机制还是由于资源不足的结果。无论哪种方式,您都可能打开了太多连接或者重新连接得太快。

同时并行启动1000-2000个连接很少是下载那么多页面的最有效方法,特别是如果大部分或全部来自单个服务器。如果测试吞吐量,您会发现最佳并发级别要低得多。

您还需要设置 Transport.MaxIdleConnsPerHost 以匹配您的并发级别。如果 MaxIdleConnsPerHost 小于预期的并发连接数,则服务器连接经常会在请求后被关闭,然后立即重新打开--这将显著地减慢进度,并可能达到服务器施加的连接限制。


4
这是一个很好的答案。我最终进行了一些测量,以确定同时连接多少个可以获得最佳性能,在我目前使用的这个连接中,大约为50个,超过这个数量的连接几乎没有额外的性能提升。我将运行的go例程数量限制为最大50个,并将MaxIdleConnsPerHost设置为50。现在每次都可以正常工作! - fgblomqvist
@JimB:很多时间过去了,但现在对我仍然有意义:如果收到错误消息后尝试发送另一个请求是否可能呢? (我知道这可能不是最好的解决方案)。 如果连接重置,client.Do()会返回错误吗? 我不太确定,因为它似乎只返回状态代码2XX的错误。 我的初始方法是稍等一会儿,然后再尝试相同的请求。 除了实施您在答案中提出的内容之外,这是否是处理错误的有效方法? - Mxngls
@Mxngls,这完全取决于你。如果你遇到意外错误,并且想要重试请求,那么你可以这样做。 - JimB
@JimB:感谢您的快速回复!我的问题更具体地询问了这种错误处理是否在此处有效。查看http包的文档,我不确定错误是否来自发送请求的client.Do()函数。 - Mxngls
1
网络连接可能会在任何时候关闭,因此您可能会从Do()或读取响应时得到该信息。但这并不重要,因为网络是不可靠的,如果出现意外错误并希望重试,则这是完全正常的事情。 - JimB
显示剩余3条评论

20

作为一名Golang新手,希望这能有所帮助。

var netClient = &http.Client{}

func init() {
    tr := &http.Transport{
        MaxIdleConns:       20,
        MaxIdleConnsPerHost:  20,
    }
    netClient = &http.Client{Transport: tr}
}

func foo() {
    resp, err := netClient.Get("http://www.example.com/")
}

5

我通过在传输中设置MaxConnsPerHost选项取得了良好的结果...

cl := &http.Client{
    Transport: &http.Transport{MaxConnsPerHost: 50}
}

MaxConnsPerHost是可选的,可以限制每个主机的总连接数,包括处于拨号、活动和空闲状态的连接。当达到限制时,拨号将会被阻塞。https://golang.org/pkg/net/http/#Transport.MaxConnsPerHost。注:为了澄清,在@AG1或@JimB的回答发布之前,这个选项是在Go 1.11中发布的,因此我发布了这篇文章。

这基本上是与@AG1在2年前发布的相同解决方案。 - fgblomqvist
5
请仔细阅读我的回答,我想说的是,AG1使用了MaxIdleConnsPerHost,但这对我没有用。在 Go 1.11 中引入了MaxConnsPerHost(于2018年11月发布),而当 AG1 发布他的回答时,Go 1.11 还未发布。 - JamesHalsall
2
抱歉,我有点匆忙地阅读了您的答案。尽管如此,感谢您的澄清,这肯定会帮助未来的读者。 - fgblomqvist
我该如何以这种方式为每个请求设置不同的代理?这是可能的吗? - Amir Khoshhal

0
可能你正在下载网页的服务器有某种限制机制,防止来自特定IP的请求超过一定数量/每秒。尝试将请求数量限制在每秒100个或在请求之间添加延迟。 "Connection reset by peer"基本上是服务器拒绝为您提供服务。(链接:"connection reset by peer"是什么意思?)

考虑到当我在与Web服务器位于同一国家的服务器上运行时,一切都正常运行,它似乎没有这样的限制(除非它们仅对来自其他国家的人施加,但在我的情况下这并不太合理)。然而,我将研究每秒请求的数量限制。 - fgblomqvist
通常服务器只能处理一定数量的并发请求,而您可能已经超过了该容量。它可以在同一国家内正常运行的原因是请求可能需要更少的时间,因此连接不会被占用太长时间,服务器可以处理更多请求。 - robbrit
@robbrit 我猜那可能是情况。我认为我需要实现一个连接池。 - fgblomqvist
@fgblomqvist:你不需要连接池,http.Transport已经为您处理了。只需限制并发性,并设置 Transport.MaxIdleConnsPerHost 以匹配您的最大并发性。 - JimB
@JimB 能否详细解释一下?我不明白设置MaxIdleConnsPerHost如何限制到主机的最大打开连接数?另外,为什么我还需要限制并发性呢?如果我启动1000个go例程,每个例程都进行一次GET请求,它们将打开约1000个连接,无论它们是否共享HTTP客户端。 - fgblomqvist

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