【问题标题】:How do I prevent Socket/Port Exhaustion?如何防止套接字/端口耗尽?
【发布时间】:2012-07-19 21:16:47
【问题描述】:

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

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

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

我正在尝试以每个线程 500 次迭代运行 100 个线程。

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

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

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

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

问题:

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

代码:

包装 HttpRequest 的类

编辑: 在 using 中包装了 WebClient,因此每次迭代都会实例化、使用和处置一个新的 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();
}

【问题讨论】:

  • 要考虑的一件事是不要使用“从消防水带中喝水”的方法进行测试。您应该慢慢开始并将请求/秒增加到一个固定的最大值,以正确测试您的系统。然后,您可以在多次运行中增加最大值,直到找到极限。无限的网络请求不会告诉你什么。
  • 请记住,只有大约 65000 个可用端口,并非所有端口都可用于传出连接。因此,您需要使用多个 IP/NIC 来执行您尝试执行的 50000 个连接
  • @StefanH 我知道如果我正在执行大量线程,这将是一个问题,但是,一旦循环的迭代完成,我就不再需要套接字了,但它仍然存在,导致下一次迭代打开一个新的。我正在寻找一种方法来防止这种情况发生
  • 您正在使用正在返回的流,但不是您的 Web 客户端。您也可以在 WebClient 上尝试使用 using 语句,以便处理它。或在阅读完毕后手动处理。

标签: c# multithreading sockets tcp


【解决方案1】:

您了解 TIME_WAIT 的用途吗?在此期间,重用端口是不安全的,因为之前事务中丢失的数据包(已成功重新传输)可能在该时间段内被传递。

您可能可以在注册表中的某个地方对其进行调整,但我怀疑这是否是一个明智的下一步。

事实证明,我在测试环境中创建真实负载的经验非常令人沮丧。当然,从 localhost 运行负载测试器绝不是现实的,而且我使用 .net http api 进行的大多数网络测试似乎比服务器本身需要更多的客户端。

因此,最好转移到第二台机器上以在您的服务器上产生负载......但是国内路由设备很少能够支持任何接近会导致任何类型负载的连接数量的工作编写良好的服务器应用程序,所以现在您还需要升级您的路由/交换设备!

最后,我在 .net Http 客户端 API 上遇到了一些非常奇怪和意想不到的性能问题。归根结底,他们都使用 HttpWebRequest 来完成繁重的工作。 IMO 它远没有它可以达到的性能。 DNS 是同步的,即使在异步调用 API 时(尽管如果您只从单个主机请求,这不是问题),并且在持续使用 CPU 使用率之后,直到客户端成为 CPU 受限而不是 IO 受限。如果您希望生成持续的高负载,那么任何依赖 HttpWebRequest 的请求繁重的应用程序都是 IMO 的虚假投资。

总而言之,这是一项相当棘手的工作,而且最终只能在野外证明,除非你有大量现金可以花在拥有更好设备的舰队上。

[提示:我使用异步 Socket api 和 3rd 方 DNS 客户端库编写的自己的客户端获得了更好的性能]

【讨论】:

  • 我很欣赏这篇文章。我将尝试单独的硬件/办公资源分配路径,但我也想尝试实施不需要额外资源的技术解决方案。你用的是什么库?其他技术(我认为是 C++)“更好”吗?
  • 好吧,DNS 对您来说不是问题,但您可能会觉得这很有趣:stackoverflow.com/questions/11480742/…。我发现使用 .net Socket api 聊天 HTTP 可以获得更好的性能。我自己写了一个小库(用于爬虫),但不能分享,因为它属于我的公司,而且它只实现了非常有限的 HTTP 子集。然而,它确实使 .net 在执行 HTTP 时确实非常快,在几乎空闲时达到了 IO 限制。
【解决方案2】:

问:如何显式关闭套接字...以防止 TIME_WAIT 状态?

答:伙计,TIME_WAIT 是不可或缺的——而且很重要! - TCP/IP 本身的一部分!

可以调整操作系统以减少 TIME_WAIT(这可能会产生负面影响)。

您可以调整操作系统以增加 #/ephemeral 端口:

这里有一个链接,说明为什么 TIME_WAIT 存在......以及为什么它是一件好事:

【讨论】:

    【解决方案3】:

    这不是关闭套接字或释放应用程序中的资源的问题。 TIME _WAIT 是已释放套接字上的 TCP 堆栈超时,以防止它们重新使用,直到几乎不可能从先前连接到该套接字的任何数据包“剩余”未过期。

    出于测试目的,您可以将等待时间从默认值(几分钟,AFAIK)减少到更小的值。在负载测试服务器时,我将其设置为 6 秒。

    它在注册表中的某个地方 - 如果您使用 Google 搜索,您会找到它。

    找到了:

    Change TIME_WAIT delay

    【讨论】:

      【解决方案4】:

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

      要么将您的 WebClient 实例包装在 using 块中,要么在您完成从 URL 读取后对其手动调用 dispose。

      试试这个:

      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 ) ) 
                  {
      
                  }
              }
          }
      }
      

      【讨论】:

      • 我最初将它“展开”,因为我认为它只会在其生命周期内创建 1 个套接字。我编辑了我的代码,(正如我上面的编辑所反映的那样)但问题仍然存在。
      • @James 我不确定那是什么,抱歉我无法提供更多帮助。
      【解决方案5】:

      你不需要弄乱 TIME_WAIT 来完成你想要的。

      问题是您每次调用 Execute() 时都在处理 WebClient。当你这样做时,你关闭了与服务器的套接字连接,并且 TCP 端口在 TIME_WAIT 期间保持忙碌。

      更好的方法是在 HttpGetTest 类的构造函数中创建 WebClient,并在整个测试过程中重用相同的对象。

      WebClient 默认使用保持活动状态,并将对其所有请求重用相同的连接,因此在您的情况下,只有 100 个打开的连接。

      【讨论】:

      • 1.这就是问题代码的原始版本所做的(并且问题仍然在谈论“我假设 WebClient (每个线程实例化一次)只会使用一个套接字。” 2. Keep-alive 仅在您使用时重用连接与同一主机名建立第二个连接。问题是关于在关闭连接后重用客户端套接字。
      • 嗯...问题已编辑,我没有看到原始代码。 1. 是的,正如我所说,这是正确的。 2. 是的,同一个主机,怎么可能呢?此外,他并没有说有很多主机,即使它是一个 webfarm,100 个线程也可能足以访问所有服务器(假设它远少于 100 个)。可能还有其他事情发生,如果您重用它,WebClient 应该会正确运行。问题是“如何防止 Socket/Port Exhaustion?”:正确使用 WebClient(或者可能是 HttpClient)将防止端口耗尽。
      猜你喜欢
      • 2022-01-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多