hosting e scalabilità di ASP.NET Core SignalR

Di Andrew Stanton-Nurse, Brady Gaster e Tom Dykstra

Questo articolo illustra le considerazioni sull'hosting e sul ridimensionamento per le app a traffico elevato che usano ASP.NET Core SignalR.

Sessioni persistenti

SignalR richiede lo stesso processo del server per gestire tutte le richieste HTTP per una connessione specifica. Quando SignalR viene eseguito in una server farm (più server), è necessario usare le "sessioni permanenti". Le "Sticky sessions" sono dette anche affinità di sessione. Servizio app di Azure usa Microsoft Application Request Routing (ARR) per instradare le richieste. L'abilitazione dell'impostazione "Affinità di sessione" (Affinità ARR) nell'app di App Service consente di usare sessioni permanenti.

Esistono tre scenari in cui le sessioni permanenti non sono necessarie per un'app:

  • Hosting su un singolo server in un singolo processo
  • Usando il servizio Azure SignalR (le sessioni permanenti sono abilitate per il servizio, non per l'app)
  • Tutti i client sono configurati per l'uso solo di WebSocket e la configurazione client abilita SkipNegotiation

In tutti gli altri scenari (incluso quando viene usato il backplane Redis), l'ambiente server deve essere configurato per le sessioni permanenti.

Per ottenere indicazioni sulla configurazione di Servizio app di Azure per SignalR, vedere Pubblicare un'app ASP.NET Core SignalR su Servizio app di Azure. Per indicazioni sulla configurazione di sessioni permanenti per le app Blazor che usano il Servizio SignalR di Azure, vedere Blazor.

Risorse di connessione TCP

Il numero di connessioni TCP simultanee supportate da un server Web è limitato. I client HTTP standard usano connessioni temporanee . Queste connessioni possono essere chiuse quando il client diventa inattivo e riaperte in un secondo momento. D'altra parte, una SignalR connessione è persistente. SignalR le connessioni rimangono aperte anche quando il client diventa inattivo. In un'app a traffico elevato che serve molti client, queste connessioni persistenti possono causare il raggiungimento del numero massimo di connessioni dei server.

Le connessioni permanenti utilizzano anche memoria aggiuntiva per tenere traccia di ogni connessione.

L'uso elevato delle risorse SignalR correlate alla connessione può influire su altre app Web ospitate nello stesso server. Quando SignalR si apre e contiene le ultime connessioni TCP disponibili, anche altre app Web nello stesso server non dispongono di altre connessioni.

Se un server esaurisce le connessioni, vengono visualizzati errori di socket casuali e errori di reimpostazione della connessione. Ad esempio:

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

Per evitare SignalR che l'utilizzo delle risorse causi errori in altre app Web, eseguire SignalR su server diversi rispetto alle altre app Web.

Per evitare che l'utilizzo delle risorse causi errori in un'app SignalR, aumentare la scalabilità per limitare il numero di connessioni che un server deve gestire.

Aumentare il numero di istanze

Un'app che usa SignalR deve tenere traccia di tutte le connessioni, che crea problemi per una server farm. Aggiungere un server e ottenere nuove connessioni che gli altri server non conoscono. Ad esempio, SignalR in ogni server del diagramma seguente non è a conoscenza delle connessioni negli altri server. Quando SignalR in uno dei server vuole inviare un messaggio a tutti i client, il messaggio passa solo ai client connessi a tale server.

Illustrazione che mostra la scalabilità SignalR senza un backplane.

Le opzioni per risolvere questo problema sono il SignalR di Azure e il backplane Redis.

Servizio di Azure SignalR

Il servizio di Azure SignalR funziona come proxy per il traffico in tempo reale e raddoppia come backplane quando l'app viene ridimensionata in più server. Ogni volta che un client avvia una connessione al server, il client viene reindirizzato per connettersi al servizio. Il diagramma seguente illustra questo processo:

Illustration che illustra la creazione di una connessione al Azure SignalR Service.

Il risultato è che il servizio gestisce tutte le connessioni client, mentre ogni server necessita solo di un numero costante ridotto di connessioni al servizio, come illustrato nel diagramma seguente:

Figura che illustra i client e i server connessi al servizio.

Questo approccio alla scalabilità orizzontale presenta diversi vantaggi rispetto all'alternativa del backplane Redis:

  • Le sessioni permanenti, note anche come affinità client, non sono necessarie perché i client vengono reindirizzati immediatamente al servizio Azure SignalR quando si connettono.
  • Un'app SignalR può scalare orizzontalmente in base al numero di messaggi inviati, mentre il servizio di Azure SignalR si scala per gestire un numero qualsiasi di connessioni. Ad esempio, potrebbero esserci migliaia di client, ma se si inviano solo pochi messaggi al secondo, l'app SignalR non deve scalare orizzontalmente su più server solo per gestire le connessioni stesse.
  • Un'app SignalR non usa molte più risorse di connessione rispetto a un'app Web senza SignalR.

Per questi motivi, è consigliabile usare il servizio Azure SignalR per tutte le app ASP.NET Core SignalR ospitate in Azure, inclusi servizio app, macchine virtuali e contenitori.

Per altre informazioni, vedere la documentazione Azure SignalR Service.

Redis Backplane

Redis è un archivio chiave-valore in memoria che supporta un sistema di messaggistica con un modello di pubblicazione/sottoscrizione. Il backplane Redis SignalR usa la funzionalità di pubblicazione/sottoscrizione per inoltrare i messaggi ad altri server. Quando un client effettua una connessione, le informazioni di connessione vengono passate al backplane. Quando un server vuole inviare un messaggio a tutti i client, lo invia al backplane. Il backplane conosce tutti i client connessi e i server in cui si trovano. Invia il messaggio a tutti i client tramite i rispettivi server. Questo processo è illustrato nel diagramma seguente:

Figura che illustra il backplane Redis con un messaggio inviato da un server a tutti i client.

Il backplane Redis è l'approccio di scalabilità orizzontale consigliato per le app ospitate nella propria infrastruttura. Se esiste una latenza di connessione significativa tra il data center e un data center Azure, Azure SignalR Service potrebbe non essere un'opzione pratica per le app locali con bassa latenza o requisiti di velocità effettiva elevata.

I vantaggi del servizio Azure SignalR descritti in precedenza sono svantaggi per il backplane Redis:

  • Le sessioni permanenti, note anche come affinità al client, sono obbligatoria, tranne quando entrambe le seguenti sono vere:
    • Tutti i client sono configurati per l'uso solo di WebSocket.
    • L'impostazione SkipNegotiation è abilitata nella configurazione client. Dopo l'avvio di una connessione in un server, la connessione deve rimanere su tale server.
  • Un'app SignalR deve scalare orizzontalmente in funzione del numero di client, anche quando vengono inviati pochi messaggi.
  • Un'app SignalR usa molte più risorse di connessione rispetto a un'app Web senza SignalR.

Limitazioni di IIS nel sistema operativo client Windows

Windows 10 e Windows 8.x sono sistemi operativi client. Internet Information Services (IIS) nei sistemi operativi client ha un limite di 10 connessioni simultanee. Le SignalR connessioni presentano le caratteristiche seguenti:

  • Sono temporanei e spesso ristabiliti.
  • Non vengono eliminati immediatamente quando non vengono più usati.

Queste caratteristiche consentono di raggiungere il limite di 10 connessioni in un sistema operativo client. Quando si usa un sistema operativo client per lo sviluppo, prendere in considerazione le raccomandazioni seguenti:

  • Evitare IIS
  • Usare Kestrel o IIS Express come destinazioni di distribuzione

Linux con Nginx

Il codice seguente contiene le impostazioni minime necessarie per abilitare WebSockets, ServerSentEvents e LongPolling per 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;
    }
  }
}

Quando si usano più server back-end, è necessario aggiungere sessioni permanenti per impedire SignalR alle connessioni di cambiare server durante la connessione. Esistono diversi modi per aggiungere sessioni permanenti in Nginx. Gli esempi seguenti illustrano due approcci in base a ciò che è disponibile.

Il codice seguente integra la configurazione di esempio precedente. Nei frammenti di codice backend è il nome del gruppo di server.

  • Con Nginx Open Source, usare ip_hash per instradare le connessioni a un server in base all'indirizzo IP del client:

    http {
       upstream backend {
         # App server 1
         server localhost:5000;
         # App server 2
         server localhost:5002;
    
         ip_hash;
       }
    }
    
  • Con Nginx Plus, usa sticky per aggiungere un cookie alle richieste e vincolare le richieste degli utenti a un server:

    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;
       }
    }
    
  • Per entrambe le configurazioni, modificare proxy_pass http://localhost:5000 nella server sezione in proxy_pass http://backend.

Per altre informazioni, vedere Host ASP.NET Core in Linux con Nginx.

Altri SignalR provider di backplane

I provider non Microsoft seguenti offrono anche SignalR backplane: