【问题标题】:Go http.Get, concurrency, and "Connection reset by peer"转到 http.Get、并发和“对等方重置连接”
【发布时间】:2016-10-12 23:29:50
【问题描述】:

我有 1000-2000 个网页要从一台服务器下载,我正在使用 go 例程和通道来实现高效率。问题是每次我运行我的程序时,最多 400 个请求都会失败,并出现错误“对等方重置连接”。很少(可能 10 次中有 1 次)没有请求失败。

我能做些什么来防止这种情况发生?

有趣的一点是,当我在与网站所在服务器位于同一国家/地区的服务器上运行此程序时,0 个请求失败,所以我猜测延迟存在一些问题(就像现在一样在不同大陆的服务器上运行)。

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

【问题讨论】:

  • 所有或大部分页面来自同一台服务器?您同时发出的最大请求数是多少?
  • 所有页面都来自同一服务器(编辑问题以反映这一点)。我不确定同时制作了多少。我只是启动与要下载的网页一样多的 goroutine,然后让 CPU/Golang 对并发施加限制。
  • 没有明确的并发限制,你需要自己做。

标签: http go concurrency


【解决方案1】:

您从中下载网页的服务器可能具有某种类型的节流机制,该机制会阻止来自某个 ip 的每秒/(或类似)超过一定数量的请求?尝试限制为每秒 100 个请求或在请求之间添加睡眠。 对等方重置连接基本上是服务器拒绝您提供服务。 (What does "connection reset by peer" mean?)

【讨论】:

  • 考虑到当我在与 web 服务器位于同一国家的服务器上运行它时一切运行良好,它似乎没有这样的限制(除非它们只强加给来自其他国家的人,这确实在我的场景中没有多大意义)。不过,我会考虑限制每秒的请求数量。
  • 一般服务器只能处理一定数量的并发请求,你可能会超过这个容量。它可以从同一个国家/地区正常运行的一个原因是请求可能花费的时间会大大减少,因此连接不会被用完,服务器可以处理更多。
  • @robbrit 我猜可能是这样。我认为我将不得不实现一个连接池。
  • @fgblomqvist:您不需要连接池,http.Transport 已经为您做到了。只需限制并发,并设置Transport.MaxIdleConnsPerHost 以匹配您的最大并发。
  • @JimB 想扩展它吗?我不明白设置 MaxIdleConnsPerHost 将如何限制与主机的最大打开连接?另外,为什么我还需要限制并发?如果我启动 1000 个 go 例程,每个都发出一个 GET 请求,它们将打开大约 1000 个连接,无论它们是否共享 HTTP 客户端
【解决方案2】:

消息connection reset by peer 表示远程服务器发送了RST 来强制关闭连接,要么是故意作为限制连接的机制,要么是由于缺乏资源。无论哪种方式,您都可能打开太多连接,或者重新连接太快。

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

您还需要设置Transport.MaxIdleConnsPerHost 以匹配您的并发级别。如果MaxIdleConnsPerHost 低于预期的并发连接数,服务器连接通常会在请求后关闭,然后立即再次打开——这将显着减慢您的进度,并可能达到服务器施加的连接限制。

【讨论】:

  • 这是一个很好的答案。我最终对有多少同时连接提供了最佳性能进行了一些测量,对于我目前正在使用的这个连接,结果大约是 50 个,比这更多的连接几乎没有或没有额外的性能。我将运行的 goroutine 的数量限制为最大 50,并将 MaxIdleConnsPerHost 设置为 50。现在每次都有效!
  • @AG1:你在找什么代码?答案归结为将MaxIdleConnsPerHost 设置为等于并发请求的数量。
  • @JimB 我添加了代码作为答案以使其更具体。
  • @AG1:你可以在this answer看到一个更完整的例子
【解决方案3】:

还是个 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/")
}

【讨论】:

    【解决方案4】:

    通过在传输上设置MaxConnsPerHost 选项,我得到了很好的结果...

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

    MaxConnsPerHost 可以选择限制每台主机的连接总数,包括处于拨号、活动和空闲状态的连接。违反限制时,拨号将被阻止。

    https://golang.org/pkg/net/http/#Transport.MaxConnsPerHost

    编辑:澄清一下,这个选项是在 Go 1.11 中发布的,在上面 @AG1 或 @JimB 的回答时不可用,因此我发布了这个。

    【讨论】:

    • 这与@AG1 两年前发布的解决方案基本相同......
    • 不是,仔细阅读我的回答,AG1 使用了MaxIdleConnsPerHost,这对我不起作用,MaxConnsPerHost 是在 Go 1.11(2018 年 11 月发布)中引入的,在 AG1 发布时甚至没有发布他的回答...
    • 抱歉,阅读您的答案有点太快了。尽管如此,感谢您的澄清,一定会帮助未来的读者。
    • 如何以这种方式为每个请求设置不同的代理?有可能吗?
    猜你喜欢
    • 1970-01-01
    • 2011-03-09
    • 2018-12-22
    • 2013-09-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多