多个http请求会出现“无法分配请求的地址”的错误,除非加快速度。

huangapple go评论148阅读模式
英文:

multiple http.Requests gives "can't assign requested address," unless sped up

问题

使用以下客户端代码(以及在此服务器上的8088端口上的侦听Web服务器),在此错误从client.Get()弹出之前,我很少能够获得超过23000次点击:

panic: Get http://localhost:8088/: dial tcp 127.0.0.1:8088: can't assign requested address

奇怪的是,如果我增加计时器延迟(即从毫秒增加到微秒),需要更多的点击才会出现错误,例如170,000次甚至更多。

查看网络流量,每个客户端连接仅使用几次后就会断开连接(即客户端发送FIN)。因此,显然它正在创建许多TCP连接并溢出套接字表。鉴于Golang HTTP文档中默认启用了keepalive,我不希望出现这种情况。内核跟踪显示在关闭之前底层套接字没有发出错误(除了预期的EAGAIN,并不总是在套接字关闭之前发生)。

这是在OSX(14.4.0)上使用Go 1.4.2。为什么客户端连接在整个过程中没有被重用?

package main

import (
	"io/ioutil"
	"net/http"
	"runtime"
	"sync"
	"time"
)

var reqnum = 0

func hit(client *http.Client) {
	resp, err := client.Get("http://localhost:8088/")
	if err != nil {
		println(reqnum)
		panic(err)
	}
	defer resp.Body.Close()
	_, err = ioutil.ReadAll(resp.Body)
	if err != nil {
		panic(err)
	}
	reqnum++ // not thread safe, but shouldn't cause errors.
}

func main() {
	var wg sync.WaitGroup
	runtime.GOMAXPROCS(runtime.NumCPU())
	client := &http.Client{}
	for i := 0; i < 10; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			ticker := time.NewTicker(time.Microsecond * 1000)
			for j := 0; j < 120000; j++ {
				<-ticker.C
				hit(client)
			}
			ticker.Stop()
		}()
	}
	wg.Wait()
}
英文:

With the client code below (and a listening web server on port 8088 on this box), I am rarely able to get more than 23000 hits before this error pops up from the client.Get():

panic: Get http://localhost:8088/: dial tcp 127.0.0.1:8088: can&#39;t assign requested address

Oddly, if I increase the timer delay (i.e. from a millisecond to a microsecond) it takes far more hits to get the error, 170,000 or even more.

Looking at the network traffic, each client connection is used only a handful of times before it disconnects (i.e. the client side sends a FIN). So clearly it's making many TCP connections and overflowing the socket table. Given that the Golang HTTP docs say that keepalives are enabled by default, I would't expect this. A kernel trace shows no errors being emitted by the underlying socket before the close (other than EAGAIN, which is expected and doesn't always precede a socket close).

This is with Go 1.4.2 on OSX (14.4.0). Why are the client connections not being reused the whole time?

package main

import (
	&quot;io/ioutil&quot;
	&quot;net/http&quot;
	&quot;runtime&quot;
	&quot;sync&quot;
	&quot;time&quot;
)

var reqnum = 0

func hit(client *http.Client) {
	resp, err := client.Get(&quot;http://localhost:8088/&quot;)
	if err != nil {
		println(reqnum)
		panic(err)
	}
	defer resp.Body.Close()
	_, err = ioutil.ReadAll(resp.Body)
	if err != nil {
		panic(err)
	}
	reqnum++ // not thread safe, but shouldn&#39;t cause errors.
}

func main() {
	var wg sync.WaitGroup
	runtime.GOMAXPROCS(runtime.NumCPU())
	client := &amp;http.Client{}
	for i := 0; i &lt; 10; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			ticker := time.NewTicker(time.Microsecond * 1000)
			for j := 0; j &lt; 120000; j++ {
				&lt;-ticker.C
				hit(client)
			}
			ticker.Stop()
		}()
	}
	wg.Wait()
}

答案1

得分: 9

在进行拨号时出现的错误无法分配请求的地址是由于本地临时端口用于客户端连接的数量不足导致的。你之所以用光了端口,只是因为你建立连接的速度太快了。当你加快连接速度时,你开始捕获那些在关闭之前返回到连接池中的空闲连接。在进行拨号时,有一条代码路径会捕获这些新的空闲连接,以便更快地返回一个连接,但无法确定每次都能捕获到这些连接。

根据你在评论中提到的只连接一个主机,你需要做的是将Transport.MaxIdleConnsPerHost设置得更高一些。你需要找到一个平衡点,既不会打开太多的连接,又不会过快地回收它们。

甚至可能有利于在客户端上设置一个信号量,以防止太多的并发连接,这样会导致连接再次过快地回收。

英文:

The error can&#39;t assign requested address during a Dial is caused by running out of local ephemeral ports to use for the client connection. The reason you're running out of ports, is simply because you're making too many connections, too fast. What happens when you speed up the connection rate, is that you start to catch the idle connections going back into the pool before they are closed. There's a code path that catches these newly idle connections during a Dial to return a connection more quickly, but there's no way to deterministically catch these connections every time.

What you need to do since you're connecting to only one host (as discussed in the comments), is to set the Transport.MaxIdleConnsPerHost a lot higher. You'll need to see where it balances out, between too many open connections, and when you start recycling them too quickly.

It may even be advantageous to have a semephore on client to prevent too many simultaneous connections, which would start causing the connections to again recycle too quickly.

huangapple
  • 本文由 发表于 2015年8月4日 12:26:10
  • 转载请务必保留本文链接:https://go.coder-hub.com/31800692.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定