API Gateway (REST) ​​+ ereignisgesteuerte Mikrodienste

15

Ich habe eine Reihe von Microservices, deren Funktionalität ich über eine REST-API gemäß dem API-Gateway-Muster zur Verfügung stelle. Da es sich bei diesen Microservices um Spring Boot-Anwendungen handelt, verwende ich Spring AMQP, um eine synchrone RPC-Kommunikation zwischen diesen Microservices zu erreichen. Bisher lief es reibungslos. Je mehr ich jedoch über ereignisgesteuerte Mikroservice-Architekturen lese und Projekte wie Spring Cloud Stream betrachte, desto mehr bin ich davon überzeugt, dass ich mit dem synchronen RPC-Ansatz möglicherweise etwas falsch mache (insbesondere, weil ich dies für die Skalierung benötige) um Hunderte oder Tausende von Anfragen pro Sekunde von Client-Anwendungen zu beantworten).

Ich verstehe den Punkt hinter einer ereignisgesteuerten Architektur. Was ich nicht ganz verstehe, ist, wie man ein solches Muster tatsächlich verwendet, wenn man hinter einem Modell (REST) ​​sitzt, das eine Antwort auf jede Anfrage erwartet. Wenn ich zum Beispiel mein API-Gateway als Mikroservice und einen anderen Mikroservice habe, der Benutzer speichert und verwaltet, wie könnte ich so etwas GET /users/1auf rein ereignisgesteuerte Weise modellieren ?

Tony E. Stark
quelle

Antworten:

9

Sprich mir nach:

REST- und asynchrone Ereignisse sind keine Alternativen. Sie sind völlig orthogonal.

Sie können die eine oder die andere oder beide oder keine haben. Sie sind völlig unterschiedliche Werkzeuge für völlig unterschiedliche Problembereiche. Tatsächlich kann die allgemeine Anfrage-Antwort-Kommunikation absolut asynchron, ereignisgesteuert und fehlertolerant sein .


Als einfaches Beispiel sendet das AMQP-Protokoll Nachrichten über eine TCP-Verbindung. In TCP muss jedes Paket vom Empfänger bestätigt werden . Wenn ein Absender eines Pakets keine Bestätigung für dieses Paket erhält, sendet er das Paket so lange erneut, bis es bestätigt wurde oder bis die Anwendungsebene "aufgibt" und die Verbindung aufgibt. Dies ist eindeutig ein nicht fehlertolerantes Anforderungs-Antwort-Modell, da jede "Paketsendeanforderung" eine begleitende "Paketbestätigungsantwort muss , und führt dazu, dass die gesamte Verbindung fehlschlägt. Dennoch wird AMQP, ein standardisiertes und weit verbreitetes Protokoll für asynchrones fehlertolerantes Messaging, über TCP kommuniziert! Was gibt?

Das Kernkonzept hierbei ist, dass skalierbares, lose gekoppeltes, fehlertolerantes Messaging durch die von Ihnen gesendeten Nachrichten definiert wird , nicht durch die Art und Weise , wie Sie sie senden . Mit anderen Worten, lose Kopplung auf der Anwendungsschicht definiert .

Betrachten wir zwei Parteien, die entweder direkt mit RESTful HTTP oder indirekt mit einem AMQP-Nachrichtenbroker kommunizieren. Angenommen, Party A möchte ein JPEG-Bild zu Party B hochladen, um das Bild zu schärfen, zu komprimieren oder auf andere Weise zu verbessern. Partei A benötigt das verarbeitete Bild nicht sofort, sondern benötigt einen Verweis darauf für die zukünftige Verwendung und den späteren Abruf. Hier ist ein Weg, der in REST gehen könnte:

  • Teilnehmer A sendet ein HTTP POST Anforderungsnachricht an Teilnehmer B mitContent-Type: image/jpeg
  • Partei B verarbeitet das Bild (für eine lange Zeit, wenn es groß ist), während Partei A wartet und möglicherweise andere Dinge tut
  • Teilnehmer B sendet eine HTTP- 201 CreatedAntwortnachricht an Teilnehmer A mit einem Content-Location: <url>Header, der auf das verarbeitete Bild verweist
  • Partei A betrachtet ihre Arbeit als erledigt, da sie nun einen Verweis auf das verarbeitete Bild hat
  • Irgendwann in der Zukunft, wenn Partei A das verarbeitete Bild benötigt, wird es unter Verwendung des Links aus dem früheren Content-LocationHeader abgerufen

Der 201 CreatedAntwortcode teilt einem Client mit, dass die Anforderung nicht nur erfolgreich war, sondern dass auch eine neue Ressource erstellt wurde. In einer 201-Antwort ist der Content-LocationHeader eine Verknüpfung zur erstellten Ressource. Dies ist in RFC 7231, Abschnitte 6.3.2 und 3.1.4.2 angegeben.

Lassen Sie uns nun sehen, wie diese Interaktion über ein hypothetisches RPC-Protokoll auf AMQP funktioniert:

  • Teilnehmer A sendet an einen AMQP-Nachrichtenbroker ("Messenger") eine Nachricht mit dem Bild und Anweisungen, um es zur Verarbeitung an Teilnehmer B weiterzuleiten, und antwortet dann Teilnehmer A mit einer Adresse für das Bild
  • Party A wartet und macht möglicherweise andere Dinge
  • Der Messenger sendet die ursprüngliche Nachricht von Party A an Party B
  • Teilnehmer B verarbeitet die Nachricht
  • Teilnehmer B sendet Messenger eine Nachricht mit einer Adresse für das verarbeitete Bild und Anweisungen zum Weiterleiten dieser Nachricht an Teilnehmer A
  • Messenger sendet Party A die Nachricht von Party B mit der verarbeiteten Bildadresse
  • Partei A betrachtet ihre Arbeit als erledigt, da sie nun einen Verweis auf das verarbeitete Bild hat
  • Irgendwann in der Zukunft, wenn Partei A das Bild benötigt, ruft sie das Bild unter Verwendung der Adresse ab (möglicherweise durch Senden von Nachrichten an eine andere Partei).

Sehen Sie das Problem hier? In beiden Fällen kann Partei A eine Bildadresse erst abrufen, nachdem Partei B das Bild verarbeitet hat . Party A braucht das Bild jedoch nicht sofort und es ist ihm auch egal, ob die Verarbeitung noch abgeschlossen ist!

Wir können dieses Problem beheben , ziemlich leicht in dem AMQP Fall , indem Partei B A sagt , dass B akzeptierte das Bild für die Verarbeitung, so dass A eine Adresse für , wo das Bild wird nach der Verarbeitung wird beendet. Dann kann Teilnehmer B A irgendwann in der Zukunft eine Nachricht senden, die angibt, dass die Bildverarbeitung abgeschlossen ist. AMQP-Nachrichten zur Rettung!

Nur raten Sie mal: Mit REST können Sie dasselbe erreichen . Im AMQP-Beispiel wurde die Nachricht "Hier ist das verarbeitete Bild" in die Nachricht "Das Bild wird verarbeitet, Sie können es später abrufen" geändert. Um dies in RESTful HTTP zu tun, verwenden wir den 202 AcceptedCode und noch Content-Locationeinmal:

  • Teilnehmer A sendet eine HTTP- POSTNachricht an Teilnehmer B mitContent-Type: image/jpeg
  • Teilnehmer B sendet sofort eine 202 AcceptedAntwort zurück, die eine Art Inhalt für "asynchrone Operationen" enthält, der beschreibt, ob die Verarbeitung abgeschlossen ist und wo das Bild verfügbar ist, wenn die Verarbeitung abgeschlossen ist. Ebenfalls enthalten ist ein Content-Location: <link>Header, der in einer 202 AcceptedAntwort eine Verknüpfung zu der Ressource darstellt, die von dem jeweiligen Antworttext dargestellt wird. In diesem Fall ist dies eine Verknüpfung zu unserem asynchronen Betrieb!
  • Partei A betrachtet ihre Arbeit als erledigt, da sie nun einen Verweis auf das verarbeitete Bild hat
  • Irgendwann in der Zukunft, wenn Teilnehmer A das verarbeitete Abbild benötigt, ruft er zuerst die im Content-LocationHeader verknüpfte asynchrone Operationsressource ab, um festzustellen, ob die Verarbeitung abgeschlossen ist. Wenn dies der Fall ist, verwendet Teilnehmer A die Verknüpfung in der asynchronen Operation selbst, um das verarbeitete Bild abzurufen.

Der einzige Unterschied besteht darin, dass im AMQP-Modell Party B Party A mitteilt, wann die Bildverarbeitung abgeschlossen ist. Im REST-Modell prüft Partei A jedoch, ob die Verarbeitung unmittelbar vor dem tatsächlichen Bedarf des Abbilds erfolgt. Diese Ansätze sind äquivalent skalierbar . Wenn das System größer wird, nimmt die Anzahl der Nachrichten, die sowohl in der asynchronen AMQP- als auch in der asynchronen REST-Strategie gesendet werden, mit der entsprechenden asymptotischen Komplexität zu. Der einzige Unterschied besteht darin, dass der Client anstelle des Servers eine zusätzliche Nachricht sendet.

Der REST-Ansatz bietet jedoch noch einige weitere Tricks: Dynamische Erkennung und Protokollaushandlung . Überlegen Sie, wie sowohl die Synchronisierungs- als auch die asynchrone REST-Interaktion gestartet wurden. Partei A schickte genau die gleiche Anfrage an Partei B, mit dem einzigen Unterschied, dass Partei B auf die jeweilige Art der Erfolgsmeldung geantwortet hatte. Was wäre, wenn Teilnehmer A wählen wollte , ob die Bildverarbeitung synchron oder asynchron ist? Was ist, wenn Teilnehmer A nicht weiß, ob Teilnehmer B überhaupt eine asynchrone Verarbeitung ausführen kann?

Nun, HTTP hat tatsächlich bereits ein standardisiertes Protokoll dafür! Es nennt sich HTTP Preferences, speziell die respond-asyncVoreinstellung von RFC 7240, Abschnitt 4.1. Wenn Teilnehmer A eine asynchrone Antwort wünscht, enthält er einen Prefer: respond-asyncHeader mit seiner anfänglichen POST-Anforderung. Wenn Partei B beschließt, dieser Bitte nachzukommen, sendet sie eine 202 AcceptedAntwort zurück, die a enthält Preference-Applied: respond-async. Andernfalls ignoriert Party B den PreferHeader einfach und sendet ihn 201 Createdwie gewohnt zurück .

Auf diese Weise kann Teilnehmer A mit dem Server verhandeln und sich dynamisch an die Implementierung der Bildverarbeitung anpassen, mit der er gerade spricht. Darüber hinaus bedeutet die Verwendung expliziter Links, dass Teilnehmer A nichts anderes als Teilnehmer B wissen muss: kein AMQP-Nachrichtenbroker, kein mysteriöser Teilnehmer C, der weiß, wie die Bildadresse tatsächlich in Bilddaten umgewandelt wird, kein zweiter B-Async Teilnehmer, wenn sowohl synchrone als auch asynchrone Anforderungen gestellt werden müssen usw. Es wird lediglich beschrieben, was benötigt wird, was optional gewünscht wird, und anschließend auf Statuscodes, Antwortinhalte und Links reagiert. HinzufügenCache-ControlHeader mit expliziten Anweisungen zum Aufbewahren lokaler Kopien von Daten. Jetzt können Server mit Clients aushandeln, von welchen Ressourcen Clients lokale (oder sogar Offline-!) Kopien aufbewahren. Auf diese Weise erstellen Sie lose gekoppelte fehlertolerante Mikrodienste in REST.

Jack
quelle
1

Ob Sie rein ereignisgesteuert sein müssen oder nicht, hängt natürlich von Ihrem speziellen Szenario ab. Angenommen, Sie müssen es wirklich sein, dann könnten Sie das Problem lösen, indem Sie:

Speichern einer lokalen schreibgeschützten Kopie der Daten durch Abhören der verschiedenen Ereignisse und Erfassen der Informationen in ihren Nutzdaten. Auf diese Weise können Sie schnell (er) Daten lesen, die in einer für die jeweilige Anwendung geeigneten Form gespeichert sind. Dies bedeutet jedoch auch, dass Ihre Daten letztendlich über die Dienste hinweg konsistent sind.

Um GET /users/1mit diesem Ansatz zu modellieren , könnte man auf das UserCreatedund hörenUserUpdated Ereignisse, und speichern Sie die nützliche Teilmenge der Benutzer Daten in den Dienst. Wenn Sie diese Benutzerinformationen benötigen, können Sie einfach Ihren lokalen Datenspeicher abfragen.

Nehmen wir für eine Minute an, dass der Dienst, der den /users/Endpunkt verfügbar macht, keinerlei Ereignisse veröffentlicht. In diesem Fall können Sie ähnliche Ergebnisse erzielen, indem Sie einfach die Antworten auf die von Ihnen gestellten HTTP-Anforderungen zwischenspeichern und so die Notwendigkeit aufheben, innerhalb eines bestimmten Zeitraums mehr als eine HTTP-Anforderung pro Benutzer zu stellen.

Andy Hunt
quelle
Ich verstehe. Wie sieht es in diesem Szenario mit der Fehlerbehandlung (und Berichterstellung) für die Clients aus?
Tony E. Stark
Ich meine, wie melde ich REST-Clients Fehler, die bei der Behandlung des UserCreatedEreignisses auftreten (z. B. doppelter Benutzername oder E-Mail- oder Datenbankausfall).
Tony E. Stark
Es hängt davon ab, wo Sie die Aktion ausführen. Wenn Sie sich im Benutzersystem befinden, können Sie alle Überprüfungen durchführen, in den Datenspeicher schreiben und dann das Ereignis veröffentlichen. Ansonsten halte ich es für absolut akzeptabel, eine Standard-HTTP-Anforderung an den /users/Endpunkt auszuführen und diesem System zu ermöglichen, sein Ereignis zu veröffentlichen, wenn dies erfolgreich war, und auf die Anforderung mit der neuen Entität zu antworten
Andy Hunt,
0

Bei einem ereignisgesteuerten System spielen die asynchronen Aspekte normalerweise eine Rolle, wenn etwas geändert wird, das den Status darstellt, z. B. eine Datenbank oder eine aggregierte Ansicht einiger Daten. In Ihrem Beispiel könnte ein Aufruf von GET / api / users einfach die Antwort eines Dienstes zurückgeben, der eine aktuelle Darstellung einer Liste von Benutzern im System enthält. In einem anderen Szenario kann die Anforderung an GET / api / users dazu führen, dass ein Dienst den Ereignisstrom seit dem letzten Snapshot von Benutzern verwendet, um einen weiteren Snapshot zu erstellen und einfach die Ergebnisse zurückzugeben. Ein ereignisgesteuertes System ist von der Anforderung bis zur Antwort nicht unbedingt nur asynchron, sondern befindet sich in der Regel auf der Ebene, auf der Dienste mit anderen Diensten interagieren müssen. Häufig ist es nicht sinnvoll, eine GET-Anforderung asynchron zurückzugeben, sodass Sie einfach die Antwort eines Dienstes zurückgeben können.

Lloyd Moore
quelle