sobota, 4 lutego 2012

Nieblokujące serwery sterowane zdarzeniami cz. 4: Zastosowanie

Zgodnie z obietnicą w tej części cyklu opiszę jakiego typu aplikacje mogą skorzystać z dobrodziejstw podejścia asynchronicznego sterowanego zdarzeniami. W poprzedniej części napisałem, że aby poczuć różnicę między podejściem blokującym a nieblokującym to ilość równoległe obsługiwanych żądań musi być liczona w tysiącach. W sytuacji wykorzystania liczby wątków odpowiadającej tak dużej liczbie żądań może się okazać, że procesor przez większość czasu będzie wykorzystywany do operacji context switch, a pamięć RAM do przechowywania stosu wątków. Może zabraknąć więc mocy obliczeniowej na wykonywanie algorytmów i pamięci RAM na przechowywanie danych. Jakie aplikacje mają jednak takie wymagania? O tym poniżej.

Pierwszym najbardziej rozpowszechnionym zastosowaniem serwerów asynchronicznych sterowanych zdarzeniami są serwery serwujące statyczne pliki z dysku, czyli np. serwery www takie jak Nginx. Ich głównym zadaniem jest wczytywanie plików i przesyłanie ich do klienta - czyli w większości wypadków przeglądarki www. Czytanie zawartości plików z dysku trwa jakiś czas, dlatego serwer musi na nie czekać. W podejściu nieblokującym serwer nie będzie blokował wątku do czasu odczytu fragmentu pliku i przejdzie do obsługi następnego żądania. Serwer jest więc w stanie obsłużyć wiele tysięcy żądań w ciągu sekundy. Oczywiście przepustowość zależy także (a raczej przede wszystkim) od szybkości dysku oraz tego, czy pliki są w buforze systemu operacyjnego. Taki serwer potrzebuje niewielkiej ilości pamięci  oraz jest oszczędny jeśli chodzi o czas procesora. Dlatego tak dużo firm decyduje się na użycie Nginx lub innych podobnych serwerów.

Oprócz serwerów serwujących statyczne pliki z podejścia nieblokującego mogą korzystać także serwery proxy, na przykład te będące serwerami buforującymi (cacheującymi odpowiedzi w niepoprawnym polsko-angielskim ;) ). Do takich zaliczyć można Varnish lub wspomniany już Nginx. Także w tym wypadku serwer przez większość czasu czeka na zasoby - konkretnie na odpowiedź proxowanego serwera lub odczyt wcześniej zapisanej odpowiedzi z dysku (tylko w przypadku Nginx).

Innym przykładem są serwery kolejek wiadomości. Szczególnie chodzi o wyciąganie wiadomości z kolejki - klient chcący pobrać wiadomość jest blokowany dopóki wiadomość nie pojawi się w kolejce.  Wykorzystując nieblokujące podejście serwer może obsługiwać bardzo dużą liczbę słuchaczy nie marnując przy tym zasobów. Przykładowo silnik HornetQ może wykorzystywać Netty (framework służący do tworzenie nieblokujących serwerów w Javie) w celu ograniczenia liczby wykorzystanych wątków zapewniając przy tym większą przepustowość.

Powyżej opisywane serwery są raczej systemami ogólnego zastosowania. Dlatego szansa, że sam będziesz taki serwer pisać jest raczej mała (ze względu na fakt, że napisano już ich setki i nie ma sensu wymyślać koła na nowo ;) ). Są jednak bardziej specyficzne problemy, które może rozwiązać architektura asynchroniczna. Na przykład - serwer chatu. Problem znany od dawna, jednak wciąż sprawiający problemy. Ilość przesyłanych wiadomości między użytkownikami nie musi być wcale duża w porównaniu do liczby użytkowników zalogowanych do chatu. Część użytkowników może w ogóle nie być przy komputerze, część tylko czyta wiadomości itp. Aby użytkownik widział pojawiające się wiadomości w czasie rzeczywistym to musi być cały czas podłączony do serwera. Korzystając z podejścia blokującego trzeba wykorzystać tyle wątków ile użytkowników. Sam serwer robi jednak niewiele - najczęściej jest  tylko pośrednikiem między klientem (np. przeglądarką) a serwerem kolejek (np. typu topic). Dlatego jeśli mamy zamiar obsługiwać co najmniej 10 tysięcy użytkowników lub więcej jednocześnie to lepiej wykorzystać nieblokujące podejście sterowane zdarzeniami. Trzeba jednak pamiętać, aby API za pomocą, którego porozumiewamy się z serwerem kolejek też działało w sposób nieblokujący (tak aby można było zwolnić wątek).

Do serwera chatu podobne są serwery gier - czyli serwery, gdzie każda czynność gracza jest natychmiast wysyłana od klienta. Zasada w sumie identyczna, aczkolwiek sam serwer gier może wykonywać bardziej skomplikowane algorytmy niż chat co z kolei powoduje, że liczba równoległych żądań jest ograniczona bez względu na użyte podejście: blokujące czy nie. Współczesne gry społecznościowe działają nieco inaczej - np. Playfish w swoich grach buforuje po stronie klienta czynności gracza po czym wysyła cały pakiet czynności w jednym żądaniu. Po stronie serwera zaś operacje uruchamiają się w sposób asynchroniczny (algorytmy uruchamiają się później na innych maszynach), dzięki czemu serwer przyjmujący akcje użytkownika nie zajmuje się "ciężkimi operacjami".

Kolejnym przypadkiem wykorzystania podejścia nieblokującego sterowanego zdarzeniami jest pisanie serwera strumieniującego, np. serwera pozwalającego na streaming wideo. Serwer taki także czeka na dane np. z dysku i przesyła je fragmentami do klienta. W podejściu blokującym znowu liczba wątków będzie równa liczbie użytkowników oglądających film. W nieblokującym będzie można to zrobić nawet na jednym wątku.

Oczywiście przykłady, które podałem nie wyczerpują tematu. Z pewnością jest o wiele więcej zastosowań. Zapraszam więc do dodania komentarza, do czego jeszcze można użyć tą architekturę. W następnej części cyklu opiszę na jakie problemy można natrafić korzystać z serwerów asynchronicznych. Mam nadzieję, że do tej pory nie zanudziłem i chociaż trochę rozjaśniłem temat nieblokujących serwerów sterowanych zdarzeniami :)

Brak komentarzy:

Prześlij komentarz