Warum Streams in Orleans?
Es gibt bereits eine große Bandbreite von Technologien, mit denen Sie Streamverarbeitungssysteme erstellen können. Dazu gehören Systeme zum dauerhaften Speichern von Streamdaten (z. B. Event Hubs und Kafka) und Systeme zum Ausdrücken von Computevorgängen über Streamdaten (z. B. Azure Stream Analytics, Apache Storm und Apache Spark Streaming). Dies sind großartige Systeme, mit denen Sie effiziente Datenstrom-Verarbeitungspipelines erstellen können.
Einschränkungen vorhandener Systeme
Diese Systeme eignen sich jedoch nicht für ein differenziertes Freiformcompute von Streamdaten. Die oben genannten Streamingcomputesysteme ermöglichen es Ihnen sämtlich, ein einheitliches Datenflussdiagramm von Vorgängen anzugeben, die auf alle Datenstromelemente auf die gleiche Weise angewendet werden. Dies ist ein leistungsfähiges Modell, wenn Daten einheitlich sind und Sie denselben Satz von Transformations-, Filter- oder Aggregationsvorgängen für diese Daten ausdrücken möchten. Es gibt aber auch andere Anwendungsfälle, in denen Sie grundsätzlich unterschiedliche Vorgänge für verschiedene Datenelemente ausdrücken müssen. In einigen von ihnen müssen Sie im Rahmen dieser Verarbeitung gelegentlich einen externen Aufruf ausführen, z. B. eine beliebige REST-API aufrufen. Die einheitlichen Datenflussverarbeitungs-Engines unterstützen diese Szenarien entweder nicht, unterstützen sie auf begrenzte und eingeschränkte Weise oder sind in ihrer Unterstützung ineffizient. Dies liegt daran, dass sie inhärent für eine große Menge ähnlicher Elemente optimiert und in der Regel in Bezug auf Ausdrucksfähigkeit, Verarbeitung begrenzt sind. Orleans-Datenströme zielen auf diese anderen Szenarien ab.
Motivation
Alles begann mit Anfragen von Orleans-Benutzern, das Zurückgeben einer Sequenz von Elementen aus einem Grain-Methodenaufruf zu unterstützen. Wie Sie sich vorstellen können, war das nur die Spitze des Eisbergs. Sie brauchten viel mehr als das.
In einem typischen Szenario für Orleans-Streams haben Sie etwa Benutzerdatenströme und möchten für jeden Benutzer im Kontext eines einzelnen Benutzers je Benutzer unterschiedliche Verarbeitungen ausführen. Wir können über Millionen von Benutzern verfügen, aber einige von ihnen interessieren sich für das Wetter und können Wetterwarnungen für einen bestimmten Ort abonnieren, während andere an Sportveranstaltungen interessiert sind; wieder ein anderer verfolgt den Status eines bestimmten Fluges. Für die Verarbeitung dieser Ereignisse ist eine je andere Logik erforderlich. Sie möchten jedoch keine zwei unabhängigen Instanzen der Datenstromverarbeitung ausführen. Einige Benutzer interessieren sich nur für eine bestimmte Aktie und nur dann, wenn eine bestimmte externe Bedingung zutrifft, eine Bedingung, die möglicherweise nicht unbedingt Teil der Datenstromdaten ist (und daher zur Laufzeit im Rahmen der Verarbeitung dynamisch überprüft werden muss).
Benutzer ändern ihre Interessen ständig, daher kommen und gehen ihre Abonnements bestimmter Datenströme von Ereignissen dynamisch, sodass sich die Streamingtopologie dynamisch und schnell ändert. Darüber hinaus entwickelt sich die Verarbeitungslogik pro Benutzer dynamisch weiter und ändert sich, basierend auf dem Benutzerstatus und externen Ereignissen. Durch externe Ereignisse kann sich die Verarbeitungslogik für einen bestimmten Benutzer ändern. Wenn beispielsweise in einem Erkennungssystem für Spielbetrug eine neue Betrugsmöglichkeit entdeckt wird, muss die Verarbeitungslogik mit der neuen Regel aktualisiert werden, um diesen neuen Verstoß zu erkennen. Dies muss natürlich ohne Unterbrechung der laufenden Verarbeitungspipeline erfolgen. Massendatenfluss-Datenflussverarbeitungs-Engines wurden nicht entwickelt, um solche Szenarien zu unterstützen.
Es versteht sich fast von selbst, dass ein solches System auf mehreren über ein Netzwerk verbundenen Computern und nicht auf einem einzelnen Knoten ausgeführt werden muss. Daher muss die Verarbeitungslogik skalierbar und elastisch über einen Cluster von Servern verteilt werden.
Neue Anforderungen
Wir haben vier grundlegende Anforderungen für unser Datenstrom-Verarbeitungssystem identifiziert, die es ermöglichen, den oben dargestellten Szenarien Rechnung zu tragen.
- Flexible Datenstromverarbeitungslogik
- Unterstützung für hochdynamische Topologien
- Fein abgestufte Datenstromgranularität
- Distribution
Flexible Datenstromverarbeitungslogik
Wir möchten, dass das System verschiedene Methoden zum Ausdrücken der Datenstromverarbeitungslogik unterstützt. Für die vorhandenen Systeme, die wir oben erwähnt haben, muss der Entwickler ein deklaratives Datenflussberechnungsdiagramm schreiben, in der Regel in einem funktionalen Programmierstil. Dies schränkt die Ausdrucksstärke und Flexibilität der Verarbeitungslogik ein. Orleans-Datenströme sind indifferent gegenüber der Art und Weise, wie Verarbeitungslogik ausgedrückt wird. Sie kann als Datenfluss ausgedrückt werden (z. B. durch Verwendung von Reaktiven Erweiterungen (Rx) in .NET), als funktionales Programm, als deklarative Abfrage oder in einer allgemeinen imperativen Logik. Die Logik kann zustandsbehaftet oder zustandslos sein, kann Nebenwirkungen haben oder nicht und kann externe Aktionen auslösen. Die ganze Macht liegt beim Entwickler.
Unterstützung für dynamische Topologien
Wir möchten, dass das System sich dynamisch weiterentwickelnde Topologien ermöglicht. Die oben erwähnten vorhandenen Systeme sind in der Regel auf statische Topologien beschränkt, die zum Zeitpunkt der Bereitstellung festgeschrieben sind und sich zur Laufzeit nicht weiterentwickeln können. Im folgenden Beispiel für einen Datenflussausdruck ist alles schön und einfach, bis Sie ihn ändern müssen.
Stream.GroupBy(x=> x.key).Extract(x=>x.field).Select(x=>x+2).AverageWindow(x, 5sec).Where(x=>x > 0.8) *
Ändern Sie die Schwellenwertbedingung im Where-Filter, fügen Sie eine Select-Anweisung hinzu, oder fügen Sie im Datenflussdiagramm einen weiteren Branch hinzu, und erstellen Sie einen neuen Ausgabedatenstrom. In vorhandenen Systemen ist dies nicht möglich, ohne die gesamte Topologie einzureißen und den Datenfluss von Grund auf neu zu starten. Praktisch versehen diese Systeme die vorhandene Berechnung mit Prüfpunkten und sind dann imstande, vom letzten Prüfpunkt aus neu zu starten. Ein solcher Neustart stellt trotzdem eine Unterbrechung dar und ist für einen Onlinedienst, der Ergebnisse in Echtzeit erzeugt, teuer. Ein derartiger Neustart wird besonders unpraktisch, wenn wir es mit einer großen Anzahl solcher Ausdrücke mit ähnlichen, aber unterschiedlichen Parametern (pro Benutzer, pro Gerät usw.) zu tun haben, die sich ständig ändern.
Das System soll die Entwicklung des Datenstrom-Verarbeitungsdiagramms zur Laufzeit ermöglichen, indem dem Berechnungsdiagramm neue Verknüpfungen oder Knoten hinzugefügt werden oder die Verarbeitungslogik innerhalb der Berechnungsknoten geändert wird.
Fein abgestufte Datenstromgranularität
In den vorhandenen Systemen ist die kleinste Abstraktionseinheit in der Regel der gesamte Fluss (Topologie). Für viele unserer Zielszenarien ist es jedoch erforderlich, dass ein einzelner Knoten/Link in der Topologie selbst eine logische Entität darstellt. Auf diese Weise kann jede Entität potenziell unabhängig verwaltet werden. Beispielsweise können in der großen Datenstromtopologie, die mehrere Verbindungen umfasst, verschiedene Verbindungen unterschiedliche Merkmale aufweisen und über verschiedene physische Transporte implementiert werden. Einige Verbindungen können über TCP-Sockets erfolgen, während andere auf zuverlässige Warteschlangen bauen. Für verschiedene Verbindungen können unterschiedliche Liefergarantien gelten. Verschiedene Knoten können unterschiedliche Prüfpunktstrategien aufweisen, und ihre Verarbeitungslogik kann in verschiedenen Modellen oder sogar in verschiedenen Sprachen ausgedrückt werden. Eine solche Flexibilität ist in vorhandenen Systemen in der Regel nicht möglich.
Die Abstraktionseinheit und das Flexibilitätsargument ähneln einem Vergleich zwischen SoA (dienstorientierte Architekturen) und Akteuren. Akteursysteme ermöglichen mehr Flexibilität, da jeder Akteur im Wesentlichen ein unabhängig verwalteter „kleiner Dienst“ ist. Ebenso möchten wir, dass das Datenstromsystem eine solche fein abgestufte Steuerung ermöglicht.
Distribution
Und natürlich sollte unser System alle Eigenschaften eines „guten verteilten Systems“ haben. Dazu gehören:
- Skalierbarkeit: Es wird eine große Anzahl von Streams und Computeelementen unterstützt.
- Elastizität: Das Hinzufügen/Entfernen von Ressourcen ist möglich, um je nach Auslastung zu wachsen oder schrumpfen.
- Zuverlässigkeit: Stabilität auch bei Fehlern
- Effizienz: Effiziente Nutzung der zugrunde liegenden Ressourcen
- Reaktionsfähigkeit: Möglichkeit von Quasi-Echtzeitszenarien.
Dies waren die Anforderungen, die wir bei der Entwicklung von Orleans Streaming im Auge hatten.
Klarstellung: Derzeit wird das Schreiben deklarativer Datenflussausdrücke wie im Beispiel oben in Orleans nicht direkt unterstützt. Die aktuellen Orleans-Streaming-APIs sind eher Bausteine auf niedriger Ebene, wie hier beschrieben. Die Bereitstellung deklarativer Datenflussausdrücke ist unser Ziel für die Zukunft.