Teilen über


Skalieren von SignalR Service mit mehreren Instanzen

Das SignalR Service SDK unterstützt mehrere Endpunkte für SignalR Service-Instanzen. Sie können dieses Feature zum Skalieren der gleichzeitigen Verbindungen oder für das regionsübergreifende Messaging verwenden.

Für ASP.NET Core

Hinzufügen mehrerer Endpunkte über die Konfiguration

Verwenden Sie für die Konfiguration den Schlüssel Azure:SignalR:ConnectionString oder Azure:SignalR:ConnectionString: als SignalR Service-Verbindungszeichenfolge.

Wenn der Schlüssel mit Azure:SignalR:ConnectionString: beginnt, sollte er das Format Azure:SignalR:ConnectionString:{Name}:{EndpointType} aufweisen. Hierbei sind Name und EndpointType Eigenschaften des ServiceEndpoint-Objekts, die über den Code zugänglich sind.

Sie können Verbindungszeichenfolgen für mehrere Instanzen hinzufügen, indem Sie die folgenden dotnet-Befehle verwenden:

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>

Hinzufügen mehrerer Endpunkte über Code

Eine ServiceEndpoint-Klasse beschreibt die Eigenschaften eines Azure SignalR Service-Endpunkts. Sie können mehrere Instanzen für Endpunkte konfigurieren, wenn Sie das Azure SignalR Service SDK verwenden:

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"),
            };
        });

Anpassen des Endpunktrouters

Standardmäßig wird vom SDK das DefaultEndpointRouter-Element verwendet, um Endpunkte zu erfassen.

Standardverhalten

  1. Routing von Clientanforderungen:

    Wird verwendet, wenn Clients die Aushandlung (/negotiate) mit dem App-Server durchführen. Standardmäßig nimmt das SDK eine zufällige Auswahl eines Endpunkts aus den verfügbaren Dienstendpunkten vor.

  2. Routing von Servernachrichten:

    Wenn eine Nachricht an eine bestimmte Verbindung gesendet und die Zielverbindung an den aktuellen Server geleitet wird, geht die Nachricht direkt an diesen verbundenen Endpunkt. Andernfalls werden die Nachrichten an jeden Azure SignalR-Endpunkt übertragen.

Anpassen des Routingalgorithmus

Sie können Ihre eigenen Router erstellen, wenn Sie über spezielle Kenntnisse verfügen und ermitteln können, an welche Endpunkte die Nachrichten gesendet werden sollen.

Im folgenden Beispiel wird ein benutzerdefinierter Router definiert, der Nachrichten mit einer Gruppe, die mit east- beginnt, an den Endpunkt namens east weiterleitet:

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

Im folgenden Beispiel wird das Standardverhalten für die Aushandlung außer Kraft gesetzt und der Endpunkt abhängig vom Standort des App-Servers ausgewählt.

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

Vergessen Sie nicht, den Router wie folgt für den DI-Container zu registrieren:

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

Für ASP.NET

Hinzufügen mehrerer Endpunkte über die Konfiguration

Verwenden Sie für die Konfiguration den Schlüssel Azure:SignalR:ConnectionString oder Azure:SignalR:ConnectionString: als SignalR Service-Verbindungszeichenfolge.

Wenn der Schlüssel mit Azure:SignalR:ConnectionString: beginnt, sollte er das Format Azure:SignalR:ConnectionString:{Name}:{EndpointType} aufweisen. Hierbei sind Name und EndpointType Eigenschaften des ServiceEndpoint-Objekts, die über den Code zugänglich sind.

Sie können web.config Verbindungszeichenfolgen für mehrere Instanzen hinzufügen:

<?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>

Hinzufügen mehrerer Endpunkte über Code

Eine ServiceEndpoint-Klasse beschreibt die Eigenschaften eines Azure SignalR Service-Endpunkts. Sie können mehrere Instanzen für Endpunkte konfigurieren, wenn Sie das Azure SignalR Service SDK verwenden:

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>"),
            }
        });

Anpassen eines Routers

Der einzige Unterschied zwischen ASP.NET SignalR und ASP.NET Core SignalR ist der HTTP-Kontexttyp für GetNegotiateEndpoint. Für ASP.NET SignalR lautet der Typ IOwinContext.

Der folgende Code ist ein Beispiel für die benutzerdefinierte Aushandlung für 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
    }
}

Vergessen Sie nicht, den Router wie folgt für den DI-Container zu registrieren:

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: "west", connectionString: "<connectionString2>"),
                    new ServiceEndpoint("<connectionString3>")
                };
});

Dienstendpunktmetriken

Zum Aktivieren eines erweiterten Routers bietet das SignalR Server SDK mehrere Metriken, die einem Server das Treffen intelligenter Entscheidungen ermöglichen. Die Eigenschaften befinden sich unter ServiceEndpoint.EndpointMetrics.

Metrikname Beschreibung
ClientConnectionCount Gesamtanzahl gleichzeitiger Clientverbindungen auf allen Hubs für den Dienstendpunkt
ServerConnectionCount Gesamtanzahl gleichzeitiger Serververbindungen auf allen Hubs für den Dienstendpunkt
ConnectionCapacity Gesamtverbindungskontingent für den Dienstendpunkt, einschließlich Client- und Serververbindungen

Der folgende Code ist ein Beispiel für das Anpassen eines Routers gemäß ClientConnectionCount:

private class CustomRouter : EndpointRouterDecorator
{
    public override ServiceEndpoint GetNegotiateEndpoint(HttpContext context, IEnumerable<ServiceEndpoint> endpoints)
    {
        return endpoints.OrderBy(x => x.EndpointMetrics.ClientConnectionCount).FirstOrDefault(x => x.Online) // Get the available endpoint with minimal clients load
               ?? 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
    }
}

Dienstendpunkte (ServiceEndpoints) mit dynamischer Skalierung

Ab SDK Version 1.5.0 aktivieren wir zuerst Dienstendpunkte (ServiceEndpoints) mit dynamischer Skalierung für die ASP.NET Core-Version. Daher müssen Sie den App-Server nicht neu starten, wenn Sie ein ServiceEndpoint-Element hinzufügen/entfernen müssen. Da ASP.NET Core die Standardkonfiguration wie appsettings.json mit reloadOnChange: true unterstützt, müssen Sie keinen Code ändern, er wird automatisch unterstützt. Wenn Sie eine benutzerdefinierte Konfiguration hinzufügen und Hot Reload nutzen möchten, lesen Sie Konfiguration in ASP.NET Core.

Hinweis

Die Dauer der Verbindungseinrichtung zwischen Server/Dienst und Client/Dienst kann variieren. Daher gibt es einen Bereitstellungszeitraum, der abgewartet wird, damit die Serververbindungen bereit sind, bevor der neue Dienstendpunkt (ServiceEndpoint) für Clients geöffnet wird. Somit wird sichergestellt, dass während der Skalierung keine Nachrichten verloren gehen. In der Regel dauert es wenige Sekunden, bis der Vorgang abgeschlossen ist und ein Protokollmeldung wie Succeed in adding endpoint: '{endpoint}' angezeigt wird, die angibt, dass der Vorgang abgeschlossen ist.

In einigen zu erwartenden Situationen, etwa bei regionsübergreifenden Netzwerkproblemen oder Konfigurationsinkonsistenzen auf verschiedenen App-Servern, kann der Bereitstellungszeitraum jedoch möglicherweise nicht ordnungsgemäß abgeschlossen werden. In diesen Fällen wird empfohlen, den App-Server neu zu starten, wenn Sie feststellen, dass der Skalierungsprozess nicht ordnungsgemäß funktioniert.

Der standardmäßige Timeoutzeitraum für die Skalierung beträgt fünf Minuten und kann durch Änderung des Werts ServiceOptions.ServiceScaleTimeout angepasst werden. Wenn Sie viele App-Server besitzen, wird empfohlen, den Wert etwas mehr zu erhöhen.

Konfiguration in regionsübergreifenden Szenarien

Das ServiceEndpoint-Objekt verfügt über eine EndpointType-Eigenschaft mit dem Wert primary oder secondary.

Primäre Endpunkte werden als Endpunkte zum Empfangen von Clientdatenverkehr bevorzugt, da sie zuverlässigere Netzwerkverbindungen aufweisen. Sekundäre Endpunkte verfügen über weniger zuverlässige Netzwerkverbindungen und werden nur für Datenverkehr vom Server zum Client verwendet. Beispielsweise werden sekundäre Endpunkte für Broadcastmeldungen und nicht für Datenverkehr vom Client zum Server verwendet.

In Fällen mit regionsübergreifender Nutzung kann das Netzwerk instabil sein. Für einen App-Server in der Region USA, Osten ist der SignalR Service-Endpunkt in derselben Region (also ebenfalls USA, Osten) primary, und die Endpunkte in anderen Regionen sind als secondary markiert. Bei dieser Konfiguration können Dienstendpunkte in anderen Regionen Nachrichten von diesem App-Server in der Region USA, Osten empfangen, aber es werden keine regionsübergreifenden Clients an diesen App-Server geleitet. Das folgende Diagramm veranschaulicht diese Architektur:

Regionsübergreifende Infrastruktur

Wenn ein Client versucht, per /negotiate die Aushandlung mit dem App-Server über den Standardrouter durchzuführen, wird vom SDK ein Endpunkt aus den verfügbaren primary-Endpunkten zufällig ausgewählt. Wenn der primäre Endpunkt nicht verfügbar ist, führt das SDK dann eine zufällige Auswahl aus allen verfügbaren Endpunkten vom Typ secondary durch. Der Endpunkt wird als verfügbar gekennzeichnet, wenn die Verbindung zwischen Server und Dienstendpunkt aktiv ist.

Wenn ein Client in einem regionsübergreifenden Szenario versucht, per /negotiate die Aushandlung mit dem App-Server durchzuführen, der in USA, Osten gehostet wird, wird standardmäßig immer der Endpunkt vom Typ primary zurückgegeben, der sich in derselben Region befindet. Falls alle Endpunkte der Region USA, Osten nicht verfügbar sind, leitet de Router den Client an die Endpunkte in anderen Regionen. Im folgenden Abschnitt Failover ist das Szenario ausführlich beschrieben.

Normale Aushandlung

Failover

Wenn kein Endpunkt vom Typ primary verfügbar ist, wird für den Client per /negotiate eine Auswahl aus den verfügbaren secondary-Endpunkten getroffen. Für diesen Failovermechanismus ist es erforderlich, dass jeder Endpunkt für mindestens einen App-Server als Endpunkt vom Typ primary dient.

Diagramm: Prozess des Failovermechanismus

Nächste Schritte

Sie können mehrere Endpunkte in Szenarien für Hochverfügbarkeit und Notfallwiederherstellung nutzen.