Laden...

SignalR Architektur: Wie Multimandantenfähig und wie eine MessageQueue?

Erstellt von Papst vor 5 Jahren Letzter Beitrag vor 5 Jahren 2.691 Views
P
Papst Themenstarter:in
441 Beiträge seit 2014
vor 5 Jahren
SignalR Architektur: Wie Multimandantenfähig und wie eine MessageQueue?

Hallo zusammen,

ich stehe davor, meiner Applikation ein RealTime Dashboard zu verpassen.
Hierfür möchte ich SignalR einsetzen, da die Daten auch eingangs gestreamed werden.

Idee ist also folgendes:
-> Daten kommen als Stream durch eine Message Queue
-> WebApp hängt an der Message Queue (--> hier fehlt mir eine bessere Idee, da diese Architektur nicht so schön skalierbar ist... da jede Nachricht nur von einem Client empfangen werden kann)
-> WebApp pusht per SignalR (IHubContext<THub>) in einem IHostedService die Daten an die Clients

Meine aktuellen Herausforderungen - und da finde ich leider die Dokumentation etwas unzureichend - sind:
-> Bessere Idee für die Schnittstelle MessageQueue -> WebApp
-> Wie mache ich das Multimandantenfähig?

Multimandantenfähig heißt in dem Fall:
Ein User hat einen bestimmten Mandanten (gewählt), in diesem mandanten gibt es n unterschiedliche Signale, die über SignalR kommen können. Ein Subset aus diesen Signalen hat er zu seinem Dashboard hinzugefügt.
Frage in dem Fall:
-> Besser viele SignalR Gruppen oder wenigere?
Die Frage bezieht sich darauf, ob ich für jedes Signal eine eigene Gruppe erstellen sollte (Dabei können wir gut auf 100k Gruppen kommen, wenn mal mehr als ein Mandant läuft; was ich für ziemlich viel halte!) oder macht es Sinn jedem Dashboard eine Gruppe zu geben und hier die Daten aufbereitet hinzusenden, was mich wieder zu der ersten Herausforderung führt: Die Schnittstelle MessageQueue -> WebApp müsste dann besser skalierbar sein.

Gehostet werden soll das ganze auf Azure. Ein SingalR Service wäre dann sicherlich eine Option.

16.806 Beiträge seit 2008
vor 5 Jahren

Also erst mal ist es ja so, dass ein Message Broker per default Mandantenfähig ist.
I.d.R. wird das über die Namespaces bzw. Partitionierung umgesetzt, also zB. /appName/<tenant>

Die Menge an Gruppen in SignalR hat keinen Einfluss auf die Performance selbst, sondern nur auf das Storage.
Wenn Du die Gruppen im RAM ablegst, dann ist der RAM das Limit.
Wenn Du die Gruppen in eine DB ablegst, dann ist das das Limit.

Welche Nachricht an welche User Du schickst bekommst Du ja durch ein entsprechendes Mapping von Tenant > User > Connection IDs raus.
Dazu müssen aber alle Connections in der DB liegen, da Du sonst nicht skalieren kannst.

Die Architektur, dass eine Web App an einem Message Broker hängt, ist eine weit verbreitete Architektur.
Ich verwende dafür i.d.R. ein ASP.NET Core Rahmen - jedoch ohne MVC Pipeline.
Eine eigene Middleware sorgt dann dafür, dass die ASP.NET Core Instanz auf die Messages hört.
Ist halt nicht Cloudnative.

Durch die eingebaute Dependency Injection registriere ich Message Handler und kann so Nachrichten vom Broker sehr einfach abonnieren und modular verarbeiten, zB an einen SignalR Hub.

P
Papst Themenstarter:in
441 Beiträge seit 2014
vor 5 Jahren

Hi Abt,

wie stellst du dann sicher, dass z.B. mit Loadbalancing, die richtige Instanz der WebApp die Nachrichten erhält?

Ich arbeite mit Azure ServiceBus und jede Nachricht, die ich in eine Queue hineinwerfe kann von genau einem Client verarbeitet werden (ausser ich werfe sie vom Client aus wieder zurück... dann habe ich aber ein Problem und muss irgendwie bestimmen, dass ich die Nachricht schon einmal verabeitet habe)...

Oder sehe ich hier Probleme, wo keine sind?

16.806 Beiträge seit 2008
vor 5 Jahren

Das ist doch irrelevant, welche Instanz das tut.
Wenn man in Instanzen denkt, dann ist das Scaling Konzept falsch 😉

Jede Instanz kann theoretisch jede Nachricht bekommen.
Jede Instanz kennt durch die gemeinsame Datenbank (in der Doc glaube ich als externe Datenquelle bezeichent) aber auch alle Connections.

Daher kann irgendeine Instanz jede Message nehmen, und an jeden Client schicken.

PS: der Azure Service Bus versteht nicht nur Queues, sondern auch Topics.
Aber in Deinem Fall ist Queue das richtige.

PPS: Du könntest auch den EventHub für das Verarbeiten der Nachrichten und Senden der SignalR Daten verwenden.
Damit könntest Du den ASP.NET-Part weglassen, wenn Du Cloudnative sein willst.

2.207 Beiträge seit 2011
vor 5 Jahren

Hallo Papst,

beim Stichwort "Scaling" werde ich auch mal Azure SignalR in den Raum:

https://azure.microsoft.com/en-us/blog/azure-signalr-service-a-fully-managed-service-to-add-real-time-functionality/

Gruss

Coffeebean

P
Papst Themenstarter:in
441 Beiträge seit 2014
vor 5 Jahren

Hallo Coffeebean,

danke - das kenne ich. Ist allerdings noch in der Preview. Behebt aber denke ich auch nicht mein Hauptproblem, dass wird ja entsprechend sein, die eingehenden Nachrichten so zu verarbeiten, dass ich sie auf die Gruppen aufteile.

Hat einer von euch zufälligerweise passende Informationen über Partitionierung mit SignalR, ich finde da leider nicht so arg viel zu.
Das brauchbarste ist noch https://apprenda.com/blog/a-multi-tenant-real-time-app-with-apprenda-and-signalr/ , allerdings für das .NET Framework SignalR.

@Abt: ja, das ist finde ich ein Riesenproblem von Azure. Es gibt soviel Auswahl und man kann so viele Dienste nutzen um ähnliche Funktionalitäten zu erreichen.
Da ist es schon praktisch, wenn es die passenden MS Partner Firmen gibt die da aushelfen können.
EventHub habe ich mich noch nicht mit beschäftigt, fällt bei mir aber glaube ich raus, da ich die Authentifizierung über Identityserver mache.

16.806 Beiträge seit 2008
vor 5 Jahren

Die einen nennen es Problem, die anderen Flexibilität.
Ich bin froh, dass es in Azure nicht nur einen Weg gibt, um Dinge umzusetzen.

P
Papst Themenstarter:in
441 Beiträge seit 2014
vor 5 Jahren

Wir schweifen ab 😃
Problem ist sicherlich falsch, was fehlt ist ein einfacher Weg zu finden und zu vergleichen. Da ist sicherlich viel Vorarbeit gemacht, aber viel kann auch nur durch Erfahrung beurteilt werden.

Zu der von dir angesprochen Partitionierung finde ich leider kaum etwas in Bezug auf SignalR.

Wenn man in Instanzen denkt, dann ist das Scaling Konzept falsch 😉

Ist es so einfach und ich kann das aussen vor lassen? Würde mir natürlich entgegen kommen, wenn dem so ist.
Der Knoten in meinem Kopf sieht wie folgt aus:
Angenommen ich habe mehrere WebApp Instanzen. Ein Client verbindet sich aber immer nur mit einer davon (richtig? Sonst wäre ja Load Balancing nicht zum verteilen der Clients auf mehrere App Instanzen). Wie ist dann sichergestellt, dass der richtige Client alle Nachrichten bekommt, wenn theoretisch jede App Instanz nur einen Teil der Message Queue Nachrichten bekommt?

Wo ist mein Denkfehler?

16.806 Beiträge seit 2008
vor 5 Jahren

SignalR hat keine Partinionierung, aber der Service Bus hat das.

Hinter einem Message Bus steckt auch nichts anderes als ein Mehr-Instanzen-System hinter einem Load-Balancer.
Und Partionen sind quasi jeweils eine Instanz.
Damit man nun Szenarien wie Sessions oder Transactions abbilden kann, die immer die Zuweisung auf eine Instanz erfordern, um zB Ordered Messages umsetzen zu können, sorgt nun der Partition Key dafür, dass Messages mit dem gleichen Partition Key auch wirklich auf die gleiche Partition kommt.
Für das Lesen nachher ist es irrelevant. Es ist wichtig dafür, wie nachher Nachrichten intern im Service Bus geschrieben werden.

Ein Partition Key kann eben auch eine Tenant-ID sein.
Spielt hier aber weniger eine Rolle.

Was eher wichtig ist, ist dann das Namespacing.
Beim Service Bus kannst Du Namespaces anlegen und darüber auch ein Permission System, um eben Mehr-Mandanten-Systeme umzusetzen.

SignalR als ASP.NET Middleware speichert per default die Verbindungen im Memory.
Damit ist keine Skalierung möglich, da in diesem Fall jede Instanz sein eigenen Speicher für Verbindungen hat.

Das hat zur Folge, dass Instanz 1 nicht die Verbindungen von Instanz 2 kennt.
Das würde genau Dein Problem beschreiben: Du kannst nicht sicherstellen, dass die Instanz, die die Nachricht bekommt, auch die Verbindung zum Client hat.

Wenn Du nun in SignalR aber die Verbindungen nicht im Memory hast, sondern in einer Datenbank (External Storage), auf die alle Instanzen Zugriff haben, dann kennt auch jede Instanz das Ziel einer Nachricht.
Eine Instanz kann dabei sogar offline gehen, weil ja sowieso nichts im RAM liegt, sondern alle nötigen Informationen in einer Datenbank.

Einfach ma in die Doku gucken: Mapping SignalR Users to Connections - Permanent, external storage 😉

PS: was die Authentifizierung mit dem EventProcessor des Event Hub zutun hat, weiß ich leider nicht 😉

P
Papst Themenstarter:in
441 Beiträge seit 2014
vor 5 Jahren

Hi Abt,

danke für deine Geduld mit mir. Ich komme wahrscheinlich einfach aus einer sehr unterschiedlichen Welt der Programmierung und muss mich erst noch an vieles Gewöhnen.
Die Doku zum persistieren der Verbindungen hatte ich mir schon angeschaut - es bleibt mir allerdings noch eine Frage, vielleicht verstehst du dann auch meine ganzen Fragen in der Richtung besser 😃

-> Ein Websocket ist ja anders als eine Klassische Webseite (ob SPA mit WebApi, MVC oder irgendwie anders gelagert) eine Stateful Application, durch den logischen Kanal der hier mittels einer aufrechterhaltenen TCP Verbindung herrscht bekommt die App einen Zustand (den Socket in diesem Fall).
Wenn ich jetzt die "Verbindung" persistiere kann deswegen doch eine zweite Instanz nicht auf diesen Zustand zugreifen, denn die Verbindung hat als Inhalt nur Benutzerdefinierte Proerties, aber niemals einen Socket.. diesen kann ich gar nicht auf diese Weise persistieren.

Folgendes Szenario:
Es existierten zwei Instanzen der Websocket App (A, B).
Client baut Verbindung mit Instanz A der Websocket App auf.
Instanz B (hängt an der gleichen Queue des Service Bus) erhält eine Nachricht, die für den mit Instanz A verbundenen Client bestimmt ist, holt sich die "Verbindung" aus dem Storage und dann? Wie kommt die Nachricht in den logischen TCP Kanal? Einfach senden kann die Instanz B ja nicht, das blockiert jede Firewall.
Oder ist das alles soweit standardisiert, dass sich jeder beliebige Reverse Proxy (denn was anderes sind ja Load Balancer nicht) um die Weiterleitung kümmert?

PS: was die Authentifizierung mit dem EventProcessor des Event Hub zutun hat, weiß ich leider nicht 😉

Das bezog sich auf

Damit könntest Du den ASP.NET-Part weglassen, wenn Du Cloudnative sein willst.

aber vielleicht fehlen mir dazu die basics, denn der ASP.NET-Part kümmert sich bei mir neben Authentifizierung (über JWT) noch um die Authorisierung.

16.806 Beiträge seit 2008
vor 5 Jahren

Zu letzterem: AuthZ funktioniert auch im EventProcessor, aber deutlich aufwändiger.
Da ist natürlich das Policy-Konstrukt von ASP.NET Core einfach erste Sahne.

Zum Problem:

Du hast ja Deine SignalR Instanzen hinter einem Load Balancer.
Der Nutzer schickt also eine Nachricht an den Load Balancer, und der verteilt die ohne Sticky Session an irgendeine Instanz.

Bei einer Antwort schickt irgendeine Instanz eine Nachricht an den Load Balancer, und der an den Nutzer.
Der Nutzer hat also immer eine dauerhafte TCP-Verbindung, auch wenn er nicht weiß, welche SignalR Instanz am Ende erreicht.

Die SignalR Instanzen sprechen hinter dem Load Balancer über die Backplane; das ist das SignalR Konzept, wei zB. Broadcasting umgesetzt wird.

Ich hab mal meine extrem guten Zeichenskills verwendet, und Dir nen Bild angehängt

P
Papst Themenstarter:in
441 Beiträge seit 2014
vor 5 Jahren

Vielen Dank, so wird dann auch ein Schuh draus.