Redaktion – Die siebenteilige Artikelserie ist nun komplett:
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.
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.
Später werden wir uns bestimmte IPC-Technologien ansehen. Lassen Sie uns jedoch zunächst verschiedene Designprobleme untersuchen.
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:
Die zweite Dimension ist, ob die Interaktion synchron oder asynchron ist:
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:
Es gibt die folgenden Arten von Eins-zu-viele-Interaktionen:
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.
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.
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.
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.
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:
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.
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.
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.
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:
Die Verwendung von Messaging hat jedoch auch einige Nachteile:
Nachdem wir uns nun mit der Verwendung von Messaging-basiertem IPC befasst haben, untersuchen wir nun Request/Response-basiertes 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.
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:
Das folgende Diagramm zeigt eine der Möglichkeiten, wie die Taxiruf-Anwendung REST verwenden kann.
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.
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.POST-
Anfrage, die die auszuführende Aktion und alle Parameter angibt.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.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:
curl
testen (vorausgesetzt, es wird JSON oder ein anderes Textformat verwendet).Die Verwendung von HTTP bringt einige Nachteile mit sich:
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.
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.
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.
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:
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."