BLOG | NGINX

Erstellen von Microservices: Interprozesskommunikation in einer Microservices-Architektur

NGINX-Teil-von-F5-horiz-schwarz-Typ-RGB
Chris Richardson Miniaturbild
Chris Richardson
Veröffentlicht am 24. Juli 2015

Redaktion – Die siebenteilige Artikelserie ist nun komplett:

  1. Einführung in Microservices
  2. Erstellen von Microservices: Verwenden eines API-Gateways
  3. Erstellen von Microservices: Interprozesskommunikation in einer Microservices-Architektur (dieser Artikel)
  4. Service Discovery in einer Microservices-Architektur
  5. Ereignisgesteuertes Datenmanagement für Microservices
  6. Auswählen einer Bereitstellungsstrategie für Microservices
  7. Refactoring eines Monolithen in Microservices

Sie können den kompletten Artikelsatz sowie Informationen zur Implementierung von Microservices mit NGINX Plus auch als E-Book herunterladen – Microservices: Vom Entwurf bis zur Bereitstellung . Schauen Sie sich auch die neue Seite mit Microservices-Lösungen an.

Dies ist der dritte Artikel unserer Reihe zum Erstellen von Anwendungen mit einer Microservices-Architektur. Der erste Artikel stellt das Muster der Microservices-Architektur vor, vergleicht es mit dem Muster der monolithischen Architektur und diskutiert die Vor- und Nachteile der Verwendung von Microservices. Der zweite Artikel beschreibt, wie Clients einer Anwendung über einen als API-Gateway bezeichneten Vermittler mit den Microservices kommunizieren. In diesem Artikel schauen wir uns an, wie die Dienste innerhalb eines Systems miteinander kommunizieren. Der vierte Artikel untersucht das eng damit verbundene Problem der Diensterkennung.

Einführung

In einer monolithischen Anwendung rufen sich Komponenten gegenseitig über Methoden- oder Funktionsaufrufe auf Sprachebene auf. Im Gegensatz dazu ist eine auf Microservices basierende Anwendung ein verteiltes System, das auf mehreren Maschinen ausgeführt wird. Jede Serviceinstanz ist normalerweise ein Prozess. Folglich müssen Dienste, wie das folgende Diagramm zeigt, über einen Interprozesskommunikationsmechanismus (IPC) interagieren.


In einer Microservices-Anwendung benötigen die Dienste einen Mechanismus zur Interprozesskommunikation (IPC) (wohingegen Module in einem Monolithen Routinen aufrufen können).

Später werden wir uns bestimmte IPC-Technologien ansehen. Lassen Sie uns jedoch zunächst verschiedene Designprobleme untersuchen.

Interaktionsstile

Bei der Auswahl eines IPC-Mechanismus für einen Dienst ist es sinnvoll, zunächst darüber nachzudenken, wie Dienste interagieren. Es gibt zahlreiche Interaktionsstile zwischen Kunde und Dienst. Sie können anhand von zwei Dimensionen kategorisiert werden. Die erste Dimension ist, ob es sich um eine Eins-zu-eins- oder eine Eins-zu-viele-Interaktion handelt:

  • Eins-zu-eins – Jede Client-Anfrage wird von genau einer Serviceinstanz verarbeitet.
  • Eins-zu-viele – Jede Anfrage wird von mehreren Serviceinstanzen verarbeitet.

Die zweite Dimension ist, ob die Interaktion synchron oder asynchron ist:

  • Synchron – Der Client erwartet eine zeitnahe Antwort vom Dienst und kann während der Wartezeit sogar blockieren.
  • Asynchron – Der Client wird nicht blockiert, während er auf eine Antwort wartet, und die Antwort (falls vorhanden) wird nicht unbedingt sofort gesendet.

Die folgende Tabelle zeigt die verschiedenen Interaktionsstile.

  Einzelunterricht Eins-zu-viele
Synchron Anfrage/Antwort  — 
Asynchron Benachrichtigung Veröffentlichen/Abonnieren
Anforderung/asynchrone Antwort Veröffentlichen/asynchrone Antworten

Es gibt folgende Arten der Eins-zu-eins-Interaktion:

  • Anfrage/Antwort – Ein Client stellt eine Anfrage an einen Dienst und wartet auf eine Antwort. Der Kunde erwartet eine zeitnahe Antwort. In einer threadbasierten Anwendung kann der Thread, der die Anforderung stellt, während des Wartens sogar blockiert werden.
  • Benachrichtigung (auch als Einweganfrage bekannt) – Ein Client sendet eine Anfrage an einen Dienst, aber es wird keine Antwort erwartet oder gesendet.
  • Anfrage/asynchrone Antwort – Ein Client sendet eine Anfrage an einen Dienst, der asynchron antwortet. Der Client blockiert nicht während des Wartens und ist auf die Annahme ausgelegt, dass die Antwort möglicherweise eine Weile ausbleibt.

Es gibt die folgenden Arten von Eins-zu-viele-Interaktionen:

  • Veröffentlichen/Abonnieren – Ein Client veröffentlicht eine Benachrichtigungsnachricht, die von null oder mehr interessierten Diensten genutzt wird.
  • Veröffentlichen/asynchrone Antworten – Ein Client veröffentlicht eine Anforderungsnachricht und wartet dann eine bestimmte Zeit auf Antworten von interessierten Diensten.

Jeder Dienst verwendet normalerweise eine Kombination dieser Interaktionsstile. Für einige Dienste ist ein einzelner IPC-Mechanismus ausreichend. Andere Dienste müssen möglicherweise eine Kombination von IPC-Mechanismen verwenden. Das folgende Diagramm zeigt, wie Dienste in einer Taxiruf-Anwendung interagieren können, wenn der Benutzer eine Fahrt anfordert.


Die Dienste verwenden eine Kombination aus Benachrichtigungen, Anforderung/Antwort und Veröffentlichen/Abonnieren. Beispielsweise sendet das Smartphone des Fahrgastes eine Benachrichtigung an den Fahrtenmanagement-Dienst, um eine Abholung anzufordern. Der Reisemanagementdienst überprüft, ob das Konto des Passagiers aktiv ist, indem er per Anfrage/Antwort den Passagierdienst aufruft. Der Fahrtenverwaltungsdienst erstellt dann die Fahrt und benachrichtigt per Publizieren/abonnieren andere Dienste, einschließlich des Dispatchers, der einen verfügbaren Fahrer sucht.

Nachdem wir uns nun die Interaktionsstile angesehen haben, schauen wir uns an, wie APIs definiert werden.

Definieren von APIs

Die API eines Dienstes ist ein Vertrag zwischen dem Dienst und seinen Clients. Unabhängig von Ihrem gewählten IPC-Mechanismus ist es wichtig, die API eines Dienstes mithilfe einer Art Schnittstellendefinitionssprache (IDL) genau zu definieren. Es gibt sogar gute Argumente für die Verwendung eines API-First-Ansatzes zur Definition von Diensten. Sie beginnen mit der Entwicklung eines Dienstes, indem Sie die Schnittstellendefinition schreiben und diese mit den Client-Entwicklern überprüfen. Erst nach der Iteration der API-Definition implementieren Sie den Dienst. Wenn Sie diesen Entwurf im Vorfeld erstellen, erhöhen sich Ihre Chancen, einen Dienst zu entwickeln, der die Anforderungen Ihrer Kunden erfüllt.

Wie Sie später in diesem Artikel sehen werden, hängt die Art der API-Definition davon ab, welchen IPC-Mechanismus Sie verwenden. Wenn Sie Messaging verwenden, besteht die API aus den Nachrichtenkanälen und den Nachrichtentypen. Wenn Sie HTTP verwenden, besteht die API aus den URLs und den Anforderungs- und Antwortformaten. Später werden wir einige IDLs genauer beschreiben.

Weiterentwickelnde APIs

Die API eines Dienstes ändert sich zwangsläufig im Laufe der Zeit. In einer monolithischen Anwendung ist es normalerweise unkompliziert, die API zu ändern und alle Anrufer zu aktualisieren. In einer auf Microservices basierenden Anwendung ist es viel schwieriger, selbst wenn alle Verbraucher Ihrer API andere Dienste in derselben Anwendung sind. Normalerweise können Sie nicht alle Clients zwingen, im Gleichschritt mit dem Dienst zu aktualisieren. Außerdem werden Sie neue Versionen eines Dienstes wahrscheinlich schrittweise bereitstellen, sodass sowohl alte als auch neue Versionen eines Dienstes gleichzeitig ausgeführt werden. Es ist wichtig, eine Strategie zum Umgang mit diesen Problemen zu haben.

Wie Sie mit einer API-Änderung umgehen, hängt von der Größe der Änderung ab. Einige Änderungen sind geringfügig und abwärtskompatibel mit der vorherigen Version. Sie können beispielsweise Anfragen oder Antworten Attribute hinzufügen. Es ist sinnvoll, Clients und Dienste so zu gestalten, dass sie dem Robustheitsprinzip entsprechen. Clients, die eine ältere API verwenden, sollten weiterhin mit der neuen Version des Dienstes arbeiten. Der Dienst stellt Standardwerte für die fehlenden Anforderungsattribute bereit und die Clients ignorieren alle zusätzlichen Antwortattribute. Es ist wichtig, einen IPC-Mechanismus und ein Nachrichtenformat zu verwenden, mit denen Sie Ihre APIs problemlos weiterentwickeln können.

Manchmal müssen Sie jedoch umfangreiche, inkompatible Änderungen an einer API vornehmen. Da Sie Clients nicht zu einem sofortigen Upgrade zwingen können, muss ein Dienst ältere Versionen der API für einen gewissen Zeitraum unterstützen. Wenn Sie einen HTTP-basierten Mechanismus wie REST verwenden, besteht eine Möglichkeit darin, die Versionsnummer in die URL einzubetten. Jede Serviceinstanz kann mehrere Versionen gleichzeitig verarbeiten. Alternativ können Sie verschiedene Instanzen bereitstellen, die jeweils eine bestimmte Version verarbeiten.

Umgang mit teilweisen Fehlern

Wie im vorherigen Artikel zum API Gateway<.htmla> erwähnt, besteht in einem verteilten System immer das Risiko eines Teilausfalls. Da es sich bei Clients und Diensten um getrennte Prozesse handelt, kann ein Dienst möglicherweise nicht rechtzeitig auf die Anfrage eines Clients reagieren. Ein Dienst ist möglicherweise aufgrund einer Störung oder Wartung ausgefallen. Oder der Dienst ist überlastet und reagiert extrem langsam auf Anfragen.

Betrachten Sie beispielsweise das Szenario „Produktdetails“ aus diesem Artikel. Stellen wir uns vor, dass der Empfehlungsdienst nicht reagiert. Eine naive Implementierung eines Clients kann beim Warten auf eine Antwort auf unbestimmte Zeit blockiert sein. Dies hätte nicht nur eine negative Benutzererfahrung zur Folge, sondern würde in vielen Anwendungen auch eine wertvolle Ressource, beispielsweise einen Thread, verbrauchen. Irgendwann gehen der Laufzeitumgebung die Threads aus und sie reagiert nicht mehr, wie in der folgenden Abbildung gezeigt.


Eine Microservices-App muss so konzipiert sein, dass sie Teilfehler bewältigen kann. Andernfalls kann es passieren, dass der Laufzeitumgebung die Threads ausgehen, wenn Clients beim Warten auf einen nicht reagierenden Dienst blockiert werden.

Um dieses Problem zu vermeiden, müssen Sie Ihre Dienste unbedingt so konzipieren, dass sie mit Teilausfällen umgehen können.

Ein guter Ansatz, dem man folgen kann, ist der von Netflix beschriebene . Zu den Strategien zum Umgang mit Teilausfällen gehören:

  • Netzwerk-Timeouts – Blockieren Sie niemals auf unbestimmte Zeit und verwenden Sie immer Timeouts, wenn Sie auf eine Antwort warten. Durch die Verwendung von Timeouts wird sichergestellt, dass Ressourcen nie auf unbestimmte Zeit gebunden werden.
  • Begrenzung der Anzahl ausstehender Anfragen – Legen Sie eine Obergrenze für die Anzahl ausstehender Anfragen fest, die ein Client bei einem bestimmten Dienst haben kann. Wenn das Limit erreicht ist, sind weitere Anfragen wahrscheinlich sinnlos und diese Versuche müssen sofort fehlschlagen.
  • Circuit-Breaker-Muster – Verfolgen Sie die Anzahl erfolgreicher und fehlgeschlagener Anfragen. Überschreitet die Fehlerrate einen konfigurierten Grenzwert, lösen Sie den Schutzschalter aus, sodass weitere Versuche sofort fehlschlagen. Wenn eine große Anzahl von Anfragen fehlschlägt, deutet das darauf hin, dass der Dienst nicht verfügbar ist und das Senden von Anfragen sinnlos ist. Nach einer Zeitüberschreitung sollte der Client es erneut versuchen und, wenn dies erfolgreich ist, den Leistungsschalter schließen.
  • Fallbacks bereitstellen – Führen Sie eine Fallback-Logik aus, wenn eine Anforderung fehlschlägt. Geben Sie beispielsweise zwischengespeicherte Daten oder einen Standardwert wie einen leeren Satz von Empfehlungen zurück.

Netflix Hystrix ist eine Open-Source-Bibliothek, die diese und andere Muster implementiert. Wenn Sie die JVM verwenden, sollten Sie unbedingt die Verwendung von Hystrix in Betracht ziehen. Und wenn Sie in einer Nicht-JVM-Umgebung arbeiten, sollten Sie eine entsprechende Bibliothek verwenden.

IPC-Technologien

Es stehen viele verschiedene IPC-Technologien zur Auswahl. Dienste können synchrone, auf Anfragen/Antworten basierende Kommunikationsmechanismen wie HTTP-basiertes REST oder Thrift verwenden. Alternativ können sie asynchrone, nachrichtenbasierte Kommunikationsmechanismen wie AMQP oder STOMP verwenden. Darüber hinaus gibt es eine Vielzahl unterschiedlicher Nachrichtenformate. Dienste können für Menschen lesbare, textbasierte Formate wie JSON oder XML verwenden. Alternativ können sie ein Binärformat (das effizienter ist) wie Avro oder Protocol Buffers verwenden. Später werden wir uns synchrone IPC-Mechanismen ansehen, aber zuerst wollen wir asynchrone IPC-Mechanismen besprechen.

Asynchrone, nachrichtenbasierte Kommunikation

Beim Messaging kommunizieren Prozesse durch asynchronen Nachrichtenaustausch. Ein Client stellt eine Anfrage an einen Dienst, indem er ihm eine Nachricht sendet. Wenn eine Antwort des Dienstes erwartet wird, sendet dieser eine separate Nachricht an den Client zurück. Da die Kommunikation asynchron ist, wird der Client nicht blockiert, während er auf eine Antwort wartet. Stattdessen wird dem Client mit der Annahme geschrieben, dass die Antwort nicht sofort eintrifft.

Eine Nachricht besteht aus Headern (Metadaten wie z. B. dem Absender) und einem Nachrichtentext. Nachrichten werden über Kanäle ausgetauscht. Eine beliebige Anzahl von Produzenten kann Nachrichten an einen Kanal senden. Ebenso können beliebig viele Verbraucher Nachrichten von einem Kanal empfangen. Es gibt zwei Arten von Kanälen: Point-to-Point und Publish-Subscribe . Ein Punkt-zu-Punkt-Kanal übermittelt eine Nachricht an genau einen der Verbraucher, der den Kanal liest. Dienste verwenden Punkt-zu-Punkt-Kanäle für die zuvor beschriebenen Eins-zu-eins-Interaktionsstile. Ein Publish‑Subscribe-Kanal übermittelt jede Nachricht an alle angeschlossenen Verbraucher. Dienste verwenden Publish‑Subscribe‑Kanäle für die oben beschriebenen Eins‑zu‑viele‑Interaktionsstile.

Das folgende Diagramm zeigt, wie die Taxirufanwendung Publish‑Subscribe‑Kanäle verwenden könnte.


Microservices in Taxi-Vermittlungsanwendungen nutzen Publish-Subscribe-Kanäle für die Kommunikation zwischen Dispatcher und anderen Diensten.

Der Fahrtenverwaltungsdienst benachrichtigt interessierte Dienste wie den Dispatcher über eine neue Fahrt, indem er eine „Fahrt erstellt“-Nachricht in einen Publish‑Subscribe-Kanal schreibt. Der Dispatcher sucht nach einem verfügbaren Treiber und benachrichtigt andere Dienste, indem er eine „Driver Proposed“-Nachricht an einen Publish‑Subscribe-Kanal schreibt.

Es stehen zahlreiche Messagingsysteme zur Auswahl. Sie sollten ein System auswählen, das verschiedene Programmiersprachen unterstützt. Einige Messagingsysteme unterstützen Standardprotokolle wie AMQP und STOMP. Andere Nachrichtensysteme haben proprietäre, aber dokumentierte Protokolle. Zur Auswahl steht eine große Anzahl an Open-Source-Messaging-Systemen, darunter RabbitMQ , Apache Kafka , Apache ActiveMQ und NSQ . Auf einer hohen Ebene unterstützen sie alle irgendeine Form von Nachrichten und Kanälen. Sie alle streben danach, zuverlässig, leistungsstark und skalierbar zu sein. Allerdings gibt es im Detail erhebliche Unterschiede zwischen den Nachrichtenmodellen der einzelnen Broker.

Die Verwendung von Messaging bietet viele Vorteile:

  • Entkoppelt den Client vom Dienst – Ein Client stellt eine Anfrage, indem er einfach eine Nachricht an den entsprechenden Kanal sendet. Der Client hat von den Serviceinstanzen überhaupt keine Kenntnis. Um den Standort einer Serviceinstanz zu bestimmen, ist die Verwendung eines Erkennungsmechanismus nicht erforderlich.
  • Nachrichtenpufferung – Bei einem synchronen Anforderungs-/Antwortprotokoll wie etwa HTTP müssen sowohl der Client als auch der Dienst für die Dauer des Austauschs verfügbar sein. Im Gegensatz dazu stellt ein Nachrichtenbroker die in einen Kanal geschriebenen Nachrichten in eine Warteschlange, bis sie vom Verbraucher verarbeitet werden können. Dies bedeutet beispielsweise, dass ein Online-Shop Bestellungen von Kunden annehmen kann, selbst wenn das Auftragsabwicklungssystem langsam oder nicht verfügbar ist. Die Bestellnachrichten reihen sich einfach in eine Warteschlange ein.
  • Flexible Client-Service-Interaktionen – Messaging unterstützt alle zuvor beschriebenen Interaktionsstile.
  • Explizite Interprozesskommunikation – RPC-basierte Mechanismen versuchen, den Aufruf eines Remotedienstes genauso aussehen zu lassen wie den Aufruf eines lokalen Dienstes. Aufgrund physikalischer Gesetze und der Möglichkeit eines teilweisen Versagens unterscheiden sie sich jedoch erheblich. Durch die Nachrichtenübermittlung werden diese Unterschiede sehr deutlich gemacht, sodass Entwickler nicht in einem falschen Sicherheitsgefühl wiegen.

Die Verwendung von Messaging hat jedoch auch einige Nachteile:

  • Zusätzliche betriebliche Komplexität – Das Nachrichtensystem ist eine weitere Systemkomponente, die installiert, konfiguriert und betrieben werden muss. Es ist wichtig, dass der Nachrichtenbroker hochverfügbar ist, da sonst die Systemzuverlässigkeit beeinträchtigt wird.
  • Komplexität der Implementierung einer auf Anfragen/Antworten basierenden Interaktion – Die Implementierung einer Interaktion im Stil von Anfragen/Antworten erfordert einiges an Arbeit. Jede Anforderungsnachricht muss eine Antwortkanalkennung und eine Korrelationskennung enthalten. Der Dienst schreibt eine Antwortnachricht mit der Korrelations-ID in den Antwortkanal. Der Client verwendet die Korrelations-ID, um die Antwort der Anfrage zuzuordnen. Oft ist es einfacher, einen IPC-Mechanismus zu verwenden, der Anfrage/Antwort direkt unterstützt.

Nachdem wir uns nun mit der Verwendung von Messaging-basiertem IPC befasst haben, untersuchen wir nun Request/Response-basiertes IPC.

Synchron, Anfrage/Antwort IPC

Bei Verwendung eines synchronen, anforderungs-/antwortbasierten IPC-Mechanismus sendet ein Client eine Anforderung an einen Dienst. Der Dienst verarbeitet die Anfrage und sendet eine Antwort zurück. Bei vielen Clients wird der Thread, der die Anforderung stellt, blockiert, während er auf eine Antwort wartet. Andere Clients verwenden möglicherweise asynchronen, ereignisgesteuerten Clientcode, der möglicherweise von Futures oder Rx Observables gekapselt ist. Anders als beim Messaging geht der Kunde allerdings davon aus, dass die Antwort zeitnah eintrifft. Es stehen zahlreiche Protokolle zur Auswahl. Zwei beliebte Protokolle sind REST und Thrift. Schauen wir uns zunächst REST an.

AUSRUHEN

Heute ist es in Mode, APIs im RESTful -Stil zu entwickeln. REST ist ein IPC-Mechanismus, der (fast immer) HTTP verwendet. Ein Schlüsselkonzept in REST ist eine Ressource, die normalerweise ein Geschäftsobjekt wie einen Kunden oder ein Produkt oder eine Sammlung von Geschäftsobjekten darstellt. REST verwendet die HTTP-Verben zur Manipulation von Ressourcen, auf die über eine URL verwiesen wird. Beispielsweise gibt eine GET- Anfrage die Darstellung einer Ressource zurück, die in Form eines XML-Dokuments oder eines JSON-Objekts vorliegen kann. Eine POST- Anfrage erstellt eine neue Ressource und eine PUT- Anfrage aktualisiert eine Ressource. Um Roy Fielding, den Erfinder von REST, zu zitieren:

REST bietet eine Reihe von Architekturbeschränkungen, die bei Gesamtanwendung die Skalierbarkeit von Komponenteninteraktionen, die Allgemeingültigkeit von Schnittstellen, die unabhängige Bereitstellung von Komponenten und Zwischenkomponenten betonen, um die Interaktionslatenz zu reduzieren, die Sicherheit zu erhöhen und Legacy-Systeme zu kapseln.

Das folgende Diagramm zeigt eine der Möglichkeiten, wie die Taxiruf-Anwendung REST verwenden kann.


In einer auf Microservices basierenden Taxi-App sendet das Smartphone des Passagiers eine POST-Anfrage, die der Microservice für das Fahrtmanagement in eine GET-Anfrage an den Microservice für die Passagierverifizierung umwandelt.

Das Smartphone des Passagiers fordert eine Fahrt an, indem es eine POST- Anfrage an die /trips- Ressource des Fahrtenmanagementdienstes sendet. Dieser Dienst bearbeitet die Anfrage, indem er eine GET- Anfrage für Informationen über den Passagier an den Passagierverwaltungsdienst sendet. Nachdem überprüft wurde, ob der Passagier berechtigt ist, eine Reise zu erstellen, erstellt der Reiseverwaltungsdienst die Reise und gibt eine201 Reaktion auf das Smartphone.

Viele Entwickler behaupten, ihre HTTP-basierten APIs seien RESTful. Dies ist jedoch nicht bei allen der Fall, wie Fielding in diesem Blogbeitrag beschreibt. Leonard Richardson (nicht verwandt) definiert ein sehr nützliches Reifegradmodell für REST , das aus den folgenden Ebenen besteht.

  • Level 0 – Clients einer API der Ebene 0 rufen den Dienst auf, indem sie HTTP- POST- Anfragen an seinen einzigen URL-Endpunkt senden. Jede Anfrage gibt die auszuführende Aktion, das Ziel der Aktion (z. B. das Geschäftsobjekt) und etwaige Parameter an.
  • Level 1 – Eine Level-1-API unterstützt die Idee von Ressourcen. Um eine Aktion für eine Ressource auszuführen, sendet ein Client eine POST- Anfrage, die die auszuführende Aktion und alle Parameter angibt.
  • Level 2 – Eine API der Ebene 2 verwendet HTTP-Verben, um Aktionen auszuführen: GET zum Abrufen, POST zum Erstellen und PUT zum Aktualisieren. Die Abfrageparameter und der Textkörper der Anforderung (sofern vorhanden) geben die Parameter der Aktion an. Dadurch können Dienste die Webinfrastruktur wie etwa das Caching für GET -Anfragen nutzen.
  • Level 3 – Das Design einer Level-3-API basiert auf dem schrecklich benannten HATEOAS-Prinzip (Hypertext As The Engine Of Application State). Die Grundidee besteht darin, dass die von einer GET- Anfrage zurückgegebene Darstellung einer Ressource Links zum Ausführen der zulässigen Aktionen für diese Ressource enthält. Beispielsweise kann ein Kunde eine Bestellung über einen Link in der Bestelldarstellung stornieren, die als Antwort auf die zum Abrufen der Bestellung gesendete GET -Anfrage zurückgegeben wird. Zu den Vorteilen von HATEOAS gehört, dass URLs nicht mehr fest in den Client-Code eingebunden werden müssen. Ein weiterer Vorteil besteht darin, dass der Client nicht raten muss, welche Aktionen für eine Ressource in ihrem aktuellen Zustand ausgeführt werden können, da die Darstellung einer Ressource Links zu den zulässigen Aktionen enthält.

Die Verwendung eines auf HTTP basierenden Protokolls bietet zahlreiche Vorteile:

  • HTTP ist einfach und vertraut.
  • Sie können eine HTTP-API mithilfe einer Erweiterung wie Postman in einem Browser oder über die Befehlszeile mit curl testen (vorausgesetzt, es wird JSON oder ein anderes Textformat verwendet).
  • Es unterstützt direkt die Kommunikation im Anfrage-/Antwortstil.
  • HTTP ist natürlich Firewall-freundlich.
  • Es ist kein Zwischenbroker erforderlich, was die Systemarchitektur vereinfacht.

Die Verwendung von HTTP bringt einige Nachteile mit sich:

  • Es unterstützt nur direkt den Anfrage-/Antwort-Interaktionsstil. Sie können HTTP für Benachrichtigungen verwenden, aber der Server muss immer eine HTTP-Antwort senden.
  • Da Client und Dienst direkt kommunizieren (ohne einen Vermittler zum Puffern von Nachrichten), müssen sie beide für die Dauer des Austauschs ausgeführt werden.
  • Der Client muss den Standort (d. h. die URL) jeder Serviceinstanz kennen. Wie im vorherigen Artikel zum API-Gateway beschrieben, ist dies in einer modernen Anwendung kein triviales Problem. Clients müssen einen Diensterkennungsmechanismus verwenden, um Dienstinstanzen zu lokalisieren.

Die Entwickler-Community hat vor Kurzem den Wert einer Schnittstellendefinitionssprache für RESTful-APIs wiederentdeckt. Es gibt einige Optionen, darunter RAML und Swagger . Einige IDLs wie Swagger ermöglichen Ihnen, das Format von Anforderungs- und Antwortnachrichten zu definieren. Andere, wie RAML, erfordern die Verwendung einer separaten Spezifikation, beispielsweise JSON Schema . IDLs beschreiben nicht nur APIs, sondern verfügen normalerweise auch über Tools, die aus einer Schnittstellendefinition Client-Stubs und Server-Skelette generieren.

Sparsamkeit

Apache Thrift ist eine interessante Alternative zu REST. Es ist ein Framework zum Schreiben sprachübergreifender RPC- Clients und -Server. Thrift bietet eine IDL im C-Stil zum Definieren Ihrer APIs. Sie verwenden den Thrift-Compiler, um clientseitige Stubs und serverseitige Skeletons zu generieren. Der Compiler generiert Code für eine Vielzahl von Sprachen, darunter C++, Java, Python, PHP, Ruby, Erlang und Node.js.

Eine Thrift-Schnittstelle besteht aus einem oder mehreren Diensten. Eine Dienstdefinition ist analog zu einer Java-Schnittstelle. Es ist eine Sammlung stark typisierter Methoden. Thrift-Methoden können entweder einen (möglicherweise ungültigen) Wert zurückgeben oder als Einwegmethoden definiert werden. Methoden, die einen Wert zurückgeben, implementieren den Anfrage-/Antwortstil der Interaktion. Der Client wartet auf eine Antwort und löst möglicherweise eine Ausnahme aus. Einwegmethoden entsprechen dem Benachrichtigungsstil der Interaktion. Der Server sendet keine Antwort.

Thrift unterstützt verschiedene Nachrichtenformate: JSON, Binär und kompakte Binärdatei. Binär ist effizienter als JSON, da es schneller zu dekodieren ist. Und wie der Name schon sagt, ist Compact Binary ein platzsparendes Format. JSON ist natürlich menschen- und browserfreundlich. Thrift bietet Ihnen außerdem eine Auswahl an Transportprotokollen, darunter Raw TCP und HTTP. Raw TCP ist wahrscheinlich effizienter als HTTP. HTTP ist jedoch Firewall-, Browser- und Benutzerfreundlich.

Nachrichtenformate

Nachdem wir uns nun HTTP und Thrift angesehen haben, wollen wir uns nun dem Thema Nachrichtenformate widmen. Wenn Sie ein Nachrichtensystem oder REST verwenden, können Sie Ihr Nachrichtenformat auswählen. Andere IPC-Mechanismen wie Thrift unterstützen möglicherweise nur eine kleine Anzahl von Nachrichtenformaten, vielleicht nur eins. In beiden Fällen ist es wichtig, ein sprachübergreifendes Nachrichtenformat zu verwenden. Auch wenn Sie Ihre Microservices heute in einer einzigen Sprache schreiben, werden Sie in Zukunft wahrscheinlich andere Sprachen verwenden.

Es gibt zwei Hauptarten von Nachrichtenformaten: Text und Binär. Beispiele für textbasierte Formate sind JSON und XML. Ein Vorteil dieser Formate besteht darin, dass sie nicht nur für Menschen lesbar, sondern auch selbstbeschreibend sind. In JSON werden die Attribute eines Objekts durch eine Sammlung von Name-Wert-Paaren dargestellt. In ähnlicher Weise werden in XML die Attribute durch benannte Elemente und Werte dargestellt. Dadurch kann der Verbraucher einer Nachricht nur die Werte auswählen, die ihn interessieren, und den Rest ignorieren. Folglich können kleinere Änderungen am Nachrichtenformat problemlos abwärtskompatibel sein.

Die Struktur von XML-Dokumenten wird durch ein XML-Schema festgelegt. Mit der Zeit ist der Entwickler-Community klar geworden, dass auch JSON einen ähnlichen Mechanismus benötigt. Eine Möglichkeit besteht darin, JSON Schema entweder eigenständig oder als Teil eines IDL wie Swagger zu verwenden.

Ein Nachteil der Verwendung eines textbasierten Nachrichtenformats besteht darin, dass die Nachrichten tendenziell ausführlich sind, insbesondere XML. Da die Nachrichten selbstbeschreibend sind, enthält jede Nachricht neben den Werten auch die Namen der Attribute. Ein weiterer Nachteil ist der Aufwand für die Textanalyse. Aus diesem Grund sollten Sie die Verwendung eines Binärformats in Erwägung ziehen.

Es stehen mehrere Binärformate zur Auswahl. Wenn Sie Thrift RPC verwenden, können Sie binäres Thrift verwenden. Wenn Sie das Nachrichtenformat auswählen können, zählen Protocol Buffers und Apache Avro zu den beliebten Optionen. Beide Formate bieten eine typisierte IDL zum Definieren der Struktur Ihrer Nachrichten. Ein Unterschied besteht jedoch darin, dass Protocol Buffers getaggte Felder verwendet, während ein Avro-Verbraucher das Schema kennen muss, um Nachrichten zu interpretieren. Daher ist die API-Weiterentwicklung mit Protocol Buffers einfacher als mit Avro. Dieser Blogbeitrag ist ein hervorragender Vergleich von Thrift, Protocol Buffers und Avro.

Zusammenfassung

Microservices müssen über einen Interprozesskommunikationsmechanismus kommunizieren. Beim Entwurf der Kommunikation Ihrer Dienste müssen Sie verschiedene Aspekte berücksichtigen: Wie interagieren die Dienste, wie wird die API für jeden Dienst angegeben, wie werden die APIs weiterentwickelt und wie werden teilweise Fehler behandelt. Es gibt zwei Arten von IPC-Mechanismen, die Microservices verwenden können: asynchrone Nachrichtenübermittlung und synchrone Anforderungen/Antworten. Im nächsten Artikel der Reihe werden wir uns mit dem Problem der Diensterkennung in einer Microservices-Architektur befassen.

Redaktion – Die siebenteilige Artikelserie ist nun komplett:

  1. Einführung in Microservices
  2. Erstellen von Microservices: Verwenden eines API-Gateways
  3. Erstellen von Microservices: Interprozesskommunikation in einer Microservices-Architektur (dieser Artikel)
  4. Service Discovery in einer Microservices-Architektur
  5. Ereignisgesteuertes Datenmanagement für Microservices
  6. Auswählen einer Bereitstellungsstrategie für Microservices
  7. Refactoring eines Monolithen in Microservices

Sie können den kompletten Artikelsatz sowie Informationen zur Implementierung von Microservices mit NGINX Plus auch als E-Book herunterladen – Microservices: Vom Entwurf bis zur Bereitstellung .

Gastblogger Chris Richardson ist der Gründer des ursprünglichen CloudFoundry.com , einer frühen Java PaaS (Platform as a Service) für Amazon EC2. Heute berät er Organisationen bei der Verbesserung der Entwicklung und Bereitstellung von Anwendungen. Unter http://microservices.io bloggt er außerdem regelmäßig über Microservices.


„Dieser Blogbeitrag kann auf Produkte verweisen, die nicht mehr verfügbar und/oder nicht mehr unterstützt werden. Die aktuellsten Informationen zu verfügbaren F5 NGINX-Produkten und -Lösungen finden Sie in unserer NGINX-Produktfamilie . NGINX ist jetzt Teil von F5. Alle vorherigen NGINX.com-Links werden auf ähnliche NGINX-Inhalte auf F5.com umgeleitet."