Jak zaprojektować efektywną wymianę danych w czacie (wiele klientów, serwer pośredniczy w wymianie danych)?

0

Jak w temacie. Obecnie próbuję coś zdziałać przy użyciu socketów. Ciągnąć to dalej czy użyć czegoś bardziej wysokopoziomowego? Jak tak to czego? A może są jakieś sprawdzone sposoby których nie znam? Z góry dzięki za pomoc ;)

0

Widziałem ostatnio przykład w firestore, gdzie facet zapinał się na notyfikacje i uaktualniał czat na hostach. Ale sam czatu nigdy nie robiłem. Może w https://github.com/RocketChat/Rocket.Chat znajdziesz jakąś inspirację - websockety i tak dalej

1

Ewentualnie od siebie dorzucę z pakietu NIO Selector + ServerSocketChannel. Nie musisz tworzyć wielu wątków dla każdego połączenia, działa asynchronicznie

3

Hmm, ciekawy temat. Akurat sam sobie implementuję czat. Wybrałem komunikację przez SSE.
Na backendzie mam Java, Spring Boot 2.1.x, WebFlux, Netty, MongoDB (kolekcja "archiwum" + dla każdej dyskusji capped collection z obecnymi eventami, do których łączę się tailable cursorami przy użyciu ReactiveMongoTemplate).

Na lapku z CPU i7-6820HQ, na którym odpalam mongo + chat serwis + gatling stress test, wytrzymuje to około 1500 userów nasłuchujących równocześnie 500 czat roomów i spamujących co parę sekund "typing indicatorami" i wiadomościami (~700 req/s). Sam gatling podczas stress testu bierze ~30% CPU.
Serwis spokojnie wytrzymuje to przy heapie 512MB, mongo zabiera < 1GB (choć nie bawiłem się w ogóle configiem).

Szczególnie ciekawi mnie czy ktoś coś podobnego robił i czy jestem blisko uzyskania zadowalającej wydajności (w tym momencie jestem bardziej niż zadowolony, w szczególności że jest to obsługiwane przy < 1k linii kodu i bardzo łatwo się skaluje wszerz ale może ktoś uśiwadomi mnie swoimi liczbami, że moje podejście jest prostu złe:D)

EDIT:

Postanowiłem się trochę pobawić. Zamiast używać mongo do przechowywania tymczasowych eventów dla każdego chat roomu, robię to w heapie JVM:

  • jak ktoś postuje wiadomość, to zapisywana jest ona w archiwum mongo, tak jak dotychczas, ALE
  • eventu nie zapisuję w mongo (ani też nie tworzę capped collection itp), tylko zamiast tego publikuję to przez fluxa, którego nasłuchują klienci.

Plusy nowego rozwiązania:

  • liczba obsługiwanych przez serwis równoczesnych userów wzrosła na moim laptopie z 1500 do 2500 (na 500 chat roomach)
  • Zużycie CPU procesu mongo przy 2500 userach postujących wiadomości / pobierających archiwalne wiadomości oscyluje między 1.5 - 3% (no i nie ma setek/tysięcy połączeń otwartych przez kursory nasłuchujące)

Minusy:

  • Dojdzie konieczność skonfigurowania reverse proxy tak, żeby bieżące eventy każdego z chat roomow były obslugiwane przez tą samą instancję (coś jak Sticky Session, tylko że nie po sesji/IP usera a ID chatu)

Biorąc pod uwagę, że najtańszy VPS na webh/digitalocean (~20pln/miesiąc) ogarnia ~250 - 500 concurrent userów, i przy założeniu, że cluster mongo na mocnych maszynach (~1200PLN/miesiąc) wyrobi ~100 000 aktywnych (mocno aktywnych) userów, szacuję, że za kwotę rzędu 5000 - 10 000 PLN/miesiąc (wliczając rev proxy i load balancery) mógłbym obsłużyć do 100 000 concurrent userów. Nie sprawdzałem CPU-optimized VPS na DigitalOcean... może bardziej by się opłacały - na pewno nie potrzeba byłoby tak dużo serwerów :D

EDIT 2:

Powyższa zabawa dotyczyła komunikacji przez SSE (Server Sent Events). Dla próby postanowiłem sprawdzić jak to by było, gdyby klienci nasłuchiwali czatu przez polling. Wynik na podobnym teście na tym samym sprzęcie to ~4400 concurrent users (w moim teście wykonują ok. 3300 zapytań na sekundę, z czego 2100/s to polling), przy założeniu, że odpytują serwer co 2 sekundy (w przypadku SSE zapomniałem dodać, że stosowałem Flux.buffer(Duration.ofMillis(600), żeby klienci mogli przetwarzać bulkowo napływające eventy na aktywniejszych czatach)).

Przy 4400 aktywnych userach (co w moim teście przekłada się na ~1000 zapisów na sekundę, ~200 odczytów na sekundę), mongodb zużywało ~3.5% CPU

Plusy

  • znacznie mniejszy koszt infrastruktury

Minusy

  • w przypadku pollowania co 2 sekundy, czat jest troszkę mniej responsywny
  • znacznie większy ruch sieciowy - klientowi odpytującemu serwis co 2 sekundy, zwracam dane z ostatnich 5 sekund, żeby nic nie umknęło
  • troszke więcej przetwarzania po stronie klienta - przy każdej odpowiedzi z serwera, klient musi odfiltrować zduplikowane eventy (co nie jest skomplikowane/czasochłonne ale te parę linijek kodu więcej jest)

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