Hospedaje y escalado SignalR en ASP.NET Core

Por Andrew Stanton-Nurse, Brady Gaster y Tom Dykstra,

En este artículo se explican las consideraciones de hospedaje y escalado para aplicaciones de alto tráfico que usan ASP.NET Core SignalR.

Sesiones permanentes

SignalR requiere que el mismo proceso de servidor controle todas las solicitudes HTTP para una conexión específica. Cuando SignalR se ejecuta en una granja de servidores (varios servidores), se deben usar "sesiones permanentes". Algunos equilibradores de carga también denominan a las "sesiones permanentes" como afinidad de sesión. Azure App Service usa enrutamiento de solicitud de aplicaciones (ARR) para enrutar solicitudes. Al habilitar la configuración "Afinidad de ARR" en Azure App Service se habilitarán "sesiones permanentes". Las únicas circunstancias en las que no se requieren sesiones permanentes son las siguientes:

  1. Cuando se hospeda en un solo servidor, en un único proceso.
  2. Cuando se usa el servicio de Azure SignalR.
  3. Cuando todos los clientes están configurados para usar solo WebSockets y la opción SkipNegotiation está habilitada en la configuración del cliente.

En todas las demás circunstancias (incluido cuando se usa el backplane de Redis), el entorno del servidor debe configurarse para sesiones permanentes.

Para obtener instrucciones sobre cómo configurar Azure App Service para SignalR, consulte Publicación de una aplicación de ASP.NET Core SignalR en Azure App Service. Para obtener instrucciones sobre cómo configurar sesiones permanentes para aplicaciones Blazor que usan el servicio SignalR de Azure, consulte Hospedaje e implementación de aplicaciones Blazor del lado servidor de ASP.NET Core.

Recursos de conexión TCP

El número de conexiones TCP simultáneas que un servidor web puede admitir es limitado. Los clientes HTTP estándar usan conexiones efímeras . Estas conexiones se pueden cerrar cuando el cliente deja de estar inactivo y se vuelve a abrir más adelante. Por otro lado, una conexión SignalR es persistente. Las conexiones SignalR permanecen abiertas incluso cuando el cliente deja de estar inactivo. En una aplicación de alto tráfico que atiende a muchos clientes, estas conexiones persistentes pueden hacer que los servidores alcancen su número máximo de conexiones.

Las conexiones persistentes también consumen memoria adicional para supervisar cada conexión.

El uso intensivo de recursos relacionados con la conexión que hace SignalR puede afectar a otras aplicaciones web hospedadas en el mismo servidor. Cuando SignalR se abre y contiene las últimas conexiones TCP disponibles, otras aplicaciones web del mismo servidor tampoco tienen más conexiones disponibles.

Si un servidor se queda sin conexiones, verá errores de socket aleatorio y errores de restablecimiento de conexión. Por ejemplo:

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

Para evitar que el uso de recursos SignalR cause errores en otras aplicaciones web, ejecute SignalR en servidores diferentes de las demás aplicaciones web.

Para evitar que el uso de recursos SignalR cause errores en una aplicación SignalR, escale horizontalmente para limitar el número de conexiones que tiene que controlar un servidor.

Escalado horizontal

Una aplicación que usa SignalR debe supervisar todas sus conexiones, lo que crea problemas para una granja de servidores. Agregue un servidor y obtenga nuevas conexiones que los demás servidores no conocen. Por ejemplo, SignalR en cada servidor del diagrama siguiente no es consciente de las conexiones en los demás servidores. Cuando SignalR en uno de los servidores quiere enviar un mensaje a todos los clientes, el mensaje solo va a los clientes conectados a ese servidor.

Scaling SignalR without a backplane

Las opciones para resolver este problema son el Servicio SignalR de Azure y backplane de Redis.

Azure SignalR Service

El servicio SignalR de Azure funciona como proxy para el tráfico en tiempo real y se duplica como un backplane cuando la aplicación se escala horizontalmente entre varios servidores. Cada vez que un cliente inicia una conexión con el servidor, se redirige al cliente para conectarse al servicio. El proceso se muestra en el diagrama siguiente:

Establishing a connection to the Azure SignalR Service

El resultado es que el servicio administra todas las conexiones de cliente, mientras que cada servidor solo necesita un pequeño número constante de conexiones al servicio, como se muestra en el diagrama siguiente:

Clients connected to the service, servers connected to the service

Este enfoque para el escalado horizontal tiene varias ventajas sobre la alternativa del backplane de Redis:

  • Las sesiones permanentes, también conocidas como afinidad de cliente, no son necesarias, ya que los clientes se redirigen inmediatamente al servicio SignalR de Azure cuando se conectan.
  • Una aplicación SignalR puede escalar horizontalmente en función del número de mensajes enviados, mientras que el servicio SignalR de Azure se escala para controlar cualquier número de conexiones. Por ejemplo, podría haber miles de clientes, pero si solo se envían unos pocos mensajes por segundo, la aplicación SignalR no tendrá que escalar horizontalmente a varios servidores solo para controlar las propias conexiones.
  • Una aplicación SignalR no usará significativamente más recursos de conexión que una aplicación web sin SignalR.

Por estos motivos, se recomienda el servicio SignalR de Azure para todas las aplicaciones SignalR de ASP.NET Core hospedadas en Azure, incluidas las App Service, las máquinas virtuales y los contenedores.

Para más información, consulte la documentación del servicioSignalR de Azure.

Backplane de Redis

Redis es un almacén de clave-valor en memoria que admite un sistema de mensajería con un modelo de publicación y suscripción. El backplane de Redis SignalR usa la característica pub/sub para reenviar mensajes a otros servidores. Cuando un cliente realiza una conexión, la información de conexión se pasa al backplane. Cuando un servidor quiere enviar un mensaje a todos los clientes, envía al backplane. El backplane conoce todos los clientes conectados y los servidores en los que están. Envía el mensaje a todos los clientes a través de sus respectivos servidores. Este proceso se ilustra en el diagrama siguiente:

Redis backplane, message sent from one server to all clients

El backplane de Redis es el enfoque de escalado horizontal recomendado para las aplicaciones hospedadas en su propia infraestructura. Si hay una latencia de conexión significativa entre el centro de datos y un centro de datos de Azure, es posible que el servicio SignalR de Azure no sea una opción práctica para las aplicaciones locales con requisitos de baja latencia o alto rendimiento.

Las ventajas del servicio SignalR de Azure indicadas anteriormente son desventajas del backplane de Redis:

  • Las sesiones permanentes, también conocidas como afinidad de cliente, son necesarias, excepto cuando se cumplen las dos opciones siguientes:
    • Todos los clientes están configurados para usar solo WebSockets.
    • La opción SkipNegotiation está habilitada en la configuración del cliente. Una vez iniciada una conexión en un servidor, la conexión debe permanecer en ese servidor.
  • Una aplicación SignalR debe escalar horizontalmente en función del número de clientes, incluso si se envían pocos mensajes.
  • Una aplicación SignalR usa significativamente más recursos de conexión que una aplicación web sin SignalR.

Limitaciones de IIS en el sistema operativo cliente Windows

Sistemas operativos cliente de Windows 10 y Windows 8. IIS en sistemas operativos cliente tiene un límite de 10 conexiones simultáneas. Las conexiones de SignalR son las siguientes:

  • Transitoria y se vuelve a establecer con frecuencia.
  • No se elimina inmediatamente cuando ya no se usa.

Las condiciones anteriores hacen que sea probable que alcance el límite de 10 conexiones en un sistema operativo cliente. Cuando se usa un sistema operativo cliente para el desarrollo, se recomienda:

  • Evitar IIS.
  • Usar Kestrel o IIS Express como destinos de implementación.

Linux con Nginx

A continuación se incluyen los valores mínimos necesarios para habilitar WebSockets, ServerSentEvents y LongPolling para 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;
    }
  }
}

Cuando se usan varios servidores backend, se deben agregar sesiones permanentes para evitar que las conexiones SignalR cambien de servidores al conectarse. Hay varias maneras de agregar sesiones permanentes en Nginx. A continuación se muestran dos enfoques en función de lo que tenga disponible.

Se agrega lo siguiente además de la configuración anterior. En los ejemplos siguientes, backend es el nombre del grupo de servidores.

Con Nginx Open Source, use ip_hash para enrutar las conexiones a un servidor en función de la dirección IP del cliente:

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

    ip_hash;
  }
}

Con Nginx Plus, use sticky para agregar a cookie las solicitudes y anclar las solicitudes del usuario a un servidor:

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

Por último, cambie proxy_pass http://localhost:5000 en la sección server a proxy_pass http://backend.

Para obtener más información acerca de WebSockets sobre Nginx, consulte NGINX como proxy de WebSocket.

Para obtener más información sobre el equilibrio de carga y las sesiones permanentes, consulte Equilibrio de carga de NGINX.

Para obtener más información sobre ASP.NET Core con Nginx, consulte el artículo siguiente:

Proveedores de backplane de terceros SignalR

Pasos siguientes

Para obtener más información, vea los siguientes recursos: