Völker hört die Signale!

Skalieren von ASP.NET SignalR
Kommentare

Die nächste Version von SignalR ermöglicht das Umsetzen von Skalierungsstrategien. Hierzu kann der Entwickler entweder SQL Server, Azure Service Bus oder die freie NoSQL-Lösung Redis einsetzen.

Benachrichtigungsszenarien stellen im Web, wo Firewalls eingehende Verbindungen und somit auch Benachrichtigungen seitens des Servers blockieren, eine besondere Herausforderung dar. Als Lösung stehen verschiedene Long-Polling-Verfahren sowie die mit HTML5 assoziierten WebSockets zur Verfügung. ASP.NET SignalR abstrahiert diese Möglichkeiten. Abhängig von den Möglichkeiten der Kommunikationspartner entscheidet sich dieses jüngste Mitglied der ASP.NET-Familie für eine geeignete Kommunikationsart, ohne dass sich der Entwickler damit belasten muss.

Während die erste finale Version seit Februar des Jahres über NuGet sowie über die ASP.NET and Web Tools 2012.2 [1] bezogen werden kann, ist die nächste Version bereits in Arbeit. Hierfür planen die Entwickler unter anderem Unterstützung für Skalierungsszenarien. Anhand der zur Verfügung stehenden Vorabversionen wirft der vorliegende Artikel einen Blick auf die damit einhergehenden Möglichkeiten.

Überlegungen zum Skalieren von SignalR

Weblösungen können einfach skaliert werden, indem sie auf mehrere Server verteilt werden. Ein Load Balancer kann in weiterer Folge entscheiden, welche Anfrage zu welchem Server geroutet wird. Wendet man diese bewährte Lösung auf eine SignalR-basierte Anwendung an, wird man schnell feststellen, das lediglich jene Clients, die zum selben Server geroutet wurden, über SignalR miteinander kommunizieren können, zumal keine Verbindung zwischen den SignalR-Instanzen der einzelnen Server besteht. Abbildung 1 veranschaulicht dies.

Abb. 1: Naive Skalierungsstrategie

Aus diesem Grund werden künftige Versionen von SignalR die Möglichkeit bieten, die empfangenen Nachrichten nicht nur an die verbundenen Clients, sondern auch an eine übergeordnete Komponente weiterzuleiten. Die SignalR-Instanzen auf anderen Servern können die Nachrichten von dort abholen und an ihre Clients weiterleiten. Abbildung 2 veranschaulicht das.

Abb. 2: Skalierungsstrategie für SignalR

Bei dieser übergeordneten Komponente kann es sich – so der Plan der Entwickler von SignalR – um SQL Server, Azure Service Bus oder um Redis handeln.

Die Skalierungsstrategie, die SQL Server nutzt, verwendet den mit Version 2005 eingeführten Service Broker, der eine datenbankinterne Implementierung einer Nachrichtenwarteschlange darstellt. Da der Service Broker derzeit weder in SQL Azure noch in SQL Compact zu finden ist, scheiden diese beiden Varianten hierfür aus.

Bei Cloud-Lösungen kann der Entwickler jedoch auf den Azure Message Bus zurückgreifen. Die hierfür vorgesehene Skalierungsstrategie verwendet die vom Service Bus gebotenen Topics zur Kommunikation zwischen den SignalR-Instanzen. Hierbei handelt es sich um Nachrichtenwarteschlangen, die nach dem Prinzip Publish/Subscribe arbeiten und somit eine Punkt-zu-Multipunkt-Kommunikation erlauben.

Bei der freien NoSQL-Lösung Redis handelt es sich um eine hochperformante In-Memory-Datenbank zum Speichern von Key-Value-Paaren. Die auf Redis basierende Skalierungsstrategie macht sich das sowie die von Redis gebotene Publish/Subscribe-Implementierung zunutze.

SignalR mit SQL Server skalieren

Zur Demonstration der Umsetzung einer skalierbaren SignalR-Lösung wird in weiterer Folge auf Self-Hosting zurückgegriffen. Das bedeutet, dass SignalR-basierte Services nicht über einen Webserver, wie zum Beispiel IIS, sondern über eine eigene Anwendung bereitgestellt werden. Dabei kann es sich zum Beispiel um einen Windows-Dienst oder, wie nachfolgend, um eine Kommandozeilenanwendung handeln. Durch den Einsatz von Self-Hosting kann der Entwickler einfach mehrere Hosts starten und somit ein verteiltes Szenario simulieren. Die benötigten Bibliotheken werden über NuGet geladen. Es handelt sich hierbei um die Vorabversionen von:

  • microsoft.aspnet.signalr.owin
  • microsoft.owin.host.httplistener
  • microsoft.owin.hosting
  • microsoft.aspnet.signalr.sqlserver

Die ersten drei NuGet-Pakete erlauben die Realisierung von Self-Hosting; die vierte Komponente ermöglicht die Skalierung über SQL Server. Das verwendete Beispiel sieht einen Hub vor, der als Informationen Flüge entgegennimmt und diese an alle verbundenen Clients verteilt. Dazu bringt der Hub bei diesen Clients die lokale Operation ReceiveState zur Ausführung.

public class FlugHub : Hub {   public void SendState(string flugNummer, string state, string info)   {     this.Clients.All.ReceiveState(flugNummer, state, info);   } }

Die Konfiguration des Hosts erfolgt über eine Klasse, die per Definition eine Methode Configuration aufweist (Listing 1). Diese Methode nimmt eine Instanz von IAppBuilder entgegen. Weitere Anforderungen, wie die Notwendigkeit, ein Interface zu implementieren oder von einer abstrakten Basisklasse zu erben, existieren an diese Klasse nicht.

Innerhalb von Configuration platziert der Entwickler die gewünschte Konfiguration. Im betrachteten Fall legt er mit MapHubs fest, dass sämtliche Hubs über /signalr/hubs erreichbar sind. Da es sich hierbei um den Standard-URL handelt, muss er diese Adresse nicht explizit angeben. Allerdings existieren Überladungen von MapHubs, die das Festlegen einer davon abweichenden Adresse erlauben.

MapHubs nimmt eine Instanz von HubConfiguration entgegen. Deren Eigenschaft EnableCrossDomain ermöglicht so genannte CORS-Anfragen. Bei CORS (Cross-Origin Resource Sharing) handelt es sich um einen Mechanismus, der es Webseiten erlaubt, auf Services anderer Domänen zuzugreifen. Die Eigenschaft EnableDetailedErrors gibt daneben an, ob SignalR detaillierte Fehlermeldungen übers Netzwerk übertragen darf.

Zusätzlich legt der Entwickler durch Einsatz der Erweiterungsmethode UseSqlServer fest, dass zum Skalieren SQL Server zum Einsatz kommen soll. Diese Erweiterungsmethode nimmt eine Instanz von SqlScaleoutConfiguration, die zumindest die zu verwendenden Datenbankverbindungszeichenketten kennen muss, entgegen.

Listing 1
class Startup {   public void Configuration(IAppBuilder app)   {     var cnnstr = @"Data Source=IWI-NB-07SQLEXPRESS;[...]";     var ssc = new SqlScaleoutConfiguration(cnnstr);     GlobalHost.DependencyResolver.UseSqlServer(ssc);     var config = new HubConfiguration { EnableCrossDomain = true, EnableDetailedErrors = true };     app.MapHubs(config);   } }

Damit SignalR die angegebene Datenbank zum Skalieren verwenden kann, muss der Entwickler für diese den Service Broker aktivieren. Dazu führt er die folgende Anweisung in der Master-Datenbank aus, wobei DatenbankName für die einzusetzende Datenbank steht:

ALTER DATABASE DatenbankName SET ENABLE_BROKER

Die Methode, die den Host startet, findet sich in Listing 2. Sie fordert zunächst den Benutzer zur Eingabe des zu verwendenden Ports auf. Dies macht es möglich, zu Testzwecken gleichzeitig verschiedene Instanzen des Hosts, die auf unterschiedliche Ports horchen, auszuführen.

Ist der zu verwendende Port bekannt, startet Main unter Verwendung der statischen WebApplication.Start den Host. Als Typparameter gibt sie dabei die Klasse mit der Konfiguration an (Listing 1); als Übergabeparameter den heranzuziehenden URL.

Listing 2
class Program {   static void Main(string[] args)   {     string url = "http://localhost:{0}";      Console.Write("Port-Number: ");     var port = Console.ReadLine();     url = string.Format(url, port);      using (WebApplication.Start(url))     {       Console.WriteLine("Server running on {0}", url);       Console.ReadLine();     }   } }

Aufmacherbild: scale or scales with copyspace showing law justie or legal concept von Shutterstock / Urheberrecht: Gunnar Pippel

[ header = Skalieren von ASP.NET SignalR – Teil 2 ]

Implementierung eines SignalR-Clients

Die Umsetzung eines einfachen JavaScript-Clients, der den zuvor besprochenen Hub konsumiert, findet sich in Listing 3. Neben jQuery ist dazu die JavaScript-Datei jquery.signalR-1.0.0.js einzubinden. Der Entwickler erhält dieses, indem er über NuGet das Paket microsoft.aspnet.signalr einbindet. Daneben ist die vom Host bereitgestellte JavaScript-Datei /signalr/hubs einzubinden.

Beim betrachteten Listing handelt es sich um eine View, die einem ASP.NET-MVC-Projekt, das sich auf die Vorlage Internet Application abstützt, entnommen wurde. Da diese Projektvorlage jQuery über die Layoutseite einbindet, findet sich kein entsprechender Verweis darauf im Listing. Damit der Aufrufer die Wahl des serverseitigen Ports, über das der SignalR-Host anzusprechen ist, zur Laufzeit treffen kann, beinhaltet dieses Listing den Platzhalter @Request.QueryString[„port“], der für den Wert des URL-Parameters port steht.

Das Listing 3 setzt die Zieladresse über $.connection.hub.url explizit. Dies ist beim Einsatz von CORS notwendig. Anschließend greift das Listing auf den Proxy für den Hub über die Eigenschaft $.connection.flugHub zu und richtet die clientseitige Funktion receiveState ein. Nach dem Aufbau der Verbindung zu SignalR unter Verwendung von $.connection.hub.start richtet das Listing einen Event Handler für einen Button ein. Dieser Event Handler liest Informationen aus einigen Feldern aus und sendet diese an die vom Hub bereitgestellte Operation sendState.

Listing 3
   
Skalierungsszenario testen

Um das beschriebene Szenario zu testen, ist der Self-Host zweimal unter Angabe verschiedener Ports zu starten. Anschließend sind zwei Instanzen des JavaScript-Clients zu starten, wobei der erste Client durch die Angabe des Ports im URL mit dem ersten Host und der zweite Client mit dem zweiten Host verbunden wird. Funktioniert die Lösung, so sollten Informationen, die über den ersten Client versendet wurden, auch im zweiten Client, der mit einem anderen Host verbunden ist, ankommen und vice versa. Abbildung 3 demonstriert dies.

Abb. 3: Test der Umsetzung

Azure Message Bus und Redis als Alternative zu SQL Server

Für das Umsetzen eines Skalierungsszenarios mit Azure Message Bus oder Redis geht der Entwickler analog zum hier beschriebenen Beispiel vor. Die für den Einsatz von Azure Service Bus benötigten Komponenten finden sich im NuGet-Paket microsoft.aspnet.signalr.servicebus. Daneben kann der Entwickler über die Erweiterungsmethode GlobalHost.DependencyResolver.UseServiceBus die Verbindungszeichenkette, die auf die gewünschte MessageBus-Instanz in der Azure Cloud verweist, konfigurieren.

Die Komponenten für ein Skalieren mittels Redis befinden sich hingegen im NuGet-Paket microsoft.aspnet.signalr.redis und die entsprechende Erweiterungsmethode zum Konfigurieren der Eckdaten nennt sich in diesem Fall GlobalHost.DependencyResolver.UseRedis. Weitere Informationen dazu finden sich unter [2].

Fazit

Dieser Artikel hat gezeigt, dass SignalR-basierte Lösungen auf sehr einfache Weise skaliert werden können. Je nach Systemumgebung kann der Entwickler dazu entweder SQL Server, Azure Service Bus oder die freie NoSQL-Lösung Redis einsetzen. Die hierzu bereitgestellten Lösungen sind derzeit jedoch noch im BETA-Stadium und sollen laut Dokumentation noch nicht im Produktivbetrieb verwendet werden.

Beim Skalieren von SignalR ist auch zu beachten, dass durch die Skalierung selbst ein bestimmter Overhead entsteht. Bei einer einfachen lokalen Teststellung konnte der Autor beispielsweise einen Performanceunterschied von rund 4 ms pro versendeter bzw. empfangener Nachricht ausmachen.

Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -