2010-10-05 8 views
19

Piszę serwer Java, który używa zwykłych gniazd do akceptowania połączeń od klientów. Używam dość prostego modelu, w którym każde połączenie ma własny wątek odczytywany z niego w trybie blokowania. pseudokod:Jeden wątek na klienta. Wykonalny?

handshake(); 

while(!closed) { 
    length = readHeader(); // this usually blocks a few seconds 
    readMessage(length); 
} 

cleanup(); 

(Nici są tworzone z Executors.newCachedThreadPool() więc nie powinno być żadnych istotnych nakładów na ich rozpoczęciem)

Wiem, że to trochę naiwne konfiguracji i to naprawdę nie będzie skalować również do wielu połączeń, jeśli wątki były dedykowanymi wątkami systemu operacyjnego. Słyszałem jednak, że wiele wątków w Javie może współużytkować jeden wątek sprzętowy. Czy to prawda?

Wiedząc, że będę używać maszyny wirtualnej Hotspot w systemie Linux, na serwerze z 8 rdzeniami i 12 GB pamięci RAM, czy uważasz, że ta konfiguracja będzie działać dobrze dla tysięcy połączeń? Jeśli nie, jakie są alternatywy?

Odpowiedz

5

Możliwe jest skalowanie do tysięcy klientów. Ale ile jest następnych pytań tysięcy.

Typową alternatywą jest użycie selektorów i niezablokowanych we/wy znalezionych w pakiecie java.nio.

Ostatecznie pojawia się pytanie, czy warto skonfigurować serwer w konfiguracji klastrowej, równoważąc obciążenie na wielu fizycznych maszynach.

5

To będzie dobrze skalować do setek połączeń, a nie do tysięcy. Jedną z kwestii jest to, że wątek Java zajmuje również sporo stosu (na przykład 256 KB), a system operacyjny będzie miał problemy z planowaniem wszystkich wątków.

Spójrz na Java NIO lub framworks, które pomogą Ci zacząć robić złożoną rzeczy łatwiej (np Apache Mina)

+1

Każdy wątek będzie trochę stosu, ale jeśli przekształcę go w model bez blokowania, każde połączenie będzie potrzebować więcej danych na stercie, takich jak "w jakiej fazie czytania wiadomości jesteśmy teraz", która jest obecnie po prostu określone przez wskaźnik instrukcji (poprawne słowo?) wątku. Jednak planowanie może być problemem. –

+1

@Bart: dodatkowa przestrzeń na połączenie nie jest nawet tak duża jak stos; przejście na NIO poprawi skalowalność. Koszt jest taki, że większość ludzi ma trudności z grokowaniem tego, co się dzieje (a Java nie ma coroutinesów, które można wykorzystać do zrównoważenia obaw). –

+0

Jak powiedział bluszcz, głównym założeniem, aby skalować do tysięcy, jest naprawdę stos, który zużyją. Z drugiej strony nie jestem całkowicie przekonany, że planowanie będzie głównym czynnikiem. To będzie jakiś czynnik, ale może nie tak wielki, jak by się mogło wydawać. Jeśli twoimi dominującymi działaniami są I/O, wtedy twoje wątki szybko zwrócą procesory w większości przypadków, a będą one stronicowane tylko wtedy, gdy zostaną odebrane dane. Jeśli myślisz o tym, to zdjęcie nie jest zbyt różne, nawet jeśli używasz NIO. Po prostu więcej wątków bierze udział w pytaniu o mniej więcej taką samą liczbę cykli procesora. – sjlee

3

mieć dobry osiągów przy obchodzeniu się z wielu gniazd zazwyczaj użyć select podejście, które jest jak Unix API obsługuje wielowątkowe aplikacje jednowątkowe, które wymagają wielu zasobów.

Można to zrobić za pomocą pakietu java.nio, który ma klasę Selector, która zasadniczo może przeglądać wszystkie otwarte gniazda i powiadamiać użytkownika o dostępności nowych danych.

Możesz zarejestrować wszystkie otwarte strumienie wewnątrz pojedynczego Selector, a następnie możesz obsłużyć wszystkie z jednego wątku.

Można uzyskać dodatkowe infos z samouczka here

+0

Przez przypadek natknąłem się na pytanie SO dotyczące metody 'InputStream.available()', o której zapomniałem. Czy możesz mi powiedzieć, że korzystasz z 'Selectora 'w przeciwieństwie do posiadania wątku obsługującego wiele połączeń, używając' available() ', aby zapobiec blokowaniu? –

+0

Ponieważ unikasz ponownego zaimplementowania czegoś, co przechodzi przez różne gniazda programowo, ponieważ będziesz używał czegoś specjalnie dostosowanego do tego celu :) Główną zaletą będzie oszczędność czasu na debugowanie i implementację .. – Jack

+0

Hmm, ale wdrożenie tego, w przynajmniej w moim przypadku byłoby to bardzo proste. Mój protokół to 1. odczytać liczbę całkowitą określającą długość wiadomości. 2. odczytać tę liczbę bajtów za jednym razem –

1

wątki nie są tak drogie jak kiedyś, więc „zwykłym” realizacja IO może być ok do pewnego momentu. Jeśli jednak patrzysz na skalowanie do tysięcy lub więcej, prawdopodobnie warto zbadać coś bardziej wyrafinowanego.

Pakiet java.nio rozwiązuje ten problem przez zapewnienie IO multipleksowania/blokowania gniazd, co pozwala powiązać kilka połączeń z jednym selektorem. Jednak to rozwiązanie jest o wiele trudniejsze w porównaniu z prostym podejściem blokującym ze względu na wielowątkowość i brak blokowania.

Jeśli chcesz realizować coś poza prostą operacją IO, sugerowałbym przeglądanie jednej z dobrej jakości bibliotek abstrakcji sieci. Z osobistego doświadczenia mogę polecić Netty, który wykonuje większość kłopotliwych operacji NIO.Ma jednak trochę krzywej uczenia się, ale kiedy już przyzwyczaisz się do podejścia opartego na zdarzeniach, jest bardzo potężny.

1

Jeśli interesuje Cię wykorzystanie wdrożenia i zarządzania istniejącym kontenerem, możesz rozważyć utworzenie nowej procedury obsługi protokołów w Tomcat. Zobacz this answer na pokrewne pytanie.

UPDATE: This post Matthew Schmidt utrzymuje złącze Nio oparte (napisany przez Filip Hanik) w Tomcatem 6 osiągnięty 16000 jednoczesnych połączeń.

Jeśli chcesz napisać własne złącze, spójrz na MINA, aby pomóc w abstrakcjach NIO. MINA ma również funkcje zarządzania, które mogą wyeliminować potrzebę użycia innego kontenera (w przypadku wątpliwości dotyczących rozmieszczenia wielu jednostek i ich działania itd.).

+0

Interesujące, ale moja aplikacja nie używa obecnie kontenera. Jestem także zainteresowany współpracą z NIO (jeśli okaże się to konieczne) dla celów edukacyjnych. –

3

Urządzenie JVM dla Linux wykorzystuje odwzorowanie pojedynczych nitek. Oznacza to, że każdy wątek Java jest mapowany na jeden natywny wątek systemu operacyjnego.

Więc tworząc tysiąc lub więcej wątków nie jest dobrym pomysłem, ponieważ będzie to wpływać na wydajność (context switching, cache wypłukuje/misses, synchronization opóźnienia itd.) Nie ma to sensu, jeśli masz mniej niż tysiąc procesorów.

Jedynym odpowiednim rozwiązaniem dla obsługi wielu klientów równolegle jest użycie asynchronicznych operacji we/wy. Szczegółowe informacje można znaleźć na stronie this answer pod adresem Java NIO.

Zobacz także:

+1

Dzięki, to jest pomocna odpowiedź, ale nie zgadzam się z "To też nie ma sensu, jeśli masz mniej niż tysiąc procesorów.". Ma to sens w utrzymywaniu bieżącego stanu połączenia ("czy teraz czytamy nagłówek lub dane", "ile wiadomości otrzymaliśmy jeszcze" itd.) Na stosie zamiast sterty, która jest szybsza (prawda?) i ułatwia programowanie. –

+0

To tylko sprawia, że ​​czujesz się jak łatwiej jest programować (oba podejścia są stosunkowo łatwe IMO). Używanie stosu nie czyni go de facto szybszym, wszystko zależy od tego, co robisz. W każdym razie kompromis z wątkami 1K będzie większy niż utrzymywanie listy sesji z wcześniej przydzielonym stanem. –

+0

+1 za zielone wątki –

2

Spróbuj Netty.

Model "jeden wątek na żądanie" jest sposobem zapisywania większości serwerów aplikacji Java. Twoja implementacja może być skalowana tak samo, jak robi.

+0

To, że "większość" ludzi coś robi, oznacza, że ​​jest to słuszne lub najlepsze. Jedynym sposobem, aby odpowiedzieć na to na pewno, byłoby napisanie kodu przy użyciu obu metod i przetestowanie, aby zobaczyć, który z nich działa lepiej pod względem wykorzystania procesora i pamięci. Z powodu różnic między oknami i linuxem może być konieczne rozważenie testów w obu środowiskach, aby mieć pewność. Powiedział, że wierzę, że jeden wątek na żądanie nie jest skalowalny. – Jacob

0

Uważam, że lepszym podejściem jest nie radzić sobie z wątkami. Stwórz pulę (ThreadExecutor lub kilka innych rzeczy) i proste wysyłanie do puli.

Oczywiście, myślę, że asynchroniczne operacje we/wy sprawią, że będzie on lepszy i szybszy, ale pomoże rozwiązać problemy z gniazdem i siecią. Tylko. Gdy twoje wątki blokują się z powodu operacji we/wy, maszyna JVM uśpi ją i zmieni dla innego wątku, aż do momentu powrotu blokujących operacji we/wy. Ale to zablokuje tylko wątek. Twój procesor będzie działał i zacznie przetwarzać inny wątek. Więc, pomijając czas tworzenia wątku, sposób, w jaki używasz operacji we/wy, nie wpływa zbytnio na twój model. Jeśli nie utworzysz wątków (przy użyciu puli), Twój problem zostanie rozwiązany.

+0

Używam Executora, ale ponieważ każdy wątek w zasadzie robi 'while (! Closed) read();', który faktycznie nie zmniejsza liczby wątków, po prostu zmniejsza narzut na ich tworzenie. –

+0

To jest dokładnie moja uwaga. Kiedy wywołasz read(), twój wątek zablokuje się, dopóki nie będzie miał nic do zrobienia. Nie ma znaczenia, ile wątków masz, ale ile wątków RUNNABLE masz, a zablokowane wątki nie liczą się. Nie będą konkurować o czas procesora. Tak więc, w końcu będziesz miał tylko wątki, które mają rzeczy do zrobienia, konkurujące według czasu procesora. –

1

Zasugeruję, że zależy to bardziej od tego, co jeszcze robi serwer podczas przetwarzania wiadomości. Jeśli jest stosunkowo lekki, specyfikacja maszyn powinna ŁATWA poradzić sobie jedynie z obsługą połączeń tysięcy takich procesów. Dziesiątki tysięcy to może inne pytanie, ale potrzebujesz tylko dwóch maszyn w tej samej sieci, aby empirycznie je przetestować i uzyskać konkretną odpowiedź.

0

Po co wykonywać własne? Można użyć kontenera serwletów z serwletami, kolejką komunikatów lub ZeroMQ.