17

我正在尝试通过跨多个线程的请求来对网站进行性能测试。每个线程执行n次。(在 for 循环中)

但是,我遇到了问题。特别是带有内部异常的 WebException(“无法连接到远程服务器”):

无法对套接字执行操作,因为系统缺少足够的缓冲区空间或队列已满 127.0.0.1:52395

我试图以每个线程 500 次迭代运行 100 个线程。

最初我HttpWebRequest在 System.Net 中使用向服务器发出 GET 请求。目前我正在使用WebClient,因为我假设每次迭代都使用一个新的套接字(所以在短时间内 100 * 500 个套接字)。我假设 WebClient (每个线程实例化一次)只会使用一个套接字。

我不需要一次打开 50 000 个套接字,因为我想发送 GET 请求、接收响应并关闭套接字,将其释放以供下一次循环迭代使用。我明白这将是一个问题

但是,即使使用 WebClient,也会请求一堆套接字,从而导致一堆套接字处于TIME_WAIT模式(使用 netstat 检查)。这会导致其他应用程序(如 Internet 浏览器)挂起并停止运行。

我可以用更少的迭代和/或更少的线程来运行我的测试,因为看起来套接字最终会退出这个 TIME_WAIT 状态。但是,这不是一个解决方案,因为它没有充分测试 Web 服务器的能力。

问题:

如何在每次线程迭代后显式关闭套接字(从客户端)以防止 TIME_WAIT 状态和套接字耗尽?

代码:

包装 HttpRequest 的类

编辑:在使用中包装了 WebClient,因此每次迭代都会实例化、使用和处置一个新的。问题仍然存在。

  public sealed class HttpGetTest : ITest {
    private readonly string m_url;

    public HttpGetTest( string url ) {          
        m_url = url;
    }

    void ITest.Execute() {
        using (WebClient webClient = new WebClient()){
            using( Stream stream = webClient.OpenRead( m_url ) ) {          
            }
        }
    }
}

我的 ThreadWrapperClass 中创建新线程的部分:

public void Execute() {
    Action Hammer = () => {
        for( int i = 1; i <= m_iterations; i++ ) {
            //Where m_test is an ITest injected through constructor
            m_test.Execute();
        }       
    };
    ThreadStart work = delegate {
        Hammer();
    };
    Thread thread = new Thread( work );
    thread.Start();
}
4

5 回答 5

14

Do you understand the purpose of TIME_WAIT? It's a period during which it would be unsafe to reuse the port because lost packets (that have been successfully retransmitted) from the previous transaction might yet be delivered within that time period.

You could probably tweak it down in the registry somewhere, but I question if this is a sensible next step.

My experience of creating realistic load in a test environment have proved very frustrating. Certainly running your load-tester from localhost is by no means realistic, and most network tests I have made using the .net http apis seem to require more grunt in the client than the server itself.

As such, it's better to move to a second machine for generating load on your server... however domestic routing equipment is rarely up to the job of supporting anywhere near the number of connections that would cause any sort of load on a well written server app, so now you need to upgrade your routing/switching equipment as well!

Lastly, I've had some really strange and unexpected performance issues around the .net Http client API. At the end of the day, they all use HttpWebRequest to do the heavy lifting. IMO it's nowhere near as performant as it could be. DNS is sychronous, even when calling the APIs asynchronously (although if you're only requesting from a single host, this isn't an issue), and after sustained usage CPU usage creeps up until the client becomes CPU constrained rather than IO constrained. If you're looking to generate sustained and heavy load, any request-heavy app reliant on HttpWebRequest is IMO a bogus investment.

All in all, a pretty tricky job, and ultimately, something that can only be proved in the wild, unless you've got plently of cash to spend on an armada of better equipment.

[Hint: I got much better perfomance from my own client written using async Socket apis and a 3rd party DNS client library]

于 2012-07-19T21:48:01.727 回答
3

Q: How do I explicitly close a socket ... in order to prevent TIME_WAIT states?

A: Dude, TIME_WAIT is an integral - and important! - part of TCP/IP itself!

You can tune the OS to reduce TIME_WAIT (which can have negative repercussions).

And you can tune the OS to increase #/ephemeral ports:

Here's a link on why TIME_WAIT exists ... and why it's a Good Thing:

于 2012-07-19T22:15:27.237 回答
2

It's not an issue of closing sockets or releasing resources in your app. The TIME _WAIT is a TCP stack timeot on released sockets to prevent their re-use until such time as it is virtually impossible for any packets 'left over' from a previous connection to that socket to not have expired.

For test purposes, you can reduce the wait time from the default, (some minutes, AFAIK), to a smaller value. When load-testing servers, I set it at six seconds.

It's in the registry somewhere - you'll find it if you Google.

Found it:

Change TIME_WAIT delay

于 2012-07-19T21:33:30.150 回答
1

看起来您并没有强迫您的 WebClient 摆脱它分配的资源。您正在对返回的流执行 Using,但您的 WebClient 仍然有资源。

Either wrap your WebClient instantiation in a using block, or manually call dispose on it once you are done reading from the URL.

Try this:

public sealed class HttpGetTest : ITest {
    private readonly string m_url;

    public HttpGetTest( string url ) {
        m_url = url;        
    }

    public void ITest.Execute() {
        using( var m_webClient = new WebClient())
        {
            using( Stream stream = m_webClient.OpenRead( m_url ) ) 
            {

            }
        }
    }
}
于 2012-07-19T21:29:27.063 回答
0

You don't need to mess around with TIME_WAIT to accomplish what you want.

The problem is that you are disposing the WebClient every time you call Execute(). When you do that, you close the socket connection with the server and the TCP port keeps busy for the TIME_WAIT period.

A better approach is to create the WebClient in the constructor of your HttpGetTest class and reuse the same object throughout the test.

WebClient uses keep alive by default and will reuse the same connection for all its requests so in your case there will be only 100 opened connections for this.

于 2016-12-27T16:40:22.447 回答