如何使用多个实例扩展 SignalR 服务?How to scale SignalR Service with multiple instances?

最新的 SignalR 服务 SDK 支持对 SignalR 服务实例使用多个终结点。The latest SignalR Service SDK supports multiple endpoints for SignalR Service instances. 可以使用此功能来扩展并发连接,或将其用于跨区域的消息传送。You can use this feature to scale the concurrent connections, or use it for cross-region messaging.

对于 ASP.NET CoreFor ASP.NET Core

如何通过配置添加多个终结点?How to add multiple endpoints from config?

使用 SignalR 服务连接字符串的密钥 Azure:SignalR:ConnectionStringAzure:SignalR:ConnectionString: 进行配置。Config with key Azure:SignalR:ConnectionString or Azure:SignalR:ConnectionString: for SignalR Service connection string.

如果密钥以 Azure:SignalR:ConnectionString:开头,则它应采用 Azure:SignalR:ConnectionString:{Name}:{EndpointType} 格式,其中,NameEndpointTypeServiceEndpoint 对象的属性(可从代码访问)。If the key starts with Azure:SignalR:ConnectionString:, it should be in format Azure:SignalR:ConnectionString:{Name}:{EndpointType}, where Name and EndpointType are properties of the ServiceEndpoint object, and are accessible from code.

可以使用以下 dotnet 命令添加多个实例连接字符串:You can add multiple instance connection strings using the following dotnet commands:

dotnet user-secrets set Azure:SignalR:ConnectionString:east-region-a <ConnectionString1>
dotnet user-secrets set Azure:SignalR:ConnectionString:east-region-b:primary <ConnectionString2>
dotnet user-secrets set Azure:SignalR:ConnectionString:backup:secondary <ConnectionString3>

如何通过代码添加多个终结点?How to add multiple endpoints from code?

我们已引入 ServicEndpoint 类来描述 Azure SignalR 服务终结点的属性。A ServicEndpoint class is introduced to describe the properties of an Azure SignalR Service endpoint. 使用 Azure SignalR 服务 SDK 时,可通过以下代码配置多个实例终结点:You can configure multiple instance endpoints when using Azure SignalR Service SDK through:

services.AddSignalR()
        .AddAzureSignalR(options => 
        {
            options.Endpoints = new ServiceEndpoint[]
            {
                // Note: this is just a demonstration of how to set options.Endpoints
                // Having ConnectionStrings explicitly set inside the code is not encouraged
                // You can fetch it from a safe place such as Azure KeyVault
                new ServiceEndpoint("<ConnectionString0>"),
                new ServiceEndpoint("<ConnectionString1>", type: EndpointType.Primary, name: "east-region-a"),
                new ServiceEndpoint("<ConnectionString2>", type: EndpointType.Primary, name: "east-region-b"),
                new ServiceEndpoint("<ConnectionString3>", type: EndpointType.Secondary, name: "backup"),
            };
        });

如何自定义终结点路由器?How to customize endpoint router?

默认情况下,SDK 使用 DefaultEndpointRouter 来选取终结点。By default, the SDK uses the DefaultEndpointRouter to pick up endpoints.

默认行为Default behavior

  1. 客户端请求路由Client request routing

    当客户端通过 /negotiate 与应用服务器协商时,When client /negotiate with the app server. SDK 默认会从可用服务终结点集内随机选择一个终结点。By default, SDK randomly selects one endpoint from the set of available service endpoints.

  2. 服务器消息路由Server message routing

    向特定的连接发送消息时,如果目标连接路由到当前服务器,则消息将直接转到已连接的该终结点。When *sending message to a specific **connection***, and the target connection is routed to current server, the message goes directly to that connected endpoint. 否则,消息将广播到每个 Azure SignalR 终结点。Otherwise, the messages are broadcasted to every Azure SignalR endpoint.

自定义路由算法Customize routing algorithm

如果你具备专业的知识,可以识别消息会转到哪些终结点,则可以创建自己的路由器。You can create your own router when you have special knowledge to identify which endpoints the messages should go to.

下面定义了一个自定义路由器示例,演示以 east- 开头的组的消息始终转到名为 east 的终结点:A custom router is defined below as an example when groups starting with east- always go to the endpoint named east:

private class CustomRouter : EndpointRouterDecorator
{
    public override IEnumerable<ServiceEndpoint> GetEndpointsForGroup(string groupName, IEnumerable<ServiceEndpoint> endpoints)
    {
        // Override the group broadcast behavior, if the group name starts with "east-", only send messages to endpoints inside east
        if (groupName.StartsWith("east-"))
        {
            return endpoints.Where(e => e.Name.StartsWith("east-"));
        }

        return base.GetEndpointsForGroup(groupName, endpoints);
    }
}

下面提供了另一个示例,它替代了默认的协商行为,根据应用服务器所在的位置选择终结点。Another example below, that overrides the default negotiate behavior, to select the endpoints depends on where the app server is located.

private class CustomRouter : EndpointRouterDecorator
{
    public override ServiceEndpoint GetNegotiateEndpoint(HttpContext context, IEnumerable<ServiceEndpoint> endpoints)
    {
        // Override the negotiate behavior to get the endpoint from query string
        var endpointName = context.Request.Query["endpoint"];
        if (endpointName.Count == 0)
        {
            context.Response.StatusCode = 400;
            var response = Encoding.UTF8.GetBytes("Invalid request");
            context.Response.Body.Write(response, 0, response.Length);
            return null;
        }

        return endpoints.FirstOrDefault(s => s.Name == endpointName && s.Online) // Get the endpoint with name matching the incoming request
               ?? base.GetNegotiateEndpoint(context, endpoints); // Or fallback to the default behavior to randomly select one from primary endpoints, or fallback to secondary when no primary ones are online
    }
}

请不要忘记使用以下代码将路由器注册到 DI 容器:Don't forget to register the router to DI container using:

services.AddSingleton(typeof(IEndpointRouter), typeof(CustomRouter));
services.AddSignalR()
        .AddAzureSignalR(
            options => 
            {
                options.Endpoints = new ServiceEndpoint[]
                {
                    new ServiceEndpoint(name: "east", connectionString: "<connectionString1>"),
                    new ServiceEndpoint(name: "north", connectionString: "<connectionString2>"),
                    new ServiceEndpoint("<connectionString3>")
                };
            });

对于 ASP.NETFor ASP.NET

如何通过配置添加多个终结点?How to add multiple endpoints from config?

使用 SignalR 服务连接字符串的密钥 Azure:SignalR:ConnectionStringAzure:SignalR:ConnectionString: 进行配置。Config with key Azure:SignalR:ConnectionString or Azure:SignalR:ConnectionString: for SignalR Service connection string.

如果密钥以 Azure:SignalR:ConnectionString:开头,则它应采用 Azure:SignalR:ConnectionString:{Name}:{EndpointType} 格式,其中,NameEndpointTypeServiceEndpoint 对象的属性(可从代码访问)。If the key starts with Azure:SignalR:ConnectionString:, it should be in format Azure:SignalR:ConnectionString:{Name}:{EndpointType}, where Name and EndpointType are properties of the ServiceEndpoint object, and are accessible from code.

可将多个实例连接字符串添加到 web.configYou can add multiple instance connection strings to web.config:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <connectionStrings>
    <add name="Azure:SignalR:ConnectionString" connectionString="<ConnectionString1>"/>
    <add name="Azure:SignalR:ConnectionString:en-us" connectionString="<ConnectionString2>"/>
    <add name="Azure:SignalR:ConnectionString:zh-cn:secondary" connectionString="<ConnectionString3>"/>
    <add name="Azure:SignalR:ConnectionString:Backup:secondary" connectionString="<ConnectionString4>"/>
  </connectionStrings>
  ...
</configuration>

如何通过代码添加多个终结点?How to add multiple endpoints from code?

我们已引入 ServicEndpoint 类来描述 Azure SignalR 服务终结点的属性。A ServicEndpoint class is introduced to describe the properties of an Azure SignalR Service endpoint. 使用 Azure SignalR 服务 SDK 时,可通过以下代码配置多个实例终结点:You can configure multiple instance endpoints when using Azure SignalR Service SDK through:

app.MapAzureSignalR(
    this.GetType().FullName, 
    options => {
            options.Endpoints = new ServiceEndpoint[]
            {
                // Note: this is just a demonstration of how to set options.Endpoints
                // Having ConnectionStrings explicitly set inside the code is not encouraged
                // You can fetch it from a safe place such as Azure KeyVault
                new ServiceEndpoint("<ConnectionString1>"),
                new ServiceEndpoint("<ConnectionString2>"),
                new ServiceEndpoint("<ConnectionString3>"),
            }
        });

如何自定义路由器?How to customize router?

ASP.NET SignalR 与 ASP.NET Core SignalR 之间的唯一差别在于 GetNegotiateEndpoint 的 HTTP 上下文类型。The only difference between ASP.NET SignalR and ASP.NET Core SignalR is the http context type for GetNegotiateEndpoint. ASP.NET SignalR 的 HTTP 上下文类型为 IOwinContextFor ASP.NET SignalR, it is of IOwinContext type.

下面是 ASP.NET SignalR 的自定义协商示例:Below is the custom negotiate example for ASP.NET SignalR:

private class CustomRouter : EndpointRouterDecorator
{
    public override ServiceEndpoint GetNegotiateEndpoint(IOwinContext context, IEnumerable<ServiceEndpoint> endpoints)
    {
        // Override the negotiate behavior to get the endpoint from query string
        var endpointName = context.Request.Query["endpoint"];
        if (string.IsNullOrEmpty(endpointName))
        {
            context.Response.StatusCode = 400;
            context.Response.Write("Invalid request.");
            return null;
        }

        return endpoints.FirstOrDefault(s => s.Name == endpointName && s.Online) // Get the endpoint with name matching the incoming request
               ?? base.GetNegotiateEndpoint(context, endpoints); // Or fallback to the default behavior to randomly select one from primary endpoints, or fallback to secondary when no primary ones are online
    }
}

请不要忘记使用以下代码将路由器注册到 DI 容器:Don't forget to register the router to DI container using:

var hub = new HubConfiguration();
var router = new CustomRouter();
hub.Resolver.Register(typeof(IEndpointRouter), () => router);
app.MapAzureSignalR(GetType().FullName, hub, options => {
    options.Endpoints = new ServiceEndpoint[]
                {
                    new ServiceEndpoint(name: "east", connectionString: "<connectionString1>"),
                    new ServiceEndpoint(name: "north", connectionString: "<connectionString2>"),
                    new ServiceEndpoint("<connectionString3>")
                };
});

跨区域方案中的配置Configuration in cross-region scenarios

ServiceEndpoint 对象包含值为 primarysecondaryEndpointType 属性。The ServiceEndpoint object has an EndpointType property with value primary or secondary.

primary 终结点是接收客户端流量的首选终结点,我们认为其网络连接更可靠;secondary 终结点的网络连接被认为较不可靠,仅用于接收从服务器到客户端的流量(例如广播消息),而不用于接收客户端到服务器的流量。primary endpoints are preferred endpoints to receive client traffic, and are considered to have more reliable network connections; secondary endpoints are considered to have less reliable network connections and are used only for taking server to client traffic, for example, broadcasting messages, not for taking client to server traffic.

在跨区域案例中,网络可能不稳定。In cross-region cases, network can be unstable. 对于位于“中国东部”的某个应用服务器,可将同样位于“中国东部”区域的 SignalR 服务终结点配置为 primary,并将其他区域中的终结点标记为 secondaryFor one app server located in China East, the SignalR Service endpoint located in the same China East region can be configured as primary and endpoints in other regions marked as secondary. 在此配置中,其他区域中的服务终结点可以接收来自此“中国东部”应用服务器的消息,但不会将任何跨区域客户端路由到此应用服务器。In this configuration, service endpoints in other regions can receive messages from this China East app server, but there will be no cross-region clients routed to this app server. 下图显示了体系结构:The architecture is shown in the diagram below:

跨地域基础结构

当客户端尝试使用默认路由器通过 /negotiate 来与应用服务器协商时,SDK 会从可用的 primary 终结点集内随机选择一个终结点。When a client tries /negotiate with the app server, with the default router, SDK randomly selects one endpoint from the set of available primary endpoints. 当主终结点不可用时,SDK 会从所有可用的 secondary 终结点中随机选择When the primary endpoint is not available, SDK then randomly selects from all available secondary endpoints. 当服务器与服务终结点之间的连接处于活动状态时,终结点将标记为可用The endpoint is marked as available when the connection between server and the service endpoint is alive.

在跨区域方案中,如果客户端尝试通过 /negotiate 来与“中国东部”的应用服务器协商,则默认情况下,始终会返回位于同一区域中的 primary 终结点。In cross-region scenario, when a client tries /negotiate with the app server hosted in China East, by default it always returns the primary endpoint located in the same region. 当“中国东部”的所有终结点都不可用时,客户端将重定向到其他区域中的终结点。 When all China East endpoints are not available, the client is redirected to endpoints in other regions. 以下故障转移部分详细介绍了该方案。Fail-over section below describes the scenario in detail.

正常协商

故障转移Fail-over

当所有 primary 终结点都不可用时,客户端的 /negotiate 将从可用的 secondary 终结点中进行选择。When all primary endpoints are not available, client's /negotiate picks from the available secondary endpoints. 此故障转移机制要求每个终结点充当至少一个应用服务器的 primary 终结点。This fail-over mechanism requires that each endpoint should serve as primary endpoint to at least one app server.

故障转移

后续步骤Next steps

本指南介绍了如何在同一应用程序中为扩展、分片和跨区域方案配置多个实例。In this guide, you learned about how to configure multiple instances in the same application for scaling, sharding, and cross-region scenarios.

还可以在高可用性和灾难恢复方案中使用多个终结点。Multiple endpoints supports can also be used in high availability and disaster recovery scenarios.