Eine ereignisgesteuerte Architektur besteht aus Ereignisproduzenten, die einen Datenstrom von Ereignissen, Ereigniskonsumenten generieren, die auf diese Ereignisse lauschen, und Ereigniskanäle, die Ereignisse von Produzenten an Verbraucher übertragen.
Ereignisse werden nahezu in Echtzeit übermittelt, sodass Consumer unmittelbar nach dem Auftreten des Ereignisses reagieren können. Producer sind von Consumern entkoppelt – ein Producer weiß nicht, welche Consumer lauschen. Consumer sind auch voneinander entkoppelt, und jeder Consumer sieht alle Ereignisse. Dies ist ein Unterschied zum Muster der konkurrierenden Consumer, in dem Consumer Nachrichten aus einer Warteschlange abrufen und eine Nachricht nur einmal verarbeitet wird (vorausgesetzt, dass sie fehlerfrei ist). In einigen Systemen, z.B. dem Internet der Dinge (Internet of Things, IoT), müssen Ereignisse in sehr großen Mengen erfasst werden.
Eine ereignisgesteuerte Architektur kann ein Modell vom Typ Veröffentlichen/Abonnieren („publish/subscribe“ oder auch pub/sub genannt) oder ein Ereignisdatenstrom-Modell verwenden.
Veröffentlichung/Abonnement: Die Messaginginfrastruktur verfolgt die Abonnements nach. Wenn ein Ereignis veröffentlicht wird, sendet die Infrastruktur das Ereignis an jeden Abonnenten. Nachdem ein Ereignis empfangen wurde, kann es nicht wiedergegeben werden, und neue Abonnenten sehen das Ereignis nicht.
Ereignisstreaming: Ereignisse werden in ein Protokoll geschrieben. Ereignisse unterliegen einer strikten Reihenfolge (innerhalb einer Partition) und sind dauerhaft. Clients abonnieren den Ereignisstrom nicht, stattdessen kann ein Client in jedem Teil des Ereignisstroms Lesevorgänge ausführen. Der Client ist dafür zuständig, im Ereignisstrom vorzurücken. Das bedeutet, dass ein Client jederzeit beitreten und Ereignisse wiedergeben kann.
Auf Consumerseite gibt es einige gängige Variationen:
Einfache Ereignisverarbeitung. Ein Ereignis löst unmittelbar eine Aktion im Consumer aus. Sie können z.B. Azure Functions mit einem Service Bus-Trigger verwenden, sodass eine Funktion ausgeführt wird, sobald eine Nachricht in einem Service Bus-Thema veröffentlicht wurde.
Grundlegende Ereigniskorrelation. Ein Verbraucher muss eine kleine Anzahl von diskreten Geschäftsereignissen verarbeiten, in der Regel durch einen Bezeichner korreliert, und einige Informationen aus früheren Ereignissen beibehalten, die bei der Verarbeitung späterer Ereignisse verwendet werden sollen. Dieses Muster wird von Bibliotheken wie NServiceBus und MassTransit unterstützt.
Komplexe Ereignisverarbeitung. Ein Consumer verarbeitet eine Reihe von Ereignissen und sucht dabei mithilfe von Technologien wie Azure Stream Analytics nach Mustern in den Ereignisdaten. Sie können z.B. Messergebnisse eines eingebetteten Geräts für ein bestimmtes Zeitfenster aggregieren und eine Nachricht generieren, wenn der gleitende Durchschnitt einen bestimmten Schwellenwert überschreitet.
Ereignisstromverarbeitung. Verwenden Sie eine Plattform zur Verarbeitung von Datenströmen wie z.B. Azure IoT Hub oder Apache Kafka als Pipeline zum Erfassen von Ereignissen und Weiterleiten der Ereignisse an Datenstromprozessoren. Die Datenstromprozessoren verarbeiten oder transformieren den Datenstrom. Es können mehrere Datenstromprozessoren für verschiedene Subsysteme der Anwendung vorhanden sein. Dieser Ansatz eignet sich gut für IoT-Workloads.
Die Quelle der Ereignisse befindet sich möglicherweise außerhalb des Systems – z.B. physische Geräte in einer IoT-Lösung. In diesem Fall muss das System in der Lage sein, die Daten mit der Kapazität hinsichtlich Umfang und Durchsatz zu erfassen, die für die Datenquelle erforderlich ist.
Es gibt zwei primäre Ansätze zur Strukturierung von Ereignisnutzlasten. Wenn Sie die Kontrolle über Ihre Ereigniskonsumenten haben, treffen Sie diese Nutzlaststrukturentscheidung pro Verbraucher; Herangehensweisen bei Bedarf innerhalb einer einzigen Arbeitsauslastung zu mischen.
Einschließen aller erforderlichen Attribute in der Nutzlast: Dieser Ansatz wird verwendet, wenn Verbraucher alle verfügbaren Informationen erhalten sollen, ohne eine externe Datenquelle abfragen zu müssen. Es kann jedoch zu Datenkonsistenzproblemen aufgrund mehrerer Datensatzsysteme führen, insbesondere nach Updates. Vertragsverwaltung und Versionsverwaltung können auch komplex werden.
Einschließen von nur Schlüsseln in die Nutzlast: In diesem Ansatz rufen Verbraucher die erforderlichen Attribute, z. B. einen Primärschlüssel, ab, um die verbleibenden Daten aus einer Datenquelle unabhängig abzurufen. Obwohl diese Methode aufgrund eines einzelnen Datensatzsystems eine bessere Datenkonsistenz bietet, kann sie schlechter als der erste Ansatz ausgeführt werden, da Verbraucher die Datenquelle häufig abfragen müssen. Es gibt weniger Bedenken hinsichtlich Kopplung, Bandbreite, Vertragsverwaltung oder Versionsverwaltung, da Ereignisse kleiner und verträge einfacher sind.
In dem logischen Diagramm oben wird jeder Consumertyp als einzelnes Feld angezeigt. In der Praxis werden meist mehrere Instanzen eines Consumers bereitgestellt, damit der Consumer nicht zum Single Point of Failure im System wird. Mehrere Instanzen sind möglicherweise auch erforderlich, um die Menge und Häufigkeit von Ereignissen zu bewältigen. Auch verarbeitet ein einzelner Consumer Ereignisse möglicherweise in mehreren Threads. Das kann eine Herausforderung darstellen, wenn Ereignisse in einer bestimmten Reihenfolge verarbeitet werden müssen oder eine „Exactly-Once“-Semantik erfordern. Weitere Informationen finden Sie unter Minimieren der Koordination.
Zahlreiche ereignisgesteuerte Architekturen weisen zwei primäre Topologien auf:
Brokertopologie. Komponenten übertragen Vorkommen in das gesamte System als Ereignisse, und andere Komponenten reagieren auf das Ereignis oder ignorieren es. Diese Topologie ist nützlich, wenn der Ereignisverarbeitungsablauf relativ einfach ist. Es gibt keine zentrale Koordination oder Orchestrierung, sodass diese Topologie sehr dynamisch sein kann. Diese Topologie ist hochgradig entkoppelt, was eine bessere Skalierbarkeit, Reaktionsfähigkeit und Fehlertoleranz von Komponenten ermöglicht. Es gibt keine Komponente, die den Status einer mehrstufigen Geschäftstransaktion besitzt oder über diesen informiert ist, und die Aktionen werden asynchron ausgeführt. Demzufolge sind verteilte Transaktionen riskant, da es keine nativen Mittel gibt, um sie erneut zu starten oder wiederzugeben. Strategien zur Fehlerbehandlung und für manuelle Eingriffe müssen sorgfältig überdacht werden, da diese Topologie zu Dateninkonsistenzen führen kann.
Mediatortopologie. Diese Topologie beseitigt einige Mängel im Zusammenhang mit der Brokertopologie. Es gibt einen Ereignismediator, der den Ereignisflow verwaltet und steuert. Der Ereignismediator verwaltet den Status und die Funktionen für die Fehlerbehandlung und den Neustart. Im Gegensatz zur Brokertopologie übertragen Komponenten Vorkommen als Befehle, und dies nur auf hierfür bestimmte Kanäle, bei denen es sich in der Regel um Nachrichtenwarteschlangen handelt. Es wird nicht erwartet, dass diese Befehle von ihren Verbrauchern ignoriert werden. Diese Topologie ermöglicht eine bessere Kontrolle, eine besser verteilte Fehlerbehandlung und eine potenziell bessere Datenkonsistenz. Diese Topologie führt zu einer verstärkten Kopplung zwischen den Komponenten, und der Ereignismediator sich als ein Engpass erweisen oder die Zuverlässigkeit beeinträchtigen.
Einsatzmöglichkeiten für diese Architektur
- Mehrere Subsysteme, die die gleichen Ereignisse verarbeiten müssen
- Echtzeitverarbeitung mit minimaler zeitlicher Verzögerung
- Komplexe Ereignisverarbeitung, z.B. Musterabgleich oder Aggregation über bestimmte Zeitfenster hinweg
- Große Mengen und hohe Datengeschwindigkeit, z.B. in IoT-Systemen
Vorteile
- Producer und Consumer sind entkoppelt.
- Keine Punkt-zu-Punkt-Integrationen. Neue Consumer lassen sich problemlos zum System hinzufügen.
- Consumer können sofort auf Ereignisse reagieren, sobald diese eingehen.
- Hochskalierbar, elastisch und verteilt.
- Subsysteme weisen unabhängige Perspektiven des Ereignisstroms auf.
Herausforderungen
Garantierte Übermittlung.
In manchen Systemen – insbesondere in IoT-Szenarien – ist es von entscheidender Bedeutung, sicherstellen zu können, dass Ereignisse übermittelt werden.
Verarbeitung der Ereignisse in einer bestimmten Reihenfolge oder genau einmal.
Jeder Consumertyp wird aus Gründen der Resilienz und Skalierbarkeit in der Regel in mehreren Instanzen ausgeführt. Dies kann zu einer Herausforderung werden, wenn die Ereignisse innerhalb eines Consumertyps in einer bestimmten Reihenfolge verarbeitet werden müssen oder die Logik der idempotenten Nachrichtenverwaltung nicht implementiert wird.
Koordinieren von Nachrichten über Dienste hinweg.
Geschäftsprozesse umfassen häufig mehrere Dienste, die Nachrichten veröffentlichen und abonnieren, um über eine gesamte Workload hinweg ein konsistentes Ergebnis zu erzielen. Workflowmuster wie das Choreographiemuster und Saga-Orchestrierung können verwendet werden, um Nachrichtenflüsse über verschiedene Dienste hinweg zuverlässig zu verwalten.
Fehlerbehandlung.
Ereignisgesteuerte Architektur verwendet hauptsächlich asynchrone Kommunikation. Eine Herausforderung bei der asynchronen Kommunikation ist die Fehlerbehandlung. Eine Möglichkeit, dieses Problem zu beheben, besteht darin, einen separaten Fehlerhandlerprozessor zu verwenden. Wenn der Ereignisconsumer einen Fehler feststellt, sendet er das fehlerhafte Ereignis sofort und asynchron an den Fehlerhandlerprozessor und fährt fort. Der Fehlerhandlerprozessor versucht, den Fehler zu beheben und sendet das Ereignis an den ursprünglichen Erfassungskanal zurück. Schlägt der Fehlerhandlerprozessor fehl, kann er das fehlerhafte Ereignis zur weiteren Überprüfung an einen Administrator senden. Wenn Sie einen Fehlerhandlerprozessor verwenden, werden fehlerhafte Ereignisse beim erneuten Senden nicht in der richtigen Reihenfolge bearbeitet.
Datenverlust.
Eine weitere Herausforderung bei asynchroner Kommunikation ist Datenverlust. Wenn eine der Komponenten abstürzt, bevor das Ereignis erfolgreich verarbeitet und an die nächste Komponente übergeben wird, wird das Ereignis verworfen und nie zum endgültigen Ziel. Um die Wahrscheinlichkeit eines Datenverlusts zu minimieren, speichern Sie Transitereignisse, und entfernen oder deaktivieren Sie die Ereignisse nur, wenn die nächste Komponente den Empfang des Ereignisses bestätigt hat. Diese Features werden in der Regel als Client-Bestätigungsmodus und letzte Teilnehmerunterstützung bezeichnet.
Implementieren eines herkömmlichen Anforderungsantwortmusters.
Manchmal erfordert der Ereignisproduzent eine sofortige Antwort des Ereignisanwenders, z. B. das Abrufen einer Kundenberechtigung, bevor er mit einer Bestellung fortsetzt. In der ereignisgesteuerten Architektur kann die synchrone Kommunikation über Anforderungsantwortnachrichten erreicht werden.
Dieses Muster wird in der Regel mithilfe mehrerer Warteschlangen implementiert – eine Anforderungswarteschlange und eine Antwortwarteschlange. Der Ereignisproduzent sendet eine asynchrone Anforderung an eine Anforderungswarteschlange, hält einen anderen Vorgang für diese Aufgabe an und wartet auf eine Antwort in der Antwortwarteschlange; dies effektiv in einen synchronen Prozess umwandeln. Ereigniskonsumenten verarbeiten dann die Anforderung und senden die Antwort zurück über eine Antwortwarteschlange. Bei diesem Ansatz wird in der Regel eine Sitzungs-ID für die Nachverfolgung verwendet, sodass der Ereignisproduzent weiß, welche Nachricht in der Antwortwarteschlange mit der spezifischen Anforderung verknüpft ist. Die ursprüngliche Anforderung könnte auch den Namen der Antwortwarteschlange angeben, potenziell kurzlebig, in einem Antwort-to-Header oder einem anderen sich gegenseitig vereinbarten benutzerdefinierten Attribut.
Beibehalten der entsprechenden Anzahl von Ereignissen.
Durch die Generierung einer übermäßigen Anzahl fein abgestimmter Ereignisse kann das System gesättigt und überwältigt werden, wodurch es schwierig ist, den Gesamtfluss von Ereignissen effektiv zu analysieren. Dieses Problem wird verschärft, wenn Änderungen zurückgesetzt werden müssen. Umgekehrt können übermäßig konsolidierende Ereignisse auch Probleme verursachen, was zu unnötigen Verarbeitungen und Antworten von Ereigniskonsumenten führt.
Um das richtige Gleichgewicht zu erzielen, berücksichtigen Sie die Folgen von Ereignissen und ob Verbraucher die Ereignisnutzlasten überprüfen müssen, um ihre Antworten zu bestimmen. Wenn Sie beispielsweise über eine Kompatibilitätsprüfungskomponente verfügen, reicht es möglicherweise aus, nur zwei Arten von Ereignissen zu veröffentlichen: konform und nicht konform. Dieser Ansatz ermöglicht es jedem Ereignis, nur von relevanten Verbrauchern verarbeitet zu werden und unnötige Verarbeitung zu verhindern.
Weitere Überlegungen
- Die Menge der Daten, die in einem Ereignis enthalten sein soll, kann ein wichtiger Aspekt sein, der sowohl die Leistung als auch die Kosten betrifft. Wenn Sie alle relevanten Informationen an das Ereignis übertragen, die für dessen Verarbeitung benötigt werden, kann das den Verarbeitungscode vereinfachen und die Anzahl zusätzlich benötigter Suchvorgänge reduzieren. Wenn Sie die minimale Menge an Daten an ein Ereignis übertragen, z. B. nur zwei Bezeichner, werden die Transportzeit sowie die Kosten verringert. Allerdings muss der Verarbeitungscode zusätzlich benötigte Informationen suchen. Weitere Informationen finden Sie in diesem Blogbeitrag.
- Während eine Anforderung nur für die Anforderungsbehandlungskomponente sichtbar ist, sind Ereignisse häufig für mehrere Komponenten in einer Workload sichtbar, auch wenn diese Komponenten nicht oder nicht dazu gedacht sind, sie zu nutzen. Wenn Sie mit einem "assume breach"-Mindset arbeiten, denken Sie daran, welche Informationen Sie in Ereignisse einbeziehen, um unbeabsichtigte Informationsoffenlegung zu verhindern.
- Viele Anwendungen verwenden ereignisgesteuerte Architektur als primäre Architektur; Dieser Ansatz kann jedoch mit anderen Architekturen kombiniert werden, was zu Hybridarchitekturen führt. Zu den gängigen Kombinationen gehören Microservices und Rohre und Filter. Durch die Integration ereignisgesteuerter Architektur wird die Systemleistung verbessert, indem Engpässe beseitigt und während hoher Anforderungsvolumen Der Rückdruck bereitgestellt wird.
- Bestimmte Domänen umfassen häufig mehrere Ereignishersteller, Verbraucher oder Ereigniskanäle. Änderungen an einer bestimmten Domäne können sich auf viele Komponenten auswirken.
Zugehörige Ressourcen
- Community-Diskussionsvideo zu den Überlegungen zur Auswahl zwischen Choreographie und Orchestrierung.