2012-04-03 6 views
12

Ich habe eine ASP.NET 3.5-Server-Anwendung in C# geschrieben. Ausgehende Anforderungen an eine REST-API werden mit HttpWebRequest und HttpWebResponse erstellt.HttpWebResponse wird nicht für gleichzeitige ausgehende Anfragen skalieren

Ich habe eine Testanwendung eingerichtet, um diese Anforderungen an separate Threads zu senden (um die Parallelität des Servers vage nachzuahmen).

Bitte beachten Sie, dass dies eher eine Mono/Environment Frage als eine Code Frage ist; Bitte beachten Sie, dass der folgende Code nicht wortgetreu ist; nur ein Ausschneiden/Einfügen der Funktionsbits. Hier

ist einige Pseudo-Code:

// 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(); 
    } 
} 

Wenn ich die Anwendung auf meinem lokalen Entwicklungsmaschine (Windows 7) in der Visual Studio-Web-Server laufen, kann ich die numThreads und erhalten die gleiche avg Antwort Zeit mit minimaler Variation ob es 1 "Benutzer" oder 100 ist.

Veröffentlichen und Bereitstellen der Anwendung auf Apache2 in einer Mono 2.10.2-Umgebung skalieren die Antwortzeiten fast linear. (1 Thread = 300 ms, 5 Thread = 1500 ms, 10 Threads = 3000 ms). Dies geschieht unabhängig vom Serverendpunkt (unterschiedlicher Hostname, anderes Netzwerk usw.).

Mit IPTRAF (und anderen Netzwerk-Tools) scheint es, als ob die Anwendung nur 1 oder 2 Ports öffnet, um alle Verbindungen zu routen und die verbleibenden Antworten müssen warten.

Wir haben eine ähnliche PHP-Anwendung erstellt und in Mono mit den gleichen Anforderungen und den entsprechenden Antworten implementiert.

Ich habe jede einzelne Konfigurationseinstellung durchlaufen, die ich für Mono und Apache kennengelernt habe und die EINZIGE Einstellung, die zwischen den beiden Umgebungen (zumindest im Code) unterschiedlich ist, ist manchmal, dass der ServicePoint SupportsPipelining = false in Mono ist ist wahr von meiner Maschine.

Es scheint, als ob die ConnectionLimit (Standard 2) in Mono aus irgendeinem Grund nicht geändert wird, aber ich setze es auf einen höheren Wert in Code und der web.config für den/die angegebenen Host (s).

Entweder ich und mein Team übersehen etwas Bedeutendes oder dies ist eine Art Bug in Mono.

+1

Haben Sie eine Lösung gefunden? – Kugel

Antwort

8

Ich glaube, dass Sie einen Engpass in der HttpWebRequest treffen. Die Webanforderungen verwenden jeweils eine gemeinsame Service Point-Infrastruktur innerhalb des .NET-Frameworks. Dies scheint zu ermöglichen, dass Anfragen an den gleichen Host wiederverwendet werden, führt aber nach meiner Erfahrung zu zwei Engpässen.

Zunächst erlauben die Dienstpunkte standardmäßig nur zwei gleichzeitige Verbindungen zu einem bestimmten Host, um die HTTP-Spezifikation zu erfüllen. Dies kann außer Kraft gesetzt werden, indem die statische Eigenschaft ServicePointManager.DefaultConnectionLimit auf einen höheren Wert gesetzt wird. Weitere Informationen finden Sie auf der Seite MSDN. Es sieht so aus, als ob Sie dies bereits für den einzelnen Dienstpunkt selbst angehen, aber aufgrund des Sperrschemas für den gemeinsamen Zugriff auf der Dienstpunktebene kann dies zu dem Engpass beitragen.

Zweitens scheint ein Problem mit Sperrgranularität in der ServicePoint Klasse selbst zu sein. Wenn Sie die Quelle für das Schlüsselwort lock dekompilieren und untersuchen, werden Sie feststellen, dass sie die Instanz selbst zum Synchronisieren verwendet und dies an vielen Stellen tut. Da die Service-Point-Instanz unter Webanfragen für einen bestimmten Host geteilt wird, tendiert dies nach meiner Erfahrung zu Engpässen, da mehr HttpWebRequests geöffnet werden und es schlecht skaliert.Dieser zweite Punkt ist hauptsächlich persönliche Beobachtung und stochern um die Quelle, also nehmen Sie es mit einem Körnchen Salz; Ich würde es nicht als maßgebliche Quelle ansehen.

Leider fand ich zu der Zeit, als ich damit arbeitete, keinen vernünftigen Ersatz. Jetzt, da die ASP.NET-Web-API veröffentlicht wurde, möchten Sie vielleicht die HttpClient einen Blick geben. Ich hoffe, das hilft.

+0

Jesse, danke für die Info. Das Setzen von ServicePointManager DefaultConnectionLimit setzt es immer noch einfach per ServicePoint/Endpunkt, so dass ich nicht weiß, ob das Sperrproblem umgehen würde, das Sie auf der SP-Ebene beschreiben. Ich würde sicherlich erwarten, dass ServicePoint einen Engpass hat, wenn die Threads/Requests wachsen, aber nicht so dramatisch. Und es scheint immer noch so, als würden die Anfragen ausgehen, kein Problem, aber die Antworten warten. Ich werde als Alternative in HttpClient schauen müssen. – erasend

+0

Re: DefaultConnectionLimit - Ich behaupte nicht, dass es Wunder wirkt, aber es umgeht einen Aufruf, um (dies) in der Setter für ConnectionLimit in ServicePoint selbst zu sperren. Was die Skalierung betrifft, war mein Eindruck derselbe. Ich kann immer noch nicht behaupten, wirklich zu verstehen, warum der Flaschenhals so hart trifft. Es gibt einige Synchronisierungskonstrukte um die Antwort zu erhalten und wenn die Streams abgerufen werden.In Ihrem Fall würde ich spekulieren, dass der Rückgang von lokal auf nicht-lokal aufgrund der zusätzlichen Latenz mit einem externen Server ... aber auch das ist reine Spekulation. –

+0

DefaultConnectionLimit hat nichts geändert (und das Entfernen des ConnectionLimit auf dem SP-Objekt hat es auf den Standardwert 2 zurückgesetzt). Der Unterschied von local zu non-local ist eindeutig Mono/Umgebung, da das PHP-Skript keine Probleme hatte - gleiche Anfragen. Das ist es, was Im versucht zu untermauern, unter der Annahme, dass es in der Art ist, wie Mono mit diesen Antworten umgeht. – erasend

8

Ich weiß, das ist ziemlich alt, aber ich stelle das hier für den Fall, dass es jemand anderen helfen könnte, der auf dieses Problem stößt. Wir haben das gleiche Problem mit parallelen ausgehenden HTTPS-Anfragen. Es gibt ein paar Probleme im Spiel.

Das erste Problem ist, dass ServicePointManager.DefaultConnectionLimit das Verbindungslimit nicht geändert hat, soweit ich das beurteilen kann. Wenn Sie dies auf 50 setzen, eine neue Verbindung erstellen und dann das Verbindungslimit des Servicepunkts für die neue Verbindung prüfen, wird angezeigt, dass die Einstellung für diesen Servicepunkt auf 50 einmal funktioniert und für alle Verbindungen gilt, die am Ende ausgeführt werden dieser Servicepunkt.

Das zweite Problem, das wir bekamen, war mit Threading. Die aktuelle Implementierung des Mono-Thread-Pools scheint maximal zwei neue Threads pro Sekunde zu erstellen. Dies ist eine Ewigkeit, wenn Sie viele parallele Anfragen gleichzeitig starten. Um dem entgegenzuwirken, haben wir versucht, ThreadPool.SetMinThreads auf eine höhere Zahl zu setzen. Es scheint, dass Mono nur bis zu 1 neuen Thread erstellt, wenn Sie diesen Aufruf ausführen, unabhängig vom Delta zwischen der aktuellen Anzahl der Threads und der gewünschten Anzahl. Wir konnten dies umgehen, indem wir SetMinThreads in einer Schleife aufrufen, bis der Thread-Pool die gewünschte Anzahl von Leerlauf-Threads hatte.

ich einen Fehler über die letztgenannte Frage geöffnet, weil das das ist, ich bin sehr zuversichtlich, nicht wie beabsichtigt: https://bugzilla.xamarin.com/show_bug.cgi?id=7055

+0

bestätigen 'ServicePointManager.DefaultConnectionLimit' hat keinen Effekt in Mono, Grenze noch 2 – KCD

+0

es in Einstellung [' app.config' oder 'web.config'] (http://stackoverflow.com/a/436477/516748) funktioniert ... aber erweist sich in Mono als kontraproduktiv mit etwas anderem, als den Durchsatz massiv zu reduzieren (?!). Auch ServicePoint.Connections meldet immer 0, während Windows bis zu und gerade über ConnectionLimit berichtet ... – KCD

0

Wenn @ jake-moshenko Recht ServicePointManager.DefaultConnectionLimit keine Wirkung, wenn in Mono geändert, Bitte füge das als Fehler in http://bugzilla.xamarin.com/ ein.

Allerdings würde ich einige Dinge versuchen, bevor diese vollständig als Mono Ausgabe Verwerfen:

  1. Versuchen Sie es mit den SGen Garbage Collector anstelle des alten Boehm ein, durch --gc=sgen als Flag vorbei auf Mono.
  2. Wenn das Obige nicht hilft, upgraden Sie auf Mono 3.2 (welches BTW auch auf SGEN GC voreingestellt ist), da es viele Fehler gab, seit Sie die Frage gestellt haben.
  3. Wenn das obige nicht hilft, erstellen Sie Ihr eigenes Mono (Master-Zweig), da this important pull request über Threading kürzlich zusammengeführt wurde. Wenn das obige nicht hilft, bauen Sie Ihr eigenes Mono mit this pull request hinzugefügt. Wenn es Ihr Problem behebt, fügen Sie der Pull-Anforderung eine "+1" hinzu. Es könnte eine Lösung für bug 7055 sein.