HttpWebResponse 无法针对并发出站请求进行扩展



我有一个用C#编写的ASP.NET 3.5服务器应用程序。它使用HttpWebRequest和HttpWebResponse向REST API发出出站请求。

我已经设置了一个测试应用程序,以便在单独的线程上发送这些请求(以模糊地模拟针对服务器的并发)。

请注意,这更像是一个Mono/Environment问题,而不是一个代码问题;所以请记住,下面的代码不是逐字逐句的;只是功能位的剪切/粘贴。

这里有一些伪代码:

// threaded client piece
int numThreads = 1;
ManualResetEvent doneEvent;
using (doneEvent = new ManualResetEvent(false))
        {
            for (int i = 0; i < numThreads; i++)
            {
                ThreadPool.QueueUserWorkItem(new WaitCallback(Test), random_url_to_same_host);
            }
            doneEvent.WaitOne();
        }
void Test(object some_url)
{
    // setup service point here just to show what config settings Im using
    ServicePoint lgsp = ServicePointManager.FindServicePoint(new Uri(some_url.ToString()));
        // set these to optimal for MONO and .NET
        lgsp.Expect100Continue = false;
        lgsp.ConnectionLimit = 100;
        lgsp.UseNagleAlgorithm = true;
        lgsp.MaxIdleTime = 100000;        
    _request = (HttpWebRequest)WebRequest.Create(some_url);

    using (HttpWebResponse _response = (HttpWebResponse)_request.GetResponse())
    {
      // do stuff
    } // releases the response object
    // close out threading stuff
    if (Interlocked.Decrement(ref numThreads) == 0)
    {
        doneEvent.Set();
    }
}

如果我在Visual Studio web服务器的本地开发机器(Windows 7)上运行该应用程序,无论是1"用户"还是100,我都可以上调numThreads并接收相同的平均响应时间。

在Mono 2.10.2环境中将应用程序发布并部署到Apache2上,响应时间几乎是线性的。(即,1个线程=300ms,5个线程=1500ms,10个线程=3000ms)。无论服务器端点(不同的主机名、不同的网络等)如何,都会发生这种情况。

使用IPTRAF(和其他网络工具),应用程序似乎只打开1或2个端口来路由所有连接,其余的响应必须等待。

我们已经构建了一个类似的PHP应用程序,并在Mono中部署了相同的请求和相应的响应。

我已经运行了我能想到的Mono和Apache的每一个配置设置,这两个环境之间唯一不同的设置(至少在代码中)是,有时Mono中的ServicePoint SupportsPipelining=false,而在我的机器上是true。

由于某种原因,Mono中的ConnectionLimit(默认值2)似乎没有更改,但我在代码和指定主机的web.config中都将其设置为更高的值。

要么我和我的团队忽略了一些重要的东西,要么这是Mono中的某种bug。

我相信您在HttpWebRequest中遇到了瓶颈。每个web请求都使用.NET框架中的公共服务点基础结构。这似乎是为了允许重用对同一主机的请求,但根据我的经验,这会导致两个瓶颈。

首先,为了符合HTTP规范,服务点默认情况下只允许到给定主机的两个并发连接。这可以通过将静态属性ServicePointManager.DefaultConnectionLimit设置为更高的值来覆盖。有关详细信息,请参阅此MSDN页面。看起来您已经为单个服务点本身解决了这个问题,但由于服务点级别的并发锁定方案,这样做可能会导致瓶颈。

其次,ServicePoint类本身的锁粒度似乎存在问题。如果您反编译并查看lock关键字的源代码,您会发现它使用实例本身进行同步,并且在许多地方都是这样做的。在给定主机的web请求之间共享服务点实例的情况下,根据我的经验,随着打开更多的HttpWebRequests,这往往会成为瓶颈,并导致其扩展性较差。第二点主要是个人观察和探源,所以对此持怀疑态度;我不认为这是一个权威的消息来源。

不幸的是,在我使用它的时候,我没有找到一个合理的替代品。现在ASP.NETWebneneneba API已经发布,你可能希望看看HttpClient。希望能有所帮助。

我知道这很旧,但我把它放在这里,以防它可能会帮助其他遇到这个问题的人。并行出站HTTPS请求也遇到了同样的问题。有几个问题在起作用。

第一个问题是,据我所知,ServicePointManager.DefaultConnectionLimit没有更改连接限制。将此值设置为50,创建一个新连接,然后检查服务点上新连接的连接限制为2。一旦在该服务点上将其设置为50,对于最终将通过该服务点的所有连接似乎都有效并持久。

我们遇到的第二个问题是线程。单线程池的当前实现似乎每秒最多创建2个新线程。如果你正在做许多在同一时间开始的并行请求,这将是永恒的。为了抵消这种情况,我们尝试将ThreadPool.SetMinThreads设置为更高的数字。当您进行此调用时,Mono似乎最多只创建1个新线程,而不管当前线程数和所需线程数之间的增量如何。我们可以通过在循环中调用SetMinThreads来解决这个问题,直到线程池具有所需数量的空闲线程。

我打开了一个关于后一个问题的bug,因为这是我最确信没有按预期工作的问题:https://bugzilla.xamarin.com/show_bug.cgi?id=7055

如果@jake-moshenko关于ServicePointManager.DefaultConnectionLimit在Mono中更改后没有任何效果的说法是正确的,请将其作为错误提交http://bugzilla.xamarin.com/.

然而,在将其作为Mono问题完全丢弃之前,我会尝试一些事情:

  1. 通过将--gc=sgen作为标志传递给mono,尝试使用SGen垃圾收集器而不是旧的boehm垃圾收集器
  2. 如果以上内容没有帮助,请升级到Mono 3.2(BTW也默认为SGEN GC),因为自从您提出这个问题以来,已经进行了很多修复
  3. 如果以上内容没有帮助,请构建自己的Mono(主分支),因为这个关于线程的重要pull请求最近已经合并
  4. 如果以上内容没有帮助,请添加此pull请求来构建您自己的Mono。如果它解决了您的问题,请在拉取请求中添加"+1"。这可能是对错误7055的修复

最新更新