12

我有一个用 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)上运行应用程序,我可以增加 numThreads 并接收相同的平均响应时间,而无论是 1 个“用户”还是 100 个,变化最小。

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

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

我们已经构建了一个类似的 PHP 应用程序并部署在 Mono 中,具有相同的请求并且响应可以适当地扩展。

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

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

要么我和我的团队忽略了一些重要的事情,要么这是 Mono 中的某种错误。

4

3 回答 3

9

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

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

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

不幸的是,我在使用它时没有找到合理的替代品。现在 ASP.NET Web API 已经发布,您不妨看看HttpClient。希望有帮助。

于 2012-04-03T18:10:10.617 回答
8

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

第一个问题是ServicePointManager.DefaultConnectionLimit据我所知并没有改变连接限制。将此设置为 50,创建一个新连接,然后检查新连接的服务点上的连接限制说 2. 在该服务点上将其设置为 50 一次似乎可以正常工作,并且对于最终将通过的所有连接都保持不变那个服务点。

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

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

于 2012-09-11T14:16:03.877 回答
0

如果@jake-moshenko 认为ServicePointManager.DefaultConnectionLimit在 Mono 中更改没有任何影响是正确的,请将其作为错误提交到http://bugzilla.xamarin.com/

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

  1. 尝试使用 SGen 垃圾收集器而不是旧的 bo​​ehm 垃圾收集器,将--gc=sgen其作为标志传递给 mono。
  2. 如果上述方法没有帮助,请升级到 Mono 3.2(顺便说一句,它也默认为 SGEN GC),因为自从您提出问题以来已经进行了很多修复。
  3. 如果上述方法没有帮助,请构建您自己的 Mono(主分支),因为这个关于线程的重要拉取请求最近已被合并。
  4. If the above doesn't help, build your own Mono with this pull request added. If it fixes your problem, please add a "+1" to the pull request. It might be a fix for bug 7055.
于 2013-07-31T22:23:12.867 回答