Scale out SignalR: Sending messages locally and through a backplane

Scale out is one of the key things to think about when designing your application. SignalR has a mechanism for handling scale out by forwarding messages among servers through a backplane. When a message is sent, it goes through the backplane. There are no exceptions, every message has to pass through the backplane. So, the backplane can become a bottleneck. The solution is to send the messages locally for the users that are connected to the same server, and use the backplane only for communicating with users that are connected on a different server. We’ll take a chat application for an example.

Use of the SignalR instance on Server A and Server B

The idea is to use the SignalR instance on Server A and Server B as a client to a separate SignalR server (in this example it is a self-hosted application) that will use Redis backplane. You need to create a custom message bus that will be used to replace the default message bus in the application that is hosted on servers A and B.  If the recipient is not connected on the same server, the message details will be sent to the SignalR server with the Redis backplane, and from there the message details will be forwarded to the recipient where in the custom message bus the message will be rebuilt from the message details. If the recipient is connected on the same server, the message won’t be forwarded to the SignalR server with Redis backplane.

Create a CustomBackplane class that inherits from the MessageBus class. In our case, we can’t use the ScaleoutMessageBus because it requires a configuration object.  Since we are using the custom message bus only to separate the messages that need to be sent locally from those that need to be sent to another server instance, we have no use of the configuration object.

In the custom message bus add the following fields and a URL pointing to the SignalR instance with a Redis backplane.

In the constructor create a new hub connection and a hub proxy.

And next we initiate the connection.


The key part is to override the publishing logic.  We can get the UserId and ConnectionId values from the key. It’s used in format “hu-“ for hub UserId and “hc-“ for a hub ConnectionId followed by a period character.


On the Hub in the SignalR server with Redis backplane define a method Send that will receive the message details and call a method SendMessage on the client (it’s a method on the hub proxy that we created in the custom message bus).

Next register to the SendMessage event in the custom message bus constructor and specify a callback where the message details are used to build the message object and publish it locally.


To test which messages pass through Redis, the redis-cli.exe tool can be used to subscribe to the channel.

 

Conclusion

This solution allows the application to scale as the rate of messages grows proportionally with the number of chat users. Only a subset of messages would be forwarded to the backplane, so it wouldn’t become a bottleneck. It also decreases the resource usage, whether you are using resources on premises or in the cloud. Also, messages will be delivered faster since the messages won’t travel to the backplane if not necessary, and the backplane won’t be overloaded with messages without a need.

This way, we have used the backplane mechanism as part of the bigger picture. It allowed us to handle a high-traffic scenario at high peeks where a large number of users are sending even larger number of messages.