Events
Nov 19, 11 PM - Nov 21, 11 PM
Join online sessions at Microsoft Ignite created to expand your skills and help you tackle today's complex issues.
Register nowThis browser is no longer supported.
Upgrade to Microsoft Edge to take advantage of the latest features, security updates, and technical support.
By Andrew Stanton-Nurse, Brady Gaster, and Tom Dykstra
This article explains hosting and scaling considerations for high-traffic apps that use ASP.NET Core SignalR.
SignalR requires that all HTTP requests for a specific connection be handled by the same server process. When SignalR is running on a server farm (multiple servers), "sticky sessions" must be used. "Sticky sessions" are also called session affinity. Azure App Service uses Application Request Routing (ARR) to route requests. Enabling the "Session affinity" (ARR Affinity) setting in your Azure App Service enables "sticky sessions." The only circumstances in which sticky sessions aren't required for the app are:
SkipNegotiation
setting is enabled in the client configuration.In all other circumstances (including when the Redis backplane is used), the server environment must be configured for sticky sessions.
For guidance on configuring Azure App Service for SignalR, see Publish an ASP.NET Core SignalR app to Azure App Service. For guidance on configuring sticky sessions for Blazor apps that use the Azure SignalR Service, see Host and deploy ASP.NET Core server-side Blazor apps.
The number of concurrent TCP connections that a web server can support is limited. Standard HTTP clients use ephemeral connections. These connections can be closed when the client goes idle and reopened later. On the other hand, a SignalR connection is persistent. SignalR connections stay open even when the client goes idle. In a high-traffic app that serves many clients, these persistent connections can cause servers to hit their maximum number of connections.
Persistent connections also consume some additional memory, to track each connection.
The heavy use of connection-related resources by SignalR can affect other web apps that are hosted on the same server. When SignalR opens and holds the last available TCP connections, other web apps on the same server also have no more connections available to them.
If a server runs out of connections, you'll see random socket errors and connection reset errors. For example:
An attempt was made to access a socket in a way forbidden by its access permissions...
To keep SignalR resource usage from causing errors in other web apps, run SignalR on different servers than your other web apps.
To keep SignalR resource usage from causing errors in a SignalR app, scale out to limit the number of connections a server has to handle.
An app that uses SignalR needs to keep track of all its connections, which creates problems for a server farm. Add a server, and it gets new connections that the other servers don't know about. For example, SignalR on each server in the following diagram is unaware of the connections on the other servers. When SignalR on one of the servers wants to send a message to all clients, the message only goes to the clients connected to that server.
The options for solving this problem are the Azure SignalR Service and Redis backplane.
The Azure SignalR Service functions as a proxy for real-time traffic and doubles as a backplane when the app is scaled out across multiple servers. Each time a client initiates a connection to the server, the client is redirected to connect to the service. The process is illustrated by the following diagram:
The result is that the service manages all of the client connections, while each server needs only a small constant number of connections to the service, as shown in the following diagram:
This approach to scale-out has several advantages over the Redis backplane alternative:
For these reasons, we recommend the Azure SignalR Service for all ASP.NET Core SignalR apps hosted on Azure, including App Service, VMs, and containers.
For more information see the Azure SignalR Service documentation.
Redis is an in-memory key-value store that supports a messaging system with a publish/subscribe model. The SignalR Redis backplane uses the pub/sub feature to forward messages to other servers. When a client makes a connection, the connection information is passed to the backplane. When a server wants to send a message to all clients, it sends to the backplane. The backplane knows all connected clients and which servers they're on. It sends the message to all clients via their respective servers. This process is illustrated in the following diagram:
The Redis backplane is the recommended scale-out approach for apps hosted on your own infrastructure. If there is significant connection latency between your data center and an Azure data center, Azure SignalR Service may not be a practical option for on-premises apps with low latency or high throughput requirements.
The Azure SignalR Service advantages noted earlier are disadvantages for the Redis backplane:
Windows 10 and Windows 8.x are client operating systems. IIS on client operating systems has a limit of 10 concurrent connections. SignalR's connections are:
The preceding conditions make it likely to hit the 10 connection limit on a client OS. When a client OS is used for development, we recommend:
The following contains the minimum required settings to enable WebSockets, ServerSentEvents, and LongPolling for 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;
}
}
}
When multiple backend servers are used, sticky sessions must be added to prevent SignalR connections from switching servers when connecting. There are multiple ways to add sticky sessions in Nginx. Two approaches are shown below depending on what you have available.
The following is added in addition to the previous configuration. In the following examples, backend
is the name of the group of servers.
With Nginx Open Source, use ip_hash
to route connections to a server based on the client's IP address:
http {
upstream backend {
# App server 1
server localhost:5000;
# App server 2
server localhost:5002;
ip_hash;
}
}
With Nginx Plus, use sticky
to add a cookie to requests and pin the user's requests to a 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;
}
}
Finally, change proxy_pass http://localhost:5000
in the server
section to proxy_pass http://backend
.
For more information on WebSockets over Nginx, see NGINX as a WebSocket Proxy.
For more information on load balancing and sticky sessions, see NGINX load balancing.
For more information about ASP.NET Core with Nginx see the following article:
For more information, see the following resources:
ASP.NET Core feedback
ASP.NET Core is an open source project. Select a link to provide feedback:
Events
Nov 19, 11 PM - Nov 21, 11 PM
Join online sessions at Microsoft Ignite created to expand your skills and help you tackle today's complex issues.
Register nowTraining
Module
Replace client-side polling with ASP.NET Core SignalR - Training
In this module, you use ASP.NET Core SignalR to replace client-side polling functionality in an existing web app.
Documentation
Host ASP.NET Core SignalR in background services
Learn how to send messages to SignalR clients from .NET Core BackgroundService classes.
Publish an ASP.NET Core SignalR app to Azure App Service
Learn how to publish an ASP.NET Core SignalR app to Azure App Service.
To use Redis to distribute messages across a SignalR application that is deployed on two separate IIS instances.