Hébergement et mise à l'échelle ASP.NET Core SignalR

Par Andrew Stanton-Nurse, Brady Gaster et Tom Dykstra

Cet article explique les considérations d'hébergement et de mise à l'échelle pour les applications à fort trafic qui utilisent ASP.NET Core SignalR.

Séances collantes

SignalR requiert que toutes les requêtes HTTP pour une connexion spécifique soient gérées par le même processus serveur. Lorsque SignalR s'exécute sur une batterie de serveurs (plusieurs serveurs), des "sessions persistantes" doivent être utilisées. Les "sessions persistantes" sont également appelées affinité de session par certains équilibreurs de charge. Azure App Service utilise Application Request Routing (ARR) pour acheminer les requêtes. L'activation du paramètre "ARR Affinity" dans votre Azure App Service activera les "sessions permanentes". Les seules circonstances dans lesquelles les sessions persistantes ne sont pas nécessaires sont :

  1. Lors de l'hébergement sur un seul serveur, en un seul processus.
  2. Lors de l'utilisation du service Azure SignalR.
  3. Lorsque tous les clients sont configurés pour utiliser uniquement WebSockets et que le paramètre SkipNegotiation est activé dans la configuration du client.

Dans toutes les autres circonstances (y compris lorsque le fond de panier Redis est utilisé), l'environnement du serveur doit être configuré pour les sessions permanentes.

Pour obtenir des conseils sur la configuration d'Azure App Service pour SignalR, consultez Publier une application SignalR ASP.NET Core sur Azure App Service. Pour obtenir des conseils sur la configuration de sessions permanentes pour les applications Blazor qui utilisent le service SignalR Azure, consultez Héberger et déployer des applications Blazor ASP.NET Core côté serveur.

Ressources de connexion TCP

Le nombre de connexions TCP simultanées qu'un serveur Web peut prendre en charge est limité. Les clients HTTP Standard utilisent des connexions éphémères. Ces connexions peuvent être fermées lorsque le client devient inactif et rouvertes ultérieurement. En revanche, une connexion SignalR est persistante. les connexions SignalR restent ouvertes même lorsque le client devient inactif. Dans une application à fort trafic qui dessert de nombreux clients, ces connexions persistantes peuvent amener les serveurs à atteindre leur nombre maximal de connexions.

Les connexions persistantes consomment également de la mémoire supplémentaire pour suivre chaque connexion.

L'utilisation intensive des ressources liées à la connexion SignalR peut affecter d'autres applications Web hébergées sur le même serveur. Lorsque SignalR s'ouvre et maintient les dernières connexions TCP disponibles, les autres applications Web sur le même serveur n'ont plus de connexions disponibles.

Si un serveur manque de connexions, vous verrez des erreurs de socket aléatoires et des erreurs de réinitialisation de connexion. Par exemple :

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

Pour éviter que l'utilisation des ressources SignalR ne provoque des erreurs dans d'autres applications Web, exécutez SignalR sur des serveurs différents de ceux de vos autres applications Web.

Pour éviter que l'utilisation des ressources SignalR ne provoque des erreurs dans une application SignalR, effectuez une mise à l'échelle pour limiter le nombre de connexions qu'un serveur doit gérer.

Scale-out

Une application qui utilise SignalR doit garder une trace de toutes ses connexions, ce qui crée des problèmes pour une batterie de serveurs. Ajoutez un serveur et il obtient de nouvelles connexions que les autres serveurs ne connaissent pas. Par exemple, SignalR sur chaque serveur du diagramme suivant, il ignore les connexions sur les autres serveurs. Lorsque SignalR sur un des serveurs veut envoyer un message à tous les clients, le message ne va qu'aux clients connectés à ce serveur.

Scaling SignalR without a backplane

Les options pour résoudre ce problème sont le fond de panier Azure SignalR Service et Redis.

Service Azure SignalR

Le service SignalR Azure fonctionne comme un proxy pour le trafic en temps réel et sert également de fond de panier lorsque l'application est déployée sur plusieurs serveurs. Chaque fois qu'un client initie une connexion au serveur, le client est redirigé pour se connecter au service. Le processus est illustré par le schéma suivant :

Establishing a connection to the Azure SignalR Service

Le résultat est que le service gère toutes les connexions client, tandis que chaque serveur n'a besoin que d'un petit nombre constant de connexions au service, comme illustré dans le schéma suivant :

Clients connected to the service, servers connected to the service

Cette approche de scale-out présente plusieurs avantages par rapport à l'alternative de fond de panier Redis :

  • Les sessions permanentes, également appelées affinité client, ne sont pas requises, car les clients sont immédiatement redirigés vers le service SignalR Azure lorsqu'ils se connectent.
  • Une application SignalR peut évoluer en fonction du nombre de messages envoyés, tandis que le service SignalR Azure évolue pour gérer n'importe quel nombre de connexions. Par exemple, il peut y avoir des milliers de clients, mais si seulement quelques messages par seconde sont envoyés, l'application SignalR n'aura pas besoin d'évoluer vers plusieurs serveurs uniquement pour gérer les connexions elles-mêmes.
  • Une application SignalR n'utilisera pas beaucoup plus de ressources de connexion qu'une application Web sans SignalR.

Pour ces raisons, nous recommandons le service SignalR Azure pour toutes les applications SignalR ASP.NET Core hébergées sur Azure, y compris App Service, les machines virtuelles et les conteneurs.

Pour plus d'informations, consultez la documentation du service SignalR Azure.

Fond de panier Redis

Redis est un magasin clé-valeur en mémoire qui prend en charge un système de messagerie avec un modèle de publication/abonnement. Le fond de panier Redis SignalR utilise la fonction pub/sub pour transférer les messages vers d'autres serveurs. Lorsqu'un client établit une connexion, les informations de connexion sont transmises au fond de panier. Lorsqu'un serveur veut envoyer un message à tous les clients, il l'envoie au fond de panier. Le fond de panier connaît tous les clients connectés et sur quels serveurs ils se trouvent. Il envoie le message à tous les clients via leurs serveurs respectifs. Ce processus est illustré dans le schéma suivant :

Redis backplane, message sent from one server to all clients

Le fond de panier Redis est l'approche scale-out recommandée pour les applications hébergées sur votre propre infrastructure. S'il existe une latence de connexion importante entre votre centre de données et un centre de données Azure, le service SignalR Azure peut ne pas être une option pratique pour les applications locales avec des exigences de faible latence ou de débit élevé.

Les avantages du service SignalR Azure mentionnés précédemment sont des inconvénients pour le fond de panier Redis :

  • Les sessions persistantes, également appelées affinité client, sont requises, sauf lorsque les deux conditions suivantes sont remplies :
    • Tous les clients sont configurés pour utiliser uniquement WebSockets.
    • Le paramètre SkipNegotiation est activé dans la configuration du client. Une fois qu'une connexion est initiée sur un serveur, la connexion doit rester sur ce serveur.
  • Une application SignalR doit évoluer en fonction du nombre de clients, même si peu de messages sont envoyés.
  • Une application SignalR utilise beaucoup plus de ressources de connexion qu'une application Web sans SignalR.

Limitations d'IIS sur le système d'exploitation client Windows

Windows 10 et Windows 8.x sont des systèmes d'exploitation clients. IIS sur les systèmes d'exploitation clients a une limite de 10 connexions simultanées. Les connexions de SignalR sont :

  • Transitoire et fréquemment rétablie.
  • Ne pas jeter immédiatement lorsqu'il n'est plus utilisé.

Les conditions précédentes le rendent susceptible d'atteindre la limite de 10 connexions sur un système d'exploitation client. Lorsqu'un système d'exploitation client est utilisé pour le développement, nous recommandons :

  • Évitez IIS.
  • Utilisez Kestrel ou IIS Express comme cibles de déploiement.

Linux avec Nginx

Ce qui suit contient les paramètres minimum requis pour activer WebSockets, ServerSentEvents et LongPolling pour 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;
    }
  }
}

Lorsque plusieurs serveurs principaux sont utilisés, des sessions persistantes doivent être ajoutées pour empêcher les connexions SignalR de changer de serveur lors de la connexion. Il existe plusieurs façons d'ajouter des sessions persistantes dans Nginx. Deux approches sont présentées ci-dessous en fonction de ce dont vous disposez.

Ce qui suit est ajouté en plus de la configuration précédente. Dans les exemples suivants, backend est le nom du groupe de serveurs.

Avec Nginx Open Source, utilisez ip_hash pour router les connexions vers un serveur en fonction de l'adresse IP du client :

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

    ip_hash;
  }
}

Avec Nginx Plus, utilisez sticky pour ajouter un cookie aux requêtes et épingler les requêtes de l'utilisateur sur un serveur :

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;
  }
}

Enfin, changez proxy_pass http://localhost:5000 la section server en proxy_pass http://backend.

Pour plus d'informations sur WebSockets sur Nginx, consultez NGINX en tant que proxy WebSocket.

Pour plus d'informations sur l'équilibrage de charge et les sessions permanentes, consultez Équilibrage de charge NGINX.

Pour plus d'informations sur ASP.NET Core avec Nginx, consultez l'article suivant :

Fournisseurs de fond de panier tiers SignalR

Étapes suivantes

Pour plus d’informations, consultez les ressources suivantes :