Projekt serwera sieci rozproszonej - prośba o sprawdzenie poprawności

0

Cześć!

Zabieram się za napisanie serwera sieci rozproszonej. Nigdy nie pisałem tak zaawansowanego projektu, w związku z tym chciałem prosić o zweryfikowanie mojego pomysłu przez osoby bardziej doświadczone.

Sieć ma być dostępna przez zwykłe połączenia socketowe oraz WebSocketowe (z przeglądarek). Każdy klient łączy się z jednym z losowo wybranym serwerem. Serwerów jest kilka. Wszystkie serwery mają nawiązane połączenie między sobą, do wymiany wiadomości.

Dodatkowo każdy serwer jest połączony z bazą danych w chmurze, która z perspektywy systemu jest jednym bytem wspólnym dla wszystkich serwerów.

Chciałem, aby serwery odbierały wiadomości od klientów i innych serwerów w postaci wiadomości podobnych do protokołu HTTP. W każdej wiadomości byłby nazwa zadania do wykonania (np. "dodaj produkt do koszyka") oraz dane, z którymi zostaje wywołane zapytanie (np. identyfikator produktu).

Myślałem jak rozplanować zarządzanie takim przepływam wiadomości. Chciałbym, aby serwer (ze względów wydajnościowych) był oparty o zdarzenia. I tak: istniałaby jedna główna kolejka, w której znajdowałyby się odebrane wiadomości do wykonania. Dodatkowo byłyby inne kolejki, służące do wysyłania zapytań do bazy danych, serwera plików lub jeszcze innych źródeł, jeśli przyjdzie taka potrzeba. Każda kolejka będzie osobnym wątkiem, którego zadaniem byłoby branie najstarszej wiadomości (lub operacji do wykonania) z listy oczekujących i wykonanie jej. Miałoby to działać jak hardware komputera: główna kolejka do procesora i pozostałe do urządzeń wej/wyj.

Gdyby jakieś zadanie wymagałoby wykonania zapytania do bazy danych lub zewnętrznego serwera, to nastąpiłoby jego "spauzowanie". Odpowiednia żądanie trafiałoby do kolejki bazy danych lub kolejki komunikacji z zewnętrznym serwerem. Dopiero gdyby takie żądanie zostałoby wykonane i zostały zwrócone jakieś dane, to zadanie trafiałoby z powrotem do głównej kolejki wiadomości, i gdy nadeszłaby jego kolej zostałoby ono "odpauzowane" od stosownego miejsca.
Po spauzowaniu zadania wątek głównej kolejki wziąłby kolejne zadanie z listy i zacząłby je wykonywać.

Co myślicie o tym rozwiązaniu. Jest ono poprawne? A jeśli nie to co należałoby zmienić? Gdzie widzicie słabe punkty?

Z góry dziękuję za odpowiedzi!

0

Wygląda nieźle - nie jest to przypadkiem SEDA? https://en.wikipedia.org/wiki/Staged_event-driven_architecture

SEDA ma niestety jedną dużą wadę - dość pokaźne opóźnienia, bo przejście komunikatu z jednego etapu do innego wymaga oczekiwania w kolejce i przełączenia kontekstu. Przy mało obciążonym systemie o czasie będzie decydować czas przełączania kontekstu razy liczba etapów. Czas przełączania kontekstu przy sprzyjających warunkach liczy się w dziesiątkach mikrosekund, a przy mniej sprzyjających bywa, że i w milisekundach. Pewne straty też są na synchronizacji kolejek, ale jak użyjesz dobrych kolejek, to da radę obsłużyć kilkadziesiąt milionów komunikatów na sekundę. SEDA niezbyt dobrze wykorzystuje też cache L1/L2, bo komunikat przechodząc z jednej puli wątków do drugiej trafia zawsze do innego wątku, obsługiwanego być może przez inny rdzeń i dane powiązane z komunikatem muszą być ponownie wczytane z poziomu L3 lub pamięci.

IMHO lepszą architekturą jest architektura przyjęta w Netty tj. jedna kolejka na każdy rdzeń procesora, multipleksowana pomiędzy wiele żądań, nieblokujące I/O zarówno dla dysku jak i sieci, i przetwarzanie całego żądania przez jeden wątek od początku do końca. Zaletą jest brak synchronizacji w kodzie użytkownika, brak wymuszonego przełączania kontekstu, duża przyjazność dla cache L1/L2, a zatem bardzo niewielkie opóźnienie. Jedynie pewne operacje, które nie mają odpowiedników nieblokujących, są przetwarzane przez osobną, dużą pulę lekkich (tj. o małym stosie) wątków, aby nie zablokować puli głównej.

W pewnym projekcie stosuję jeszcze takie rozszerzenie, że na każdy rdzeń mam dwie kolejki - prywatną i publiczną. Ta prywatna jest tylko na zdarzenia generowane "dla siebie" - czyli jeśli zdarzenie jest obsługiwane przez wątek A i generuje kilka nowych zdarzeń, to te nowe są automatycznie przypisywane do kolejki prywatnej dla wątku A. Dzięki temu ta kolejka może nie mieć żadnej synchronizacji, bo wszystko odbywa się w ramach jednego wątku. Za to do kolejki publicznej, która jest thread-safe, może wrzucać każdy wątek, ale ona jest odczytywana dopiero, gdy prywatna zostanie opróżniona.

1 użytkowników online, w tym zalogowanych: 0, gości: 1