Skalowanie wszerz websocketów z pomocą message brokera

0

Hej. Postanowiłem spróbować swoich sił z webscoketami w rozwiązaniach webowych a właściwie ich skalowalnością. Załóżmy, że mówimy o prostej apliakcji typu czat.

Natknąłem się na jedno z możliwych rozwiązań: uzycie message brokera do dystrybucji wiadomości pomiędzy podami, ale muszę przyznać, że nie do końca go rozumiem. Już tłumaczę.
Jaki jest problem ze skalowalnością wszerz z webscoketami?
Jeżeli mamy kilka instancji (używam k8s, więc podów) to klient (np. apka w React/Angular) nie trzyma połączenia z danym podem.
Pomiędzy podami a klientem stoi tak skonfigurowany LoadBalancer w k8s

apiVersion: v1
kind: Service
metadata:
  name: lct-api-service
spec:
  selector:
    app: lct-api
  type: LoadBalancer
  ports:
    - protocol: TCP
      port: 6008
      targetPort: 80

Czego nie rozumiem?
Jak w takiej sytuacji klient powinien wysyłac wiadomości na serwer. Gdy mamy jedną instancję to wysyłam wiadomość przez otwarte połączenie ws. Jednak przy wielu isntancjach nie wchodzi to w grę, bo gdyby połączenie było trzymane z jednym konkretnym podem to nie byłoby potrzeby zaprzęgania message brokera.

  1. W takim razie jak wysyłać taką wiadomość zarówno z 'perpsektywy' klienta i serwera?
  2. Jeżeli serwer otrzyma jakoś wiadomość od klienta (przez http czy nawet bezposednio wrzuconą do message brokera) to jak dana instancja może powiadomić odpowiedniego klienta skoro połączenie nie jest utrzymane? Nawet jeżeli konkretny pod jest w stanie utrzymać połączenie to już jest bardziej SSE niż websocket.

Wstawiłem konfigurację k8s z tego względu, że być może brakuje mi jakiegoś ustawienia by takie połączenie było utrzymane i to nie jest problem ze złym zrozumieniem koncepcji co implementacji.

0

Nie wiem (może nie rozumiem problemu), ale jak zaczynasz skalować wszerz (dokładasz nowa instancje) to podstawą jest, że aplikacja musi być bezstanowa (czyli nie trzyma żadnych informacji o połączeniach, danych sesji itp itd) a cały stan jest trzymany w zewnętrznym źródle np w Redisie, żeby było szybko wówczas nie ma znaczenia gdzie ci puści ruch LoadBalacer.

0

Jak w takiej sytuacji klient powinien wysyłac wiadomości na serwer. Gdy mamy jedną instancję to wysyłam wiadomość przez otwarte połączenie ws. Jednak przy wielu isntancjach nie wchodzi to w grę, bo gdyby połączenie było trzymane z jednym konkretnym podem to nie byłoby potrzeby zaprzęgania message brokera.

Jeśli dobrze pamiętam to jest tak, że w tej sytuacji po nawiązaniu połączenia z serwerem za pomocą ws w message brokerze jest tworzona kolejka (queue) per połączenie ws i cała komunikacja pomiędzy serwerem i odbiorcami idzie poprzez tą właśnie kolejkę w stylu tradycyjnego pub/sub. Czyli

screenshot-20230721100420.png

Dzięki temu że z lewej strony jest utrzymywane połączenie pomiędzy klientem i serwerem serwer po otrzymaniu wiadomości z message brokera może ją wysłać z powrotem do klienta poprzez nawiązane połączenie ws.

Co do konfiguracji k8s-a nie pomogę :P

0

No tak, czyli Twoim zdaniem trzeba zrezygnować z websocketów?

0

To może coś źle skonfigurowałem, bo jeżeli dobrze rozumiem twój diagram to instancja SIngaling jest w stanie coś wysłać za pomocą websocketów. Zadaniem Pub/Sub brokera jest wysłanie do wszystkich podów info, że jest wiadomość a te własciwe wypchną informację do podpiętych klientów.
Zakładając, że Singaling ma połaczenie ws z klientem to w sumie nie lepiej skorzystać z SSE? Bo przy próbie nawiązania połączenia ws Klienta z serwerem i tak się ono zgubi przez Load Balancera. Czy nie lepiej strzelać hhtp i użyć SSE? Bo przy rozwiazaniu Pub/Sub to te webscokety takie naciągane.

0
S4t napisał(a):

Nie wiem (może nie rozumiem problemu), ale jak zaczynasz skalować wszerz (dokładasz nowa instancje) to podstawą jest, że aplikacja musi być bezstanowa (czyli nie trzyma żadnych informacji o połączeniach, danych sesji itp itd) a cały stan jest trzymany w zewnętrznym źródle np w Redisie, żeby było szybko wówczas nie ma znaczenia gdzie ci puści ruch LoadBalacer.

Tak, tylko problem jest z tym jak powiadomić clienta o wiadomości. Skoro połączenie ws nie jest trzymane, to jak zwrócić informację do clienta?
Bo koncepcję rozumiem, jednak w wykonaniu czegoś brakuje.

2

Rozwiązaniem jest to co opisał @markone_dev, czyli dodatkowa komunikacja pomiędzy serwerami obsługującymi WS za pomocą pub/sub. Najpopularniejsze biblioteki powinny wspierać z automatu integracje z różnymi brokerami pub/sub - widziałem m.in. redisa i kafkę.

Połączenie ws klienta powinno być trzymane, inaczej rzeczywiście nie wyślesz do niego wiadomości. Tylko jak masz kilka instancji to użytkownik A może być połączony do serwera A, a użytkownik B do serwera B. I w takim wypadku serwer B nie może wysłać wiadomości do użytkownika A, bo nie ma informacji o jego połączeniu. Dlatego przepychasz wiadomości przez pub/sub do którego zasubskrybowane są wszystkie serwery WS.

2
kenik napisał(a):

Jeżeli mamy kilka instancji (używam k8s, więc podów) to klient (np. apka w React/Angular) nie trzyma połączenia z danym podem.

No trzyma, o to chodzi w websocketach: jest otwarty socket i to połączenie (apka w Reakcie <-> konkretny POD) jest nierozerwalne aż któraś strona się nie wywali/nie zamknie. Połączenie jest utrzymane, traktuj sesję websocketową jak jedno bardzo długie zapytanie HTTP, gdzie nie ma opcji, że zmienią się strony (klient i serwer) w czasie trwania zapytania. Oczywiście dochodzą problemy pt. co zrobić jak klient zerwie połączenie i przy powtórnym połączeniu będzie gadał z innym podem albo co zrobić jak pod jest terminated, bo robimy deploy nowej wersji i trzeba przełączyć klienta na innego poda, ale to jest ten sam problem co w przypadku zwykłego modelu request/response takiego jak HTTP więc i rozwiązania są te same: trzeba trzymać stan w jakimś bezpiecznym miejscu np. w bazie.

0
slsy napisał(a):
kenik napisał(a):

Jeżeli mamy kilka instancji (używam k8s, więc podów) to klient (np. apka w React/Angular) nie trzyma połączenia z danym podem.

No trzyma, o to chodzi w websocketach: jest otwarty socket i to połączenie (apka w Reakcie <-> konkretny POD) jest nierozerwalne aż któraś strona się nie wywali/nie zamknie. Połączenie jest utrzymane, traktuj sesję websocketową jak jedno bardzo długie zapytanie HTTP, gdzie nie ma opcji, że zmienią się strony (klient i serwer) w czasie trwania zapytania. Oczywiście dochodzą problemy pt. co zrobić jak klient zerwie połączenie i przy powtórnym połączeniu będzie gadał z innym podem albo co zrobić jak pod jest terminated, bo robimy deploy nowej wersji i trzeba przełączyć klienta na innego poda, ale to jest ten sam problem co w przypadku zwykłego modelu request/response takiego jak HTTP więc i rozwiązania są te same: trzeba trzymać stan w jakimś bezpiecznym miejscu np. w bazie.

Ok, dziękuję za odpowiedź. W taki mrazie problem jest w konfiguracji kubernetes, ponieważ on mi takiego połączenia nie trzyma stąd cały ten wątek. Czy ktoś poleca jakiegoś load balancera w takim razie?? :D

0
kenik napisał(a):

Czy ktoś poleca jakiegoś load balancera w takim razie?? :D

A napisałeś gdzie hostujesz aplikację?

1
kenik napisał(a):

W taki mrazie problem jest w konfiguracji kubernetes, ponieważ on mi takiego połączenia nie trzyma stąd cały ten wątek. Czy ktoś poleca jakiegoś load balancera w takim razie?? :D

Co to znaczy, że kubernetes nie trzyma połączenia?

0

Poszukałem jeszcze raz, jednak problem nie jest z utrzymywaniem połaczenia tzn. że po nawiązaniu połączenia jest przerywane.
Problem jest z samym nawiązaniem, ponieważ singnalR wysyła 2 zapytania przy próbie nawiązania połączenia i jeżeli nie trafią do tego samego poda połączenie nie może byc nawiązane.

2

No tak, nawet w dokumentacji coś o tym jest :)
https://learn.microsoft.com/en-us/aspnet/core/signalr/scale?view=aspnetcore-7.0

Pierwszy request to pewnie negocjacja protokołu i kolejny musi trafić na ten sam serwer.
Jak masz skonfigurowany Pub/Sub (bez niego i tak nie będzie działać poprawnie w przypadku kilku serwerów) i chcesz tylko WebSockety to chyba możesz w opcjach przestawić SkipNegotiation na true i obędzie się bez sticky session.

0

jak potrzbujesz tego do roboty to KrakenD ma wbudowane wsparcie do websocketów (płatne).

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