Hosting und Skalierung von ASP.NET Core SignalR

Von Andrew Stanton-Nurse, Brady Gaster und Tom Dykstra

In diesem Artikel werden Überlegungen zum Hosten und Skalieren von Apps mit hohem Datenverkehr erläutert, die ASP.NET Core SignalR verwenden.

Fixierte Sitzungen

SignalR erfordert, dass alle HTTP-Anforderungen für eine bestimmte Verbindung vom gleichen Serverprozess verarbeitet werden. Wenn SignalR in einer Serverfarm (mehrere Server) ausgeführt wird, müssen „fixierte Sitzungen“ verwendet werden. „Fixierte Sitzungen“ werden von einigen Lastenausgleichsmodulen auch als „Sitzungsaffinität“ bezeichnet. Azure App Service verwendet Routing von Anwendungsanforderungen (Application Request Routing, ARR) zur Weiterleitung von Anforderungen. Wenn Sie die Einstellung „ARR-Affinität“ in Ihrer Azure App Service-Instanz aktivieren, werden „fixierte Sitzungen“ aktiviert. Dies sind die einzigen Umstände, unter denen fixierte Sitzungen nicht erforderlich sind:

  1. Beim Hosten auf einem einzelnen Server in einem einzigen Prozess.
  2. Bei Verwendung des Azure SignalR-Diensts.
  3. Wenn alle Clients so konfiguriert sind, dass nur WebSockets verwendet wird, und die SkipNegotiation-Einstellung in der Clientkonfiguration aktiviert ist.

In allen anderen Fällen (einschließlich bei Verwendung der Redis-Backplane) muss die Serverumgebung für fixierte Sitzungen konfiguriert werden.

Anleitungen zum Konfigurieren von Azure App Service für SignalR finden Sie unter Veröffentlichen einer SignalR-App von ASP.NET Core in Azure App Service. Anleitungen zum Konfigurieren von fixierten Sitzungen für Blazor-Apps, die den Azure SignalR-Dienst verwenden, finden Sie unter Hosten und Bereitstellen von serverseitigen ASP.NET Core Blazor-Apps.

TCP-Verbindungsressourcen

Die Anzahl gleichzeitiger TCP-Verbindungen, die ein Webserver unterstützen kann, ist begrenzt. HTTP-Standardclients verwenden kurzlebige Verbindungen. Diese Verbindungen können geschlossen werden, wenn der Client in den Leerlauf übergeht und später wieder geöffnet wird. Andererseits ist eine SignalR-Verbindung dauerhaft. SignalR-Verbindungen bleiben auch dann geöffnet, wenn der Client in den Leerlauf übergeht. In einer App mit hohem Datenverkehr, die viele Clients bedient, können diese persistenten Verbindungen dazu führen, dass Server ihre maximale Anzahl von Verbindungen erreichen.

Persistente Verbindungen verbrauchen auch zusätzlichen Arbeitsspeicher, um jede Verbindung nachzuverfolgen.

Die starke Nutzung verbindungsbezogener Ressourcen durch SignalR kann sich auf andere Web-Apps auswirken, die auf demselben Server gehostet werden. Wenn SignalR die letzten verfügbaren TCP-Verbindungen öffnet und aufrecht erhält, sind auch für andere Web-Apps auf demselben Server keine weiteren Verbindungen verfügbar.

Wenn auf einem Server keine Verbindungen mehr vorhanden sind, werden zufällige Socketfehler und Fehler beim Zurücksetzen der Verbindung angezeigt. Beispiel:

An attempt was made to access a socket in a way forbidden by its access permissions...

Um zu vermeiden, dass die Ressourcennutzung durch SignalR Fehler in anderen Web-Apps verursacht, führen Sie SignalR auf anderen Servern als Ihre anderen Web-Apps aus.

Um zu vermeiden, dass die Ressourcennutzung durch SignalR Fehler in einer SignalR-App verursacht, sollten Sie aufskalieren, um die Anzahl der Verbindungen zu begrenzen, die ein Server verarbeiten muss.

Aufskalieren

Eine App, die SignalR verwendet, muss alle ihre Verbindungen nachverfolgen, wodurch Probleme für eine Serverfarm entstehen. Fügen Sie einen Server hinzu, und es werden durch ihn neue Verbindungen abgerufen, die die anderen Server nicht kennen. So weiß beispielsweise SignalR auf jedem Server in der folgenden Abbildung nichts von den Verbindungen auf den anderen Servern. Wenn SignalR auf einem der Server eine Nachricht an alle Clients senden möchte, wird diese Nachricht nur an die mit diesem Server verbundenen Clients übermittelt.

Scaling SignalR without a backplane

Die Optionen zum Lösen dieses Problems sind der Azure SignalR-Dienst und die Redis-Backplane.

Azure SignalR Service

Der Azure SignalR-Dienst fungiert als Proxy für Echtzeitdatenverkehr und dient außerdem als Backplane, wenn die App auf mehrere Server aufskaliert wird. Jedes Mal, wenn ein Client eine Verbindung mit dem Server initiiert, wird der Client umgeleitet, um eine Verbindung mit dem Dienst herzustellen. Der Vorgang wird durch die folgende Abbildung veranschaulicht:

Establishing a connection to the Azure SignalR Service

Das Ergebnis ist, dass der Dienst alle Clientverbindungen verwaltet, während jeder Server nur eine kleine konstante Anzahl von Verbindungen mit dem Dienst benötigt, wie in der folgenden Abbildung gezeigt:

Clients connected to the service, servers connected to the service

Dieser Ansatz zum horizontalen Skalieren hat gegenüber der Redis-Backplanealternative mehrere Vorteile:

  • Fixierte Sitzungen, die auch als Clientaffinität bezeichnet werden, sind nicht erforderlich, da Clients sofort an den Azure SignalR--Dienst weitergeleitet werden, wenn sie eine Verbindung herstellen.
  • Eine SignalR App kann basierend auf der Anzahl der gesendeten Nachrichten aufskaliert werden, während der Azure SignalR--Dienst skaliert wird, um eine beliebige Anzahl von Verbindungen zu verarbeiten. Beispielsweise kann es Tausende von Clients geben, aber wenn nur wenige Nachrichten pro Sekunde gesendet werden, muss die SignalR-App nicht auf mehrere Server aufskaliert werden, nur um die Verbindungen selbst zu verarbeiten.
  • Eine SignalR-App verwendet nicht wesentlich mehr Verbindungsressourcen als eine Web-App ohne SignalR.

Aus diesen Gründen empfehlen wir den Azure SignalR-Dienst für alle SignalR-Apps von ASP.NET Core, die in Azure gehostet werden, einschließlich App Service, VMs und Containern.

Weitere Informationen finden Sie in der Dokumentation zum Azure SignalR-Dienst.

Redis-Backplane

Redis ist ein In-Memory-Schlüssel-Wert-Speicher, der ein Messagingsystem mit einem Veröffentlichungs-/Abonnementmodell unterstützt. Die Redis-Backplane von SignalR verwendet das Veröffentlichungs-/Abonnementfeature, um Nachrichten an andere Server weiterzuleiten. Wenn ein Client eine Verbindung herstellt, werden die Verbindungsinformationen an die Backplane übergeben. Wenn ein Server eine Nachricht an alle Clients senden möchte, sendet er sie an die Backplane. Die Backplane kennt alle verbundenen Clients und die Server, auf denen sie sich befinden. Sie sendet die Nachricht an alle Clients über ihre jeweiligen Server. Dieser Vorgang wird in der folgenden Abbildung gezeigt:

Redis backplane, message sent from one server to all clients

Die Redis-Backplane ist der empfohlene Ansatz für horizontale Skalierung für Apps, die in Ihrer eigenen Infrastruktur gehostet werden. Wenn zwischen Ihrem Rechenzentrum und einem Azure-Rechenzentrum erhebliche Verbindungslatenz besteht, ist der Azure SignalR-Dienst möglicherweise keine praktische Option für lokale Apps mit geringer Latenz oder hohen Durchsatzanforderungen.

Die zuvor erwähnten Vorteile des Azure SignalR-Diensts sind Nachteile für die Redis-Backplane:

  • Fixierte Sitzungen, auch als Client-Affinität bezeichnet, sind nur dann nicht erforderlich, wenn beide der folgenden Punkte zutreffen:
    • Alle Clients sind so konfiguriert, dass nur WebSockets verwendet wird.
    • Die SkipNegotiation-Einstellung ist in der Clientkonfiguration aktiviert. Sobald eine Verbindung auf einem Server initiiert wird, muss die Verbindung auf diesem Server verbleiben.
  • Eine SignalR-App muss basierend auf der Anzahl von Clients aufskaliert werden, auch wenn nur wenige Nachrichten gesendet werden.
  • Eine SignalR-App verwendet nicht wesentlich mehr Verbindungsressourcen als eine Web-App ohne SignalR.

IIS-Einschränkungen unter dem Windows-Clientbetriebssystem

Windows 10 und Windows 8.x sind Clientbetriebssysteme. Für IIS unter Clientbetriebssystemen gilt ein Grenzwert von 10 gleichzeitigen Verbindungen. Für die Verbindungen von SignalR gilt:

  • Sie sind vorübergehend und werden häufig erneut hergestellt.
  • Sie werden nicht sofort entfernt, wenn sie nicht mehr verwendet wird.

Unter den oben genannten Bedingungen ist es wahrscheinlich, dass der Grenzwert von 10 Verbindungen auf einem Clientbetriebssystem erreicht wird. Wenn ein Clientbetriebssystem für die Entwicklung verwendet wird, empfehlen wir Folgendes:

  • Vermeiden Sie IIS.
  • Verwenden Sie Kestrel oder IIS Express als Bereitstellungsziele.

Linux mit Nginx

Im Folgenden finden Sie die mindestens erforderlichen Einstellungen zum Aktivieren von WebSockets, ServerSentEvents und LongPolling für SignalR:

http {
  map $http_connection $connection_upgrade {
    "~*Upgrade" $http_connection;
    default keep-alive;
  }

  server {
    listen 80;
    server_name example.com *.example.com;

    # Configure the SignalR Endpoint
    location /hubroute {
      # App server url
      proxy_pass http://localhost:5000;

      # Configuration for WebSockets
      proxy_set_header Upgrade $http_upgrade;
      proxy_set_header Connection $connection_upgrade;
      proxy_cache off;
      # WebSockets were implemented after http/1.0
      proxy_http_version 1.1;

      # Configuration for ServerSentEvents
      proxy_buffering off;

      # Configuration for LongPolling or if your KeepAliveInterval is longer than 60 seconds
      proxy_read_timeout 100s;

      proxy_set_header Host $host;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header X-Forwarded-Proto $scheme;
    }
  }
}

Wenn mehrere Back-End-Server verwendet werden, müssen fixierte Sitzungen hinzugefügt werden, um zu verhindern, dass SignalR-Verbindungen beim Herstellen einer Verbindung die Server wechseln. Es gibt mehrere Möglichkeiten, fixierte Sitzungen in NGINX hinzuzufügen. Je nachdem, was Sie zur Verfügung haben, werden im Folgenden zwei Ansätze vorgestellt.

Folgendes wird zusätzlich zur vorherigen Konfiguration hinzugefügt. In den folgenden Beispielen ist backend der Name der Gruppe von Servern.

Verwenden Sie mit NGINX Open Sourceip_hash, um Verbindungen basierend auf der IP-Adresse des Clients an einen Server weiterzuleiten:

http {
  upstream backend {
    # App server 1
    server localhost:5000;
    # App server 2
    server localhost:5002;

    ip_hash;
  }
}

Verwenden Sie mit NGINX Plussticky, um Anforderungen ein cookie hinzuzufügen und die Anforderungen des Benutzers an einen Server anzuheften:

http {
  upstream backend {
    # App server 1
    server localhost:5000;
    # App server 2
    server localhost:5002;

    sticky cookie srv_id expires=max domain=.example.com path=/ httponly;
  }
}

Ändern Sie schließlich proxy_pass http://localhost:5000 im Abschnitt server in proxy_pass http://backend.

Weitere Informationen zu WebSockets in NGINX finden Sie unter NGINX als Websocket-Proxy.

Weitere Informationen zum Lastenausgleich und zu fixierten Sitzungen finden Sie unter NGINX-Lastenausgleich.

Weitere Informationen zu ASP.NET Core mit NGINX finden Sie im folgenden Artikel:

SignalR-Backplaneanbieter von Drittanbietern

Nächste Schritte

Weitere Informationen finden Sie in den folgenden Ressourcen: