Currently we add support for configuring multiple SignalR Service instances. You can distribute your clients to multiple SignalR service instances and send messages to multiple instances as if to one instance.
Routing logic is the way to decide to which SignalR Service instance among multiple instances your clients connect and your messages send. By applying different routing logic, this feature can be used in different scenarios.
- Scaling. Randomly route each client to one SignalR Service instance, send messages to all the SignalR Service instances so that you can scale the concurrent connections.
- Cross-geo scenario. Cross-geo networks can be comparatively unstable. Route your clients to a SignalR Service instance in the same region can reduce cross-geo connections.
- High availability and disaster recovery scenarios. Set up multiple service instances in different regions, so when one region is down, the others can be used as backup. Configure service instances as two roles, primary and secondary. By default, clients will be routed to a primary online instance. When SDK detects all the primary instances are down, it will route clients to secondary instances. Clients connected before will experience connection drops when there is a disaster and failover take place. You'll need to handle such cases at client side to make it transparent to your end customers. For example, do reconnect after a connection is closed.
Currently multiple-endpoint feature is only supported on Persistent
transport type.
To enable multiple SignalR Service instances, you should:
-
Use
Persistent
transport type.The default transport type is
Transient
mode. You should add the following entry to yourlocal.settings.json
file or the application setting on Azure.{ "AzureSignalRServiceTransportType":"Persistent" }
Notes for switching from
Transient
mode toPersistent
mode on Azure Functions runtime V3 :Under
Transient
mode,Newtonsoft.Json
library is used to serialize arguments of hub methods, however, underPersistent
mode,System.Text.Json
library is used as default on Azure Functions runtime V3.System.Text.Json
has some key differences in default behavior withNewtonsoft.Json
. If you want to useNewtonsoft.Json
underPersistent
mode, you can add a configuration item:"Azure:SignalR:HubProtocol":"NewtonsoftJson"
inlocal.settings.json
file orAzure__SignalR__HubProtocol=NewtonsoftJson
on Azure portal. -
Configure multiple SignalR Service endpoints entries in your configuration.
We use a
ServiceEndpoint
object to represent a SignalR Service instance. You can define an service endpoint with its<EndpointName>
and<EndpointType>
in the entry key, and the connection string in the entry value. The keys are in the following format :Azure:SignalR:Endpoints:<EndpointName>:<EndpointType>
<EndpointType>
is optional and defaults toprimary
. See samples below:{ "Azure:SignalR:Endpoints:EastUs":"<ConnectionString>", "Azure:SignalR:Endpoints:EastUs2:Secondary":"<ConnectionString>", "Azure:SignalR:Endpoints:WestUs:Primary":"<ConnectionString>" }
-
When you configure Azure SignalR endpoints in the App Service on Azure portal, don't forget to replace
":"
with"__"
, the double underscore in the keys. For reasons, see Environment variables. -
Connection string configured with the key
{ConnectionStringSetting}
(defaults to "AzureSignalRConnectionString") is also recognized as a primary service endpoint with empty name. But this configuration style is not recommended for multiple endpoints.
-
By default, the SDK uses the DefaultEndpointRouter to pick up endpoints.
-
Client routing: Randomly select one endpoint from primary online endpoints. If all the primary endpoints are offline, then randomly select one secondary online endpoint. If the selection fails again, then exception is thrown.
-
Server message routing: All service endpoints are returned.
Here are the steps:
-
Implement a customized router. You can leverage information provided from
ServiceEndpoint
to make routing decision. See guide here: customize-route-algorithm. Please note that Http trigger is required in the negotiation function when you needHttpContext
in custom negotiation method. -
Register the router to DI container.
using Microsoft.Azure.Functions.Extensions.DependencyInjection;
using Microsoft.Azure.SignalR;
using Microsoft.Extensions.DependencyInjection;
[assembly: FunctionsStartup(typeof(SimpleChatV3.Startup))]
namespace SimpleChatV3
{
public class Startup : FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
builder.Services.AddSingleton<IEndpointRouter, CustomizedRouter>();
}
}
}
For languages other than C#, we support specifying target endpoints in each request. You will use new binding types to get endpoint information.
The SignalRConnectionInfo
binding selects one endpoint according the default routing rule. If you want to customize routing rule, you should use SignalRNegotiation
binding instead of SignalRConnectionInfo
binding.
SignalRNegotiation
binding configuration properties are the same as SignalRConnectionInfo
. Here's a function.json
file sample:
{
"type": "signalRNegotiation",
"name": "negotiationContext",
"hubName": "<HubName>",
"direction": "in"
}
You could also add other binding data such as userId
, idToken
and claimTypeList
just like SignalRConnectionInfo
.
The object you get from SignalRNegotiation
binding is in the following format:
{
"endpoints": [
{
"endpointType": "Primary",
"name": "<EndpointName>",
"endpoint": "https://****.service.signalr.net",
"online": true,
"connectionInfo": {
"url": "<client-access-url>",
"accessToken": "<client-access-token>"
}
},
{
"...": "..."
}
]
}
Here's a Javascript usage sample of SignalRNegotiation
binding:
module.exports = function (context, req, negotiationContext) {
var userId = req.query.userId;
if (userId.startsWith("east-")) {
//return the first endpoint whose name starts with "east-" and status is online.
context.res.body = negotiationContext.endpoints.find(endpoint => endpoint.name.startsWith("east-") && endpoint.online).connectionInfo;
}
else {
//return the first online endpoint
context.res.body = negotiationContext.endpoints.filter(endpoint => endpoint.online)[0].connectionInfo;
}
}
Messages or actions routing needs two binding types to cooperate. In general, firstly you need a new input binding type SignalREndpoints
to get all the available endpoint information. Then you filter the endpoints and get an array containing all the endpoints that you want to send to. Lastly you specify the target endpoints in the SignalR
output binding.
Here's the SignalREndpoints
binding configuration properties in functions.json
file:
{
"type": "signalREndpoints",
"direction": "in",
"name": "endpoints",
"hubName": "<HubName>"
}
The object you get from SignalREndpoints
is an array of endpoints each of which is represented as a JSON object with the following schema:
{
"endpointType": "<EndpointType>",
"name": "<EndpointName>",
"endpoint": "https://****.service.signalr.net",
"online": true
}
After you get the target endpoint array, add an endpoints
property to the output binding object. This is a Javascript example:
module.exports = function (context, req, endpoints) {
var targetEndpoints = endpoints.filter(endpoint => endpoint.name.startsWith("east-"));
context.bindings.signalRMessages = [{
"target": "chat",
"arguments": ["hello-world"],
"endpoints": targetEndpoints,
}];
context.done();
}