2012-04-03 12 views
12

Mam aplikację serwera ASP.NET 3.5 napisaną w języku C#. Tworzy wychodzące żądania do REST API przy użyciu HttpWebRequest i HttpWebResponse.HttpWebResponse nie będzie skalować dla współbieżnych żądań wychodzących

Mam skonfigurować aplikację testową do wysyłania tych żądań w osobnych wątkach (w celu niewyraźnego naśladowania współbieżności z serwerem).

Należy zauważyć, że jest to raczej pytanie typu Mono/Środowisko niż pytanie kodowe; dlatego należy pamiętać, że poniższy kod nie jest dosłowny; tylko cięcie/wklejanie bitów funkcjonalnych.

Oto niektóre 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(); 
    } 
} 

Jeśli uruchomić aplikację na moim komputerze lokalnym rozwoju (Windows 7) w Visual serwerze Studio internetowej, mogę się z numThreads i otrzymać taką samą odpowiedź avg czas z minimalną zmiennością, czy to 1 "użytkownik" czy 100.

Publikowanie i wdrażanie aplikacji do Apache2 w środowisku Mono 2.10.2, czasy odpowiedzi skalują się niemal liniowo. (tj. 1 wątek = 300ms, 5 wątków = 1500ms, 10 wątków = 3000ms). Dzieje się tak bez względu na punkt końcowy serwera (inna nazwa hosta, inna sieć itp.).

Korzystanie z protokołu IPTRAF (i innych narzędzi sieciowych) wygląda tak, jakby aplikacja otwierała tylko 1 lub 2 porty w celu przekierowania wszystkich połączeń, a pozostałe odpowiedzi muszą poczekać.

Zbudowaliśmy podobną aplikację PHP i wdrożono w Mono z tymi samymi żądaniami, a odpowiedzi odpowiednio skalowane.

Przebiegłem wszystkie ustawienia konfiguracyjne, które mogę wymyślić dla Mono i Apache, a ustawienie ONLY, które różni się między tymi dwoma środowiskami (przynajmniej w kodzie), to czasami ServicePoint SupportsPipelining = false w Mono, podczas gdy jest prawdą z mojej maszyny.

Wygląda na to, że ConnectionLimit (domyślnie 2) nie jest zmieniany w Mono z jakiegoś powodu, ale ustawiam go na wyższą wartość zarówno w kodzie, jak i w pliku web.config dla określonego hosta (ów).

Zarówno ja, jak i mój zespół przeoczyliśmy coś znaczącego lub jest to jakiś błąd w Mono.

+1

Znalazłeś rozwiązanie? – Kugel

Odpowiedz

8

Wierzę, że uderzasz w wąskie gardło w HttpWebRequest. Sieć żąda każdego użycia wspólnej infrastruktury punktu obsługi w ramach platformy .NET. Wydaje się, że ma to na celu umożliwienie ponownego użycia żądań tego samego hosta, ale z mojego doświadczenia wynikają dwa wąskie gardła.

Po pierwsze, punkty usługowe zezwalają domyślnie tylko na dwa jednoczesne połączenia z danym hostem, aby zachować zgodność ze specyfikacją HTTP. Można to przesłonić, ustawiając właściwość statyczną ServicePointManager.DefaultConnectionLimit na wyższą wartość. Zobacz tę stronę MSDN, aby uzyskać więcej informacji. Wygląda na to, że już rozwiązałeś ten problem dla pojedynczego punktu usługi, ale ze względu na schemat blokowania współbieżności na poziomie punktu obsługi, może to przyczynić się do powstania wąskiego gardła.

Po drugie, wydaje się, że występuje problem z ziarnistością blokady w samej klasie ServicePoint. Jeśli zdekompilujesz i spojrzysz na źródło słowa kluczowego lock, okaże się, że używa ono samej instancji do synchronizacji i robi to w wielu miejscach. Ponieważ instancja punktu usługi jest współdzielona między żądaniami sieciowymi dla danego hosta, z mojego doświadczenia wynika, że ​​jest to wąskie gardło, ponieważ więcej otwartych jest HttpWebRequests i powoduje, że skaluje się on słabo.Ten drugi punkt to głównie obserwacja osobista i szturchanie wokół źródła, więc weź to z przymrużeniem oka; Nie uważam tego za autorytatywne źródło.

Niestety, nie znalazłem rozsądnego zamiennika w czasie, gdy pracowałem z nim. Po opublikowaniu interfejsu ASP.NET Web API możesz chcieć rzucić okiem na HttpClient. Nadzieja, która pomaga.

+0

Jesse, dzięki za informację. Ustawienie ServicePointManagera DefaultConnectionLimit nadal ustawia go na ServicePoint/punkt końcowy, więc nie wiem, czy pomijałoby to kwestię blokowania opisywaną na poziomie SP. Z pewnością spodziewałbym się, że ServicePoint stanie się wąskim gardłem w miarę wzrostu liczby wątków/żądań, ale nie tak dramatycznie. I nadal wydaje się, że wnioski nie stanowią problemu, ale odpowiedzi czekają. Będę musiał zajrzeć do HttpClient jako alternatywy. – erasend

+0

Re: DefaultConnectionLimit - Nie twierdzę, że będzie działał cuda, ale omija połączenie, aby zablokować (to) w ustawiaczu dla ConnectionLimit w samym ServicePoint. Jeśli chodzi o skalowanie, moje wrażenie było takie samo. Nadal nie mogę twierdzić, że naprawdę rozumiem, dlaczego wąskie gardło uderza tak mocno. Istnieją pewne konstrukcje synchronizacji wokół uzyskania odpowiedzi i ponownie, jeśli otrzymujesz strumienie.W twoim przypadku spekulowałbym, że spadek z lokalnego na nielokalny jest spowodowany dodatkowym opóźnieniem z serwerem zewnętrznym ... ale znowu jest to czysta spekulacja. –

+0

DefaultConnectionLimit niczego nie zmienił (a usunięcie obiektu ConnectionLimit na obiekcie SP przywróciło domyślną wartość 2). Różnica między lokalną a nielokalną jest zdecydowanie związana z Mono/środowiskiem, ponieważ skrypt PHP nie miał problemów - te same żądania. To właśnie próbuję dojść do sedna, zakładając, że Mono zajmuje się tymi reakcjami. – erasend

8

Wiem, że jest dość stary, ale umieszczam to tutaj na wypadek, gdyby pomógł komuś, kto wpadł na ten problem. Wystąpił ten sam problem z równoległymi wyjściowymi żądaniami HTTPS. Jest kilka problemów w grze.

Pierwszy problem polega na tym, że ServicePointManager.DefaultConnectionLimit nie zmienił limitu połączenia, o ile wiem. Ustawienie tego na 50, utworzenie nowego połączenia, a następnie sprawdzenie limitu połączenia w punkcie serwisowym dla nowego połączenia, mówi: 2. Wygląda na to, że ustawienie go w tym punkcie serwisowym na 50 raz działa i trwa dla wszystkich połączeń, które zostaną zakończone. ten punkt usługowy.

Drugim problemem, który napotkaliśmy, było wątkowanie. Obecna implementacja puli wątków wydaje się tworzyć najwyżej 2 nowe wątki na sekundę. Jest to wieczność, jeśli wykonujesz wiele równoległych żądań rozpoczynających się dokładnie w tym samym czasie. Aby temu przeciwdziałać, próbowaliśmy ustawić ThreadPool.SetMinThreads na wyższy numer. Wygląda na to, że Mono tworzy tylko 1 nowy wątek podczas wykonywania tego połączenia, bez względu na różnicę między bieżącą liczbą wątków a żądaną liczbą. Byliśmy w stanie obejść ten problem, wywołując funkcję SetMinThreads w pętli, dopóki pula wątków nie uzyska wymaganej liczby bezczynnych wątków.

Otworzyłem błąd o tej ostatniej kwestii, ponieważ jest to jeden jestem najbardziej pewny siebie nie działa zgodnie z przeznaczeniem: https://bugzilla.xamarin.com/show_bug.cgi?id=7055

+0

Potwierdzić, że 'ServicePointManager.DefaultConnectionLimit' nie ma wpływu na Mono, limit jest nadal 2 – KCD

+0

Ustawienie w [' app.config' lub 'web.config'] (http://stackoverflow.com/a/436477/516748) działa ... jednak okazuje się być nieproduktywne w Mono z czymkolwiek innym niż 2 zmniejszenie przepustowości masowo (?!). Również ServicePoint.Connections zawsze zgłasza 0, podczas gdy Windows raportuje do i tuż ponad ConnectionLimit ... – KCD

0

Jeśli @ jake-moshenko ma rację ServicePointManager.DefaultConnectionLimit nie mający żadnego wpływu, jeśli zmienił się w Mono, proszę zgłoś to jako błąd w http://bugzilla.xamarin.com/.

jednak chciałbym spróbować kilka rzeczy przed wyrzuceniem to całkowicie za kwestię Mono:

  1. Spróbuj użyć garbage collector SGen zamiast starego Boehm jednym, przekazując --gc=sgen jako flaga do mono.
  2. Jeśli powyższe nie pomoże, uaktualnij do Mono 3.2 (które BTW domyślnie również do SGEN GC), ponieważ od czasu zadawania pytania było wiele poprawek.
  3. Jeśli powyższe nie pomoże, należy zbudować własny Mono (gałąź master), ponieważ ostatnio zostało scalone o wątkach.
  4. Jeśli powyższe nie pomoże, należy zbudować własny Mono z dodanym this pull request. Jeśli to rozwiąże problem, dodaj "+1" do żądania wyciągnięcia. Może to być poprawka dla bug 7055.
Powiązane problemy