Kapitel 1. Einführung in gRPC
Diese Arbeit wurde mithilfe von KI übersetzt. Wir freuen uns über dein Feedback und deine Kommentare: translation-feedback@oreilly.com
Moderne Softwareanwendungen arbeiten selten isoliert. Vielmehr sind sie über Computernetzwerke miteinander verbunden und kommunizieren und koordinieren ihre Aktionen, indem sie sich gegenseitig Nachrichten übermitteln. Ein modernes Softwaresystem ist daher eine Sammlung verteilter Softwareanwendungen, die an verschiedenen Netzwerkstandorten laufen und über verschiedene Kommunikationsprotokolle miteinander kommunizieren. Ein Softwaresystem für den Online-Handel besteht zum Beispiel aus mehreren verteilten Anwendungen wie einer Auftragsverwaltungsanwendung, einer Kataloganwendung, Datenbanken und so weiter. Um die Geschäftsfunktionen eines Online-Einzelhandelssystems zu implementieren, müssen diese verteilten Anwendungen miteinander verbunden sein.
Hinweis
Microservices Architektur
Bei der Microservices-Architektur geht es darum, eine Softwareanwendung als eine Sammlung unabhängiger, autonomer (unabhängig entwickelter, bereitgestellter und skalierter), geschäftsfähigkeitsorientierter und lose gekoppelter Dienste aufzubauen.1
Mit dem Aufkommen der Microservices-Architektur und der nativen Cloud-Architektur werden herkömmliche Softwareanwendungen, die für mehrere Geschäftsfunktionen entwickelt wurden, in eine Sammlung von feinkörnigen, autonomen und auf die Geschäftsfunktionen ausgerichteten Einheiten aufgeteilt, die als Microservices bekannt sind. Ein auf Microservices basierendes Softwaresystem erfordert daher auch, dass die Microservices über das Netzwerk mit Hilfe von inter-process (oder inter-service oder inter-application) Kommunikationstechniken verbunden werden. Wenn wir zum Beispiel das gleiche Online-Einzelhandelssystem betrachten, das mit einer Microservices-Architektur implementiert wurde, findest du mehrere miteinander verbundene Microservices wie Bestellmanagement, Suche, Kasse, Versand usw. Im Gegensatz zu konventionellen Anwendungen steigt die Anzahl der Kommunikationsverbindungen im Netzwerk aufgrund der Feinkörnigkeit der Microservices an. Unabhängig davon, welchen Architekturstil du verwendest (konventionelle oder Microservices-Architektur), sind Techniken für die Kommunikation zwischen den Prozessen daher einer der wichtigsten Aspekte moderner verteilter Softwareanwendungen.
Die Kommunikation zwischen den Prozessen wird in der Regel durch Nachrichtenübermittlung im synchronen Anfrage-Antwort-Stil oder im asynchronen ereignisgesteuerten Stil realisiert. Bei der synchronen Kommunikation sendet der Client-Prozess eine Anforderungsnachricht über das Netzwerk an den Server-Prozess und wartet auf eine Antwortnachricht. Bei der asynchronen, ereignisgesteuerten Kommunikation kommunizieren Prozesse mit asynchroner Nachrichtenübermittlung, indem sie einen Vermittler, den sogenannten Event Broker, einsetzen. Je nach Anwendungsfall kannst du das Kommunikationsmuster auswählen, das du implementieren möchtest.
Wenn es darum geht, eine synchrone Anfrage-Antwort-Kommunikation für moderne native Cloud-Anwendungen und Microservices zu entwickeln, ist der gängigste und konventionelle Ansatz, sie als RESTful-Services zu erstellen, bei denen du deine Anwendung oder deinen Service als eine Sammlung von Ressourcen modellierst, auf die du über Netzwerkaufrufe, die über das HTTP-Protokoll erfolgen, zugreifen kannst und deren Zustand du ändern kannst. Für die meisten Anwendungsfälle sind RESTful-Dienste jedoch ziemlich sperrig, ineffizient und fehleranfällig für die Kommunikation zwischen den Prozessen. Oft wird eine hoch skalierbare, lose gekoppelte Interprozess-Kommunikationstechnologie benötigt, die effizienter ist als RESTful-Dienste. An dieser Stelle kommt gRPC ins Spiel, eine moderne Art der Interprozesskommunikation für den Aufbau verteilter Anwendungen und Microservices (wir werden gRPC später in diesem Kapitel mit der RESTful-Kommunikation vergleichen). gRPC verwendet in erster Linie einen synchronen Request-Response-Stil für die Kommunikation, kann aber auch vollständig asynchron oder im Streaming-Modus arbeiten, sobald die erste Kommunikation hergestellt ist.
In diesem Kapitel erfahren wir, was gRPC ist und was die Hauptgründe für die Erfindung eines solchen Kommunikationsprotokolls zwischen Prozessen sind. Wir lernen die wichtigsten Bausteine des gRPC-Protokolls mit Hilfe einiger realer Anwendungsfälle kennen. Außerdem ist es wichtig, die Techniken der Interprozesskommunikation zu kennen und zu wissen, wie sie sich im Laufe der Zeit entwickelt haben, damit du die Hauptprobleme verstehen kannst, die gRPC zu lösen versucht. Wir werden also diese Techniken durchgehen und sie miteinander vergleichen und gegenüberstellen. Beginnen wir unsere Diskussion über gRPC mit einem Blick darauf, was gRPC ist.
Was ist gRPC?
gRPC (das "g" steht in jeder gRPC-Version für etwas anderes) ist eine Technologie zur Kommunikation zwischen Prozessen, mit der du verteilte heterogene Anwendungen so einfach wie einen lokalen Funktionsaufruf verbinden, aufrufen, bedienen und debuggen kannst.
Wenn du eine gRPC-Anwendung entwickelst, definierst du als Erstes eine Dienstschnittstelle. Die Definition der Dienstschnittstelle enthält Informationen darüber, wie dein Dienst von den Verbrauchern genutzt werden kann, welche Methoden du den Verbrauchern erlaubst, aus der Ferne aufzurufen, welche Methodenparameter und Nachrichtenformate beim Aufrufen dieser Methoden verwendet werden sollen und so weiter. Die Sprache, die wir in der Dienstdefinition angeben, wird als Interface Definition Language (IDL) bezeichnet.
Anhand dieser Dienstdefinition kannst du den serverseitigen Code, das sogenannte Serverskelett, erstellen, das die serverseitige Logik vereinfacht, indem es Abstraktionen für die Kommunikation auf niedriger Ebene bereitstellt. Außerdem kannst du den clientseitigen Code, den so genannten Client-Stub, erstellen, der die clientseitige Kommunikation durch Abstraktionen vereinfacht, um die Kommunikation auf niedriger Ebene für verschiedene Programmiersprachen zu verbergen. Die Methoden, die du in der Definition der Serviceschnittstelle angibst, können von der Client-Seite genauso einfach aufgerufen werden wie ein lokaler Funktionsaufruf. Das zugrundeliegende gRPC-Framework übernimmt alle komplexen Aufgaben, die normalerweise mit der Durchsetzung strenger Dienstverträge, der Serialisierung von Daten, der Netzwerkkommunikation, der Authentifizierung, der Zugriffskontrolle, der Beobachtbarkeit usw. verbunden sind.
Um die grundlegenden Konzepte von gRPC zu verstehen, werfen wir einen Blick auf einen realen Anwendungsfall eines mit gRPC implementierten Microservices. Nehmen wir an, wir bauen eine Online-Einzelhandelsanwendung, die aus mehreren Microservices besteht. Wie in Abbildung 1-1 dargestellt, wollen wir einen Microservice erstellen, der die Details der in unserer Online-Einzelhandelsanwendung verfügbaren Produkte anzeigt (wir werden diesen Anwendungsfall in Kapitel 2 von Grund auf implementieren). Der Dienst ProductInfo
wird so modelliert, dass er über das Netzwerk als gRPC-Dienst bereitgestellt wird.
Die Definition des Dienstes wird in der Datei ProductInfo.proto angegeben, die sowohl von der Server- als auch von der Client-Seite verwendet wird, um den Code zu erzeugen. In diesem Beispiel gehen wir davon aus, dass der Dienst in der Sprache Go und der Kunde in Java implementiert ist. Die Netzwerkkommunikation zwischen dem Dienst und dem Kunden erfolgt über HTTP/2.
Kommen wir nun zu den Details der gRPC-Kommunikation. Der erste Schritt bei der Erstellung eines gRPC-Dienstes ist die Definition der Dienstschnittstelle mit den Methoden, die dieser Dienst bereitstellt, sowie den Eingabeparametern und Rückgabetypen. Kommen wir nun zu den Details der Dienstdefinition.
Dienst Definition
gRPC verwendet Protokollpuffer als IDL, um die Dienstschnittstelle zu definieren. Protokollpuffer sind ein sprachunabhängiger, plattformneutraler und erweiterbarer Mechanismus zur Serialisierung strukturierter Daten (wir werden in Kapitel 4 ausführlich auf die Grundlagen von Protokollpuffern eingehen, aber im Moment kannst du sie dir als einen Mechanismus zur Datenserialisierung vorstellen). Die Definition der Serviceschnittstelle wird in einer Proto-Datei angegeben - einer gewöhnlichen Textdatei mit der Erweiterung .proto. Du definierst gRPC-Dienste im normalen Protokollpufferformat, wobei die Parameter und Rückgabetypen der RPC-Methode als Protokollpuffer-Nachrichten angegeben werden. Da die Dienstdefinition eine Erweiterung der Protokollpuffer-Spezifikation ist, wird ein spezielles gRPC-Plug-in verwendet, um Code aus deiner Proto-Datei zu erzeugen.
In unserem Anwendungsbeispiel kann die Schnittstelle des Dienstes ProductInfo
mithilfe von Protokollpuffern definiert werden, wie in Beispiel 1-1 gezeigt. Die Dienstdefinition von ProductInfo
besteht aus einer Dienstschnittstellendefinition, in der wir die Remote-Methoden, ihre Eingabe- und Ausgabeparameter und die Typdefinition (oder Nachrichtenformate) dieser Parameter angeben.
Beispiel 1-1. gRPC-Dienstdefinition des ProductInfo-Dienstes mit Protokollpuffern
// ProductInfo.proto
syntax
=
"proto3"
;
package
ecommerce
;
service
ProductInfo
{
rpc
addProduct
(
Product
)
returns
(
ProductID
)
;
rpc
getProduct
(
ProductID
)
returns
(
Product
)
;
}
message
Product
{
string
id
=
1
;
string
name
=
2
;
string
description
=
3
;
}
message
ProductID
{
string
value
=
1
;
}
Die Dienstdefinition beginnt mit der Angabe der Protokollpufferversion (proto3), die wir verwenden.
Paketnamen werden verwendet, um Namensüberschneidungen zwischen Protokollnachrichtentypen zu vermeiden, und werden auch zur Codegenerierung verwendet.
Definieren der Serviceschnittstelle eines gRPC-Dienstes.
Remote-Methode zum Hinzufügen eines Produkts, die als Antwort die Produkt-ID zurückgibt.
Remote-Methode zum Abrufen eines Produkts anhand der Produkt-ID.
Definition des Nachrichtenformats/-typs von
Product
.Feld (Name-Wert-Paar), das die Produkt-ID mit eindeutigen Feldnummern enthält, die zur Identifizierung deiner Felder im Binärformat der Nachricht verwendet werden.
Benutzerdefinierter Typ für die Produktidentifikationsnummer.
Ein Dienst ist also eine Sammlung von Methoden (z. B. addProduct
und getProduct
), die aus der Ferne aufgerufen werden können. Jede Methode hat Eingabeparameter und Rückgabetypen, die wir entweder als Teil des Dienstes definieren oder die in die Protokollpufferdefinition importiert werden können.
Bei den Eingabe- und Rückgabeparametern kann es sich um einen benutzerdefinierten Typ handeln (z. B. die Typen Product
und ProductID
) oder um einen in der Dienstdefinition definierten bekannten Typ eines Protokollpuffers. Diese Typen sind als Nachrichten strukturiert, wobei jede Nachricht ein kleiner logischer Datensatz ist, der eine Reihe von Name-Wert-Paaren enthält, die Felder genannt werden. Diese Felder sind Name-Wert-Paare mit eindeutigen Feldnummern (z. B. string id = 1
), die zur Identifizierung der Felder im Binärformat der Nachricht verwendet werden.
Diese Dienstdefinition wird verwendet, um die Server- und Client-Seite deiner gRPC-Anwendung zu erstellen. Im nächsten Abschnitt gehen wir auf die Details der gRPC-Server-Implementierung ein.
gRPC Server
Sobald du eine Dienstdefinition erstellt hast, kannst du sie verwenden, um den serverseitigen oder clientseitigen Code mithilfe des Protokollpuffer-Compiler-Protokolls zu erzeugen. Mit dem gRPC-Plug-in für Protokollpuffer kannst du gRPC-Code auf der Server- und Client-Seite sowie den regulären Protokollpuffer-Code zum Auffüllen, Serialisieren und Abrufen deiner Nachrichtentypen erzeugen.
Auf der Serverseite implementiert der Server diese Dienstdefinition und führt einen gRPC-Server aus, um Client-Aufrufe zu bearbeiten. Auf der Serverseite musst du also Folgendes tun, damit der Dienst ProductInfo
seine Arbeit erledigt:
-
Implementiere die Servicelogik des generierten Service-Skeletts, indem du die Service-Basisklasse überschreibst.
-
Starte einen gRPC-Server, der auf Anfragen von Clients wartet und die Antworten des Dienstes zurückgibt.
Bei der Implementierung der Servicelogik muss zunächst das Service-Skelett aus der Service-Definition generiert werden. Im Codeschnipsel in Beispiel 1-2 findest du zum Beispiel die generierten Remote-Funktionen für den mit Go erstellten Dienst ProductInfo
. Im Körper dieser Remote-Funktionen kannst du die Logik der einzelnen Funktionen implementieren.
Beispiel 1-2. gRPC Server-seitige Implementierung des ProductInfo-Dienstes mit Go
import
(
...
"context"
pb
"github.com/grpc-up-and-running/samples/ch02/productinfo/go/proto"
"google.golang.org/grpc"
...
)
// ProductInfo implementation with Go
// Add product remote method
func
(
s
*
server
)
AddProduct
(
ctx
context
.
Context
,
in
*
pb
.
Product
)
(
*
pb
.
ProductID
,
error
)
{
// business logic
}
// Get product remote method
func
(
s
*
server
)
GetProduct
(
ctx
context
.
Context
,
in
*
pb
.
ProductID
)
(
*
pb
.
Product
,
error
)
{
// business logic
}
Sobald du die Dienstimplementierung fertig hast, musst du einen gRPC-Server laufen lassen, der auf Anfragen von Clients wartet, diese Anfragen an die Dienstimplementierung weiterleitet und die Antworten des Dienstes an den Client zurückschickt. Der Codeschnipsel in Beispiel 1-3 zeigt eine gRPC-Server-Implementierung mit Go für den Anwendungsfall ProductInfo
service. Hier öffnen wir einen TCP-Port, starten den gRPC-Server und registrieren den Dienst ProductInfo
bei diesem Server.
Beispiel 1-3. Ausführen eines gRPC-Servers für den ProductInfo-Dienst mit Go
func
main
()
{
lis
,
_
:=
net
.
Listen
(
"tcp"
,
port
)
s
:=
grpc
.
NewServer
()
pb
.
RegisterProductInfoServer
(
s
,
&
server
{})
if
err
:=
s
.
Serve
(
lis
);
err
!=
nil
{
log
.
Fatalf
(
"failed to serve: %v"
,
err
)
}
}
Das ist alles, was du auf der Serverseite tun musst. Kommen wir nun zur gRPC-Implementierung auf der Client-Seite.
gRPC-Client
Ähnlich wie auf der Serverseite können wir den Client-Stub mithilfe der Dienstdefinition erstellen. Der Client-Stub bietet dieselben Methoden wie der Server, die dein Client-Code aufrufen kann; der Client-Stub übersetzt sie in Netzwerkaufrufe für Remote-Funktionen, die an die Serverseite gehen. Da gRPC-Dienstdefinitionen sprachunabhängig sind, kannst du Clients und Server für jede unterstützte Sprache (über die Implementierungen von Drittanbietern) deiner Wahl erstellen. Für den Anwendungsfall des ProductInfo
Dienstes können wir also den Client-Stub für Java generieren, während unsere Serverseite mit Go implementiert wird. Im Codeschnipsel in Beispiel 1-4 findest du den Code für Java. Unabhängig von der verwendeten Programmiersprache bestehen die einfachen Schritte einer clientseitigen Implementierung darin, eine Verbindung mit dem entfernten Server herzustellen, den Client-Stub mit dieser Verbindung zu verknüpfen und die entfernte Methode mit dem Client-Stub aufzurufen.
Beispiel 1-4. gRPC-Client zum Aufrufen einer Remote-Methode eines Dienstes
// Create a channel using remote server address
ManagedChannel
channel
=
ManagedChannelBuilder
.
forAddress
(
"localhost"
,
8080
)
.
usePlaintext
(
true
)
.
build
();
// Initialize blocking stub using the channel
ProductInfoGrpc
.
ProductInfoBlockingStub
stub
=
ProductInfoGrpc
.
newBlockingStub
(
channel
);
// Call remote method using the blocking stub
StringValue
productID
=
stub
.
addProduct
(
Product
.
newBuilder
()
.
setName
(
"Apple iPhone 11"
)
.
setDescription
(
"Meet Apple iPhone 11."
+
"All-new dual-camera system with "
+
"Ultra Wide and Night mode."
)
.
build
());
Da du jetzt ein gutes Gefühl für die Schlüsselkonzepte von gRPC hast, wollen wir versuchen, den gRPC-Client-Server-Nachrichtenfluss im Detail zu verstehen.
Client-Server Nachrichtenfluss
Wenn ein gRPC-Client einen gRPC-Dienst aufruft, verwendet die gRPC-Bibliothek auf der Client-Seite den Protokollpuffer und marshaled das Protokollpufferformat des Remote-Prozeduraufrufs, das dann über HTTP/2 gesendet wird. Auf der Serverseite wird die Anfrage nicht gemarshallt und der jeweilige Prozeduraufruf wird mithilfe von Protokollpuffern ausgeführt. Die Antwort folgt einem ähnlichen Ausführungsfluss vom Server zum Client. gRPC verwendet als Transportprotokoll HTTP/2, ein leistungsfähiges binäres Nachrichtenprotokoll mit Unterstützung für bidirektionale Nachrichtenübermittlung. Die Details des Nachrichtenflusses zwischen gRPC-Clients und -Servern sowie die Protokollpuffer und die Verwendung von HTTP/2 durch gRPC werden wir in Kapitel 4 näher erläutern.
Hinweis
Beim Marshaling werden Parameter und eine Remote-Funktion in ein Nachrichtenpaket gepackt, das über das Netzwerk gesendet wird, während beim Unmarshaling das Nachrichtenpaket in den jeweiligen Methodenaufruf entpackt wird.
Bevor wir uns näher mit dem gRPC-Protokoll befassen, ist es wichtig, ein umfassendes Verständnis der verschiedenen Technologien für die Kommunikation zwischen Prozessen zu haben und wie sie sich im Laufe der Zeit entwickelt haben.
Entwicklung der Kommunikation zwischen Prozessen
Die Techniken zur Kommunikation zwischen Prozessen haben sich im Laufe der Zeit drastisch weiterentwickelt. Es gibt verschiedene Techniken, die den modernen Anforderungen gerecht werden und eine bessere und effizientere Entwicklungserfahrung ermöglichen. Deshalb ist es wichtig zu wissen, wie sich die Interprozesskommunikationstechniken entwickelt haben und wie sie ihren Weg zu gRPC gefunden haben. Schauen wir uns einige der am häufigsten verwendeten Interprozesskommunikationstechniken an und versuchen wir, sie mit gRPC zu vergleichen und gegenüberzustellen.
Konventionelle RPC
RPC war eine beliebte Technik zur Kommunikation zwischen Prozessen, um Client-Service-Anwendungen zu entwickeln. Mit RPC kann ein Client eine Funktion einer Methode aus der Ferne genauso aufrufen wie eine lokale Methode. In der Anfangszeit gab es populäre RPC-Implementierungen wie die Common Object Request Broker Architecture (CORBA) und Java Remote Method Invocation (RMI), die für den Aufbau und die Verbindung von Diensten oder Anwendungen verwendet wurden. Die meisten dieser herkömmlichen RPC-Implementierungen sind jedoch äußerst komplex, da sie auf Kommunikationsprotokollen wie TCP aufbauen, was die Interoperabilität erschwert, und auf aufgeblähten Spezifikationen basieren.
SOAP
Aufgrund der Einschränkungen herkömmlicher RPC-Implementierungen wie CORBA wurde das Simple Object Access Protocol (SOAP) entwickelt und von Großunternehmen wie Microsoft, IBM usw. stark gefördert. SOAP ist die Standard-Kommunikationstechnik in einer serviceorientierten Architektur (SOA), um XML-basierte strukturierte Daten zwischen Diensten (im Kontext von SOA meist als Webservices bezeichnet) auszutauschen, und kommuniziert über ein beliebiges zugrunde liegendes Kommunikationsprotokoll wie HTTP (am häufigsten verwendet).
Mit SOAP kannst du die Serviceschnittstelle, die Operationen dieses Dienstes und ein zugehöriges XML-Nachrichtenformat definieren, das zum Aufrufen dieser Operationen verwendet wird. SOAP war eine recht beliebte Technologie, aber die Komplexität des Nachrichtenformats sowie die Komplexität der Spezifikationen, die um SOAP herum aufgebaut sind, behindern die Agilität bei der Entwicklung verteilter Anwendungen. Im Kontext der modernen verteilten Anwendungsentwicklung werden SOAP-Webdienste daher als veraltete Technologie betrachtet. Statt SOAP zu verwenden, werden die meisten bestehenden verteilten Anwendungen heute im Stil der REST-Architektur entwickelt.
REST
Representational State Transfer (REST) ist ein Architekturstil, der auf die Dissertation von Roy Fielding zurückgeht. Fielding ist einer der Hauptautoren der HTTP-Spezifikation und der Begründer des REST-Architekturstils. REST ist die Grundlage der ressourcenorientierten Architektur (ROA), bei der du verteilte Anwendungen als eine Sammlung von Ressourcen modellierst und die Clients, die auf diese Ressourcen zugreifen, den Zustand dieser Ressourcen ändern können (erstellen, lesen, aktualisieren oder löschen).
Die De-facto-Implementierung von REST ist HTTP, und in HTTP kannst du eine RESTful-Webanwendung als eine Sammlung von Ressourcen modellieren, die über eine eindeutige Kennung (URL) zugänglich sind. Die zustandsändernden Operationen werden auf diese Ressourcen in Form von HTTP-Verben (GET, POST, PUT, DELETE, PATCH usw.) angewendet. Der Zustand der Ressourcen wird in Textformaten wie JSON, XML, HTML, YAML usw. dargestellt.
Die Erstellung von Anwendungen im REST-Architekturstil mit HTTP und JSON ist zur De-facto-Methode für die Erstellung von Microservices geworden. Mit der zunehmenden Anzahl von Microservices und deren Interaktionen im Netzwerk konnten RESTful-Dienste jedoch nicht die erwarteten modernen Anforderungen erfüllen. Es gibt einige wesentliche Einschränkungen von RESTful-Diensten, die verhindern, dass sie als Nachrichtenprotokoll für moderne Microservices-basierte Anwendungen verwendet werden können.
Ineffiziente textbasierte Nachrichtenprotokolle
RESTful-Dienste basieren auf textbasierten Transportprotokollen wie HTTP 1.x und nutzen von Menschen lesbare Textformate wie JSON. Wenn es um die Kommunikation zwischen Diensten geht, ist es ziemlich ineffizient, ein Textformat wie JSON zu verwenden, weil beide Parteien dieser Kommunikation keine solchen für Menschen lesbaren Textformate verwenden müssen.
Die Client-Anwendung (Quelle) produziert binäre Inhalte, die an den Server gesendet werden sollen. Dann wandelt sie die binäre Struktur in Text um (weil man mit HTTP 1.x Textnachrichten senden muss) und sendet sie über das Netzwerk in Textform (über HTTP) an eine Maschine, die sie parst und auf der Seite des Dienstes (Ziel) wieder in eine binäre Struktur umwandelt. Stattdessen hätten wir auch einfach ein binäres Format senden können, das sich auf die Geschäftslogik eines Dienstes und eines Verbrauchers abbilden lässt. Ein beliebtes Argument für die Verwendung von JSON ist, dass es einfacher zu handhaben ist, weil es "menschenlesbar" ist. Das ist eher ein Problem der Werkzeuge als ein Problem der Binärprotokolle.
Fehlende stark typisierte Schnittstellen zwischen Anwendungen
Mit der zunehmenden Anzahl von Diensten, die über das Netzwerk interagieren und mit unterschiedlichen polyglotten Technologien erstellt werden, war das Fehlen von gut definierten und stark typisierten Dienstdefinitionen ein großer Rückschlag. Die meisten der bestehenden Technologien zur Definition von Diensten, die wir in RESTful-Diensten verwenden, wie OpenAPI/Swagger, sind nachträglich entwickelt worden und nicht eng mit dem zugrunde liegenden Architekturstil oder den Nachrichtenprotokollen verbunden .
Dies führt zu vielen Inkompatibilitäten, Laufzeitfehlern und Interoperabilitätsproblemen beim Aufbau solcher dezentralen Anwendungen. Wenn du zum Beispiel RESTful-Dienste entwickelst, brauchst du keine Dienst- und Typdefinition für die Informationen, die zwischen den Anwendungen ausgetauscht werden. Stattdessen entwickelst du deine RESTful-Anwendungen entweder anhand des Textformats auf der Leitung oder mit API-Definitionstechnologien von Drittanbietern wie OpenAPI. Daher ist eine moderne, stark typisierte Service-Definitionstechnologie und ein Framework, das den Kern des serverseitigen und clientseitigen Codes für polyglotte Technologien generiert, eine wichtige Voraussetzung.
Der REST-Architekturstil ist schwer durchsetzbar
Als Architekturstil hat REST eine Menge "guter Praktiken", die du befolgen musst, um einen echten RESTful Service zu erstellen. Sie werden jedoch nicht als Teil der Implementierungsprotokolle (wie HTTP) durchgesetzt, was es schwierig macht, sie in der Implementierungsphase durchzusetzen. In der Praxis halten sich daher die meisten Dienste, die behaupten, RESTful zu sein, nicht an die Grundlagen des REST-Stils. Die meisten so genannten RESTful-Dienste sind also lediglich HTTP-Dienste, die über das Netzwerk bereitgestellt werden. Deshalb müssen die Entwicklungsteams viel Zeit darauf verwenden, die Konsistenz und Reinheit eines RESTful-Dienstes zu gewährleisten.
Mit all diesen Einschränkungen der prozessübergreifenden Kommunikationstechniken bei der Entwicklung moderner nativer Cloud-Anwendungen begann die Suche nach einem besseren Nachrichtenprotokoll.
Gründung der gRPC
Google hatte ein Allzweck-RPC-Framework namens Stubby verwendet, um Tausende von Microservices zu verbinden, die in verschiedenen Rechenzentren laufen und mit unterschiedlichen Technologien aufgebaut sind. Die RPC-Kernschicht wurde entwickelt, um eine Internet-Skala von zehn Milliarden Anfragen pro Sekunde zu bewältigen. Stubby hat viele großartige Funktionen, aber es ist nicht standardisiert, um als allgemeines Framework verwendet zu werden, da es zu eng an die interne Infrastruktur von Google gekoppelt ist.
Im Jahr 2015 veröffentlichte Google gRPC als Open-Source-RPC-Framework; es ist eine standardisierte, universelle und plattformübergreifende RPC-Infrastruktur. gRPC sollte die gleiche Skalierbarkeit, Leistung und Funktionalität wie Stubby bieten, allerdings für die breite Community.
Seitdem ist die Popularität von gRPC in den letzten Jahren dramatisch gestiegen und wurde von großen Unternehmen wie Netflix, Square, Lyft, Docker, Cisco und CoreOS in großem Umfang übernommen. Später trat gRPC der Cloud Native Computing Foundation (CNCF) bei, einer der populärsten Open-Source-Software-Stiftungen, die sich dafür einsetzt, Cloud Native Computing universell und nachhaltig zu machen. gRPC hat durch die Projekte im CNCF-Ökosystem viel an Zugkraft gewonnen.
Sehen wir uns nun einige der wichtigsten Gründe an, die für gRPC im Vergleich zu den herkömmlichen Protokollen für die Kommunikation zwischen Prozessen sprechen.
Warum gRPC?
gRPC ist eine Internet-basierte Interprozess-Kommunikationstechnologie, die die meisten Mängel herkömmlicher Interprozess-Kommunikationstechnologien überwinden kann. Aufgrund der Vorteile von gRPC stellen die meisten modernen Anwendungen und Server ihr Interprozess-Kommunikationsprotokoll zunehmend auf gRPC um. Warum also sollte jemand gRPC als Kommunikationsprotokoll wählen, wenn es so viele andere Möglichkeiten gibt? Schauen wir uns einige der wichtigsten Vorteile von gRPC genauer an.
Vorteile von gRPC
Die Vorteile, die gRPC mit sich bringt, sind der Schlüssel für die zunehmende Verbreitung von gRPC. Zu diesen Vorteilen gehören die folgenden:
- Es ist effizient für die Kommunikation zwischen Prozessen
-
Anstatt ein Textformat wie JSON oder XML zu verwenden, nutzt gRPC ein Protokollpuffer-basiertes Binärprotokoll, um mit gRPC-Diensten und -Clients zu kommunizieren. Außerdem implementiert gRPC Protokollpuffer auf der Grundlage von HTTP/2, was die Kommunikation zwischen Prozessen noch schneller macht. Das macht gRPC zu einer der effizientesten Technologien für die prozessübergreifende Kommunikation, die es gibt.
- Es hat einfache, gut definierte Service-Schnittstellen und Schemata
-
gRPC fördert einen Contract-First-Ansatz für die Entwicklung von Anwendungen. Du definierst zuerst die Serviceschnittstellen und kümmerst dich erst danach um die Implementierungsdetails. Im Gegensatz zu OpenAPI/Swagger für die Definition von RESTful-Diensten und WSDL für SOAP-Webdienste bietet gRPC also eine einfache, aber konsistente, zuverlässige und skalierbare Anwendungsentwicklung.
- Es ist stark typisiert
-
Da wir zur Definition von gRPC-Diensten Protokollpuffer verwenden, definieren gRPC-Dienstverträge eindeutig die Typen, die du für die Kommunikation zwischen den Anwendungen verwenden wirst. Das macht die Entwicklung verteilter Anwendungen viel stabiler, da die statische Typisierung dazu beiträgt, die meisten Laufzeit- und Interoperabilitätsfehler zu vermeiden, die bei der Entwicklung von Cloud-nativen Anwendungen auftreten können, die sich über mehrere Teams und Technologien erstrecken.
- Es ist polyglott
-
gRPC ist so konzipiert, dass es mit mehreren Programmiersprachen funktioniert. Eine gRPC-Dienstdefinition mit Protokollpuffern ist sprachunabhängig. Du kannst also die Sprache deiner Wahl wählen, aber mit jedem bestehenden gRPC-Dienst oder -Client zusammenarbeiten.
- Es hat Duplex-Streaming
-
gRPC bietet native Unterstützung für client- oder serverseitiges Streaming, die in die Dienstdefinition selbst integriert ist. Das macht es viel einfacher, Streaming-Dienste oder Streaming-Clients zu entwickeln. Die Möglichkeit, konventionelle Nachrichten im Stil von Anfrage und Antwort sowie client- und serverseitiges Streaming zu entwickeln, ist ein entscheidender Vorteil gegenüber dem konventionellen RESTful-Messaging-Stil.
- Es hat eingebaute Funktionen für Waren
-
gRPC bietet integrierte Unterstützung für Commodity-Funktionen wie Authentifizierung, Verschlüsselung, Ausfallsicherheit (Deadlines und Timeouts), Metadatenaustausch, Komprimierung, Lastausgleich, Service Discovery usw. (wir werden diese in Kapitel 5 näher betrachten).
- Es ist in Cloud-native Ökosysteme integriert
-
gRPC ist Teil der CNCF und die meisten modernen Frameworks und Technologien bieten von Haus aus native Unterstützung für gRPC. So unterstützen viele CNCF-Projekte wie Envoy gRPC als Kommunikationsprotokoll; für übergreifende Funktionen wie Metriken und Überwachung wird gRPC von den meisten dieser Tools unterstützt (z. B. mit Prometheus zur Überwachung von gRPC-Anwendungen).
- Es ist ausgereift und hat sich weit verbreitet
-
gRPC ist durch die intensive Erprobung bei Google gereift, und viele andere große Tech-Unternehmen wie Square, Lyft, Netflix, Docker, Cisco und CoreOS haben es übernommen.
Wie jede Technologie hat auch gRPC eine Reihe von Nachteilen. Es ist sehr nützlich, diese Nachteile bei der Anwendungsentwicklung zu kennen. Werfen wir also einen Blick auf einige der Einschränkungen von gRPC.
Nachteile von gRPC
Hier sind einige der Nachteile von gRPC, die du beachten musst, wenn du es für Bauanwendungen auswählst. Dazu gehören die folgenden:
- Sie eignet sich möglicherweise nicht für nach außen gerichtete Dienste
-
Wenn du die Anwendung oder Dienste einem externen Kunden über das Internet zur Verfügung stellen willst, ist gRPC möglicherweise nicht das am besten geeignete Protokoll, da die meisten externen Kunden mit gRPC und REST/HTTP recht vertraut sind. Die vertragsgesteuerte, stark typisierte Natur von gRPC-Diensten kann die Flexibilität der Dienste, die du externen Parteien zur Verfügung stellst, einschränken, und die Verbraucher erhalten weit weniger Kontrolle (im Gegensatz zu Protokollen wie GraphQL, das im nächsten Abschnitt erläutert wird). Das gRPC-Gateway wurde als Workaround entwickelt, um dieses Problem zu umgehen. Wir werden es in Kapitel 8 ausführlich besprechen.
- Drastische Änderungen der Dienstdefinition sind ein komplizierter Entwicklungsprozess
-
Schema-Änderungen sind in modernen Anwendungsfällen der Inter-Service-Kommunikation recht häufig. Bei drastischen Änderungen der gRPC-Dienstdefinition müssen wir in der Regel den Code sowohl für den Client als auch für den Server neu erstellen. Dies muss in den bestehenden kontinuierlichen Integrationsprozess integriert werden und kann den gesamten Entwicklungslebenszyklus verkomplizieren. Die meisten Änderungen an der gRPC-Dienstdefinition können jedoch berücksichtigt werden, ohne dass der Dienstvertrag gebrochen wird, und gRPC arbeitet problemlos mit Clients und Servern zusammen, die unterschiedliche Versionen eines Protos verwenden, solange keine Änderungen vorgenommen werden. In den meisten Fällen ist es also nicht nötig, den Code zu erneuern.
- Das Ökosystem ist relativ klein
-
Das gRPC-Ökosystem ist im Vergleich zum herkömmlichen REST/HTTP-Protokoll noch relativ klein. Die Unterstützung für gRPC in Browsern und mobilen Anwendungen steckt noch in den Kinderschuhen.
Bei der Entwicklung von Anwendungen musst du diese Einschränkungen berücksichtigen. Es ist also klar, dass gRPC keine Technik ist, die du für alle deine Anforderungen an die prozessübergreifende Kommunikation verwenden solltest. Vielmehr musst du den Anwendungsfall und die Anforderungen deines Unternehmens bewerten und das passende Messaging-Protokoll auswählen. Einige dieser Richtlinien werden wir in Kapitel 8 erläutern.
Wie in den vorangegangenen Abschnitten beschrieben, gibt es viele bestehende und neue Techniken für die Kommunikation zwischen Prozessen. Es ist wichtig zu wissen, wie wir gRPC mit anderen ähnlichen Technologien vergleichen können, die in der modernen Anwendungsentwicklungslandschaft an Popularität gewonnen haben, denn das wird dir helfen, das am besten geeignete Protokoll für deine Dienste zu finden.
gRPC im Vergleich zu anderen Protokollen: GraphQL und Thrift
Wir haben einige der wichtigsten Einschränkungen von REST ausführlich erörtert, die die Grundlage für die Einführung von gRPC bildeten. Außerdem gibt es eine ganze Reihe von Technologien für die Kommunikation zwischen Prozessen, die die gleichen Anforderungen erfüllen. Werfen wir also einen Blick auf einige der gängigen Technologien und vergleichen sie mit gRPC.
Apache Thrift
Apache Thrift ist ein RPC-Framework (ursprünglich bei Facebook entwickelt und später an Apache gespendet), das gRPC ähnelt. Es verwendet eine eigene Schnittstellendefinitionssprache und bietet Unterstützung für eine breite Palette von Programmiersprachen. Mit Thrift kannst du Datentypen und Dienstschnittstellen in einer Definitionsdatei definieren. Aus der Dienstdefinition generiert der Thrift-Compiler den Code für die Client- und Serverseite. Die Thrift-Transportschicht bietet Abstraktionen für die Netzwerk-E/A und entkoppelt Thrift vom Rest des Systems, d. h. es kann auf jeder Transportimplementierung wie TCP, HTTP usw. laufen.
Wenn du Thrift mit gRPC vergleichst, wirst du feststellen, dass beide ziemlich genau die gleichen Design- und Nutzungsziele verfolgen. Es gibt jedoch einige wichtige Unterschiede zwischen den beiden:
- Transport
-
gRPC ist eigenwilliger als Thrift und bietet erstklassige Unterstützung für HTTP/2. Seine Implementierungen für HTTP/2 nutzen die Fähigkeiten des Protokolls, um Effizienz und Unterstützung für Nachrichtenmuster wie Streaming zu erreichen.
- Streaming
-
gRPC-Dienstdefinitionen unterstützen von Haus aus bidirektionales Streaming (Client und Server) als Teil der Dienstdefinition selbst.
- Adoption und Gemeinschaft
-
Wenn es um die Übernahme geht, scheint gRPC eine ziemlich gute Dynamik zu haben und hat es geschafft, ein gutes Ökosystem rund um die CNCF-Projekte aufzubauen. Außerdem sind Community-Ressourcen wie eine gute Dokumentation, externe Präsentationen und Anwendungsbeispiele für gRPC weit verbreitet, was den Einführungsprozess im Vergleich zu Thrift vereinfacht.
- Leistung
-
Es gibt zwar keine offiziellen Ergebnisse, die gRPC mit Thrift vergleichen, aber es gibt ein paar Online-Ressourcen mit Leistungsvergleichen zwischen den beiden, die bessere Zahlen für Thrift zeigen. Allerdings wird auch gRPC in fast allen Versionen intensiv auf seine Leistung geprüft. Es ist also unwahrscheinlich, dass die Leistung ein ausschlaggebender Faktor für die Entscheidung zwischen Thrift und gRPC ist. Außerdem gibt es andere RPC-Frameworks, die ähnliche Funktionen bieten, aber gRPC ist derzeit die am meisten standardisierte, interoperable und weit verbreitete RPC-Technologie.
GraphQL
GraphQL ist eine weitere Technologie (die von Facebook erfunden und als offene Technologie standardisiert wurde), die für den Aufbau der prozessübergreifenden Kommunikation immer beliebter wird. Es handelt sich um eine Abfragesprache für APIs und eine Laufzeitumgebung, um diese Abfragen mit deinen vorhandenen Daten zu erfüllen. GraphQL bietet einen grundlegend anderen Ansatz für die herkömmliche Client-Server-Kommunikation, da die Clients selbst bestimmen können, welche Daten sie wie und in welchem Format benötigen. gRPC hingegen hat einen festen Vertrag für die Remote-Methoden, die die Kommunikation zwischen Client und Server ermöglichen.
GraphQL eignet sich besser für externe Dienste oder APIs, die den Kunden direkt zur Verfügung gestellt werden und bei denen die Kunden mehr Kontrolle über die Daten benötigen, die sie vom Server abrufen. Nehmen wir zum Beispiel an, dass in unserem Szenario für den Online-Einzelhandel die Verbraucher des Dienstes ProductInfo
nur bestimmte Informationen über die Produkte benötigen, aber nicht den gesamten Satz von Attributen eines Produkts. Mit GraphQL kannst du einen Dienst so modellieren, dass er es den Verbrauchern ermöglicht, den Dienst mit der Abfragesprache GraphQL abzufragen und die gewünschten Informationen zu erhalten.
In den meisten pragmatischen Anwendungsfällen von GraphQL und gRPC wird GraphQL für nach außen gerichtete Dienste/APIs verwendet, während interne Dienste, die hinter den APIs stehen, mit gRPC implementiert werden.
Werfen wir nun einen Blick auf einige reale Anwender von gRPC und ihre Anwendungsfälle.
gRPC in der realen Welt
Der Erfolg eines Interprozess-Kommunikationsprotokolls hängt in hohem Maße von der branchenweiten Akzeptanz und der Nutzer- und Entwicklergemeinschaft ab, die hinter dem Projekt steht. gRPC hat sich bei der Entwicklung von Microservices und nativen Cloud-Anwendungen durchgesetzt. Werfen wir einen Blick auf einige der wichtigsten Erfolgsgeschichten von gRPC.
Netflix
Netflix, ein abonnementbasierter Videostreaming-Anbieter, ist einer der Pioniere bei der Umsetzung einer Microservices-Architektur in großem Maßstab. Alle seine Video-Streaming-Funktionen werden den Verbrauchern über einen nach außen gerichteten verwalteten Dienst (oder APIs) angeboten und es gibt Hunderte von Backend-Diensten, die hinter den APIs stehen. Daher ist die prozessübergreifende (oder dienstübergreifende) Kommunikation einer der wichtigsten Aspekte des Anwendungsfalls. In der Anfangsphase der Microservices-Implementierung hat Netflix einen eigenen Technologie-Stack für die Kommunikation zwischen den Diensten entwickelt, der RESTful-Dienste über HTTP/1.1 nutzt, die fast 98 % der geschäftlichen Anwendungsfälle des Netflix-Produkts unterstützen.
Netflix hat jedoch einige Einschränkungen des auf RESTful-Diensten basierenden Ansatzes festgestellt, wenn sie im großen Maßstab im Internet arbeiten. Die Verbraucher von RESTful-Microservices wurden oft von Grund auf neu geschrieben, indem die Ressourcen und erforderlichen Nachrichtenformate der RESTful-Services untersucht wurden. Das war sehr zeitaufwändig, behinderte die Produktivität der Entwickler und erhöhte das Risiko für fehleranfälligen Code. Die Implementierung und Nutzung von Diensten war auch deshalb eine Herausforderung, weil es keine Technologien für eine umfassende Definition einer Dienstschnittstelle gab. Daher versuchte Netflix zunächst, die meisten dieser Einschränkungen durch den Aufbau eines internen RPC-Frameworks zu überwinden, entschied sich dann aber nach einer Evaluierung der verfügbaren Technologie-Stacks für gRPC als Technologie für die Kommunikation zwischen den Diensten. Bei der Evaluierung stellte Netflix fest, dass gRPC die besten Voraussetzungen mitbringt, um alle erforderlichen Aufgaben in einem einzigen, einfach zu nutzenden Paket zu vereinen.
Mit der Einführung von gRPC hat Netflix die Produktivität der Entwickler massiv gesteigert. Zum Beispiel werden für jeden Client Hunderte von Zeilen benutzerdefinierten Codes durch nur zwei bis drei Zeilen Konfiguration im Proto ersetzt. Die Erstellung eines Clients, die bis zu zwei bis drei Wochen dauern kann, dauert mit gRPC nur noch ein paar Minuten. Auch die allgemeine Stabilität der Plattform hat sich stark verbessert, da für die meisten Commodity-Funktionen kein handgeschriebener Code mehr benötigt wird und es eine umfassende und sichere Möglichkeit gibt, Service-Schnittstellen zu definieren. Dank der Leistungssteigerung durch gRPC hat sich die Latenzzeit der gesamten Netflix-Plattform verringert. Da gRPC für die meisten Anwendungsfälle der prozessübergreifenden Kommunikation eingesetzt wird, scheint Netflix einige seiner eigenen Projekte (z. B. Ribbon), die für die prozessübergreifende Kommunikation mit REST- und HTTP-Protokollen entwickelt wurden, in den Wartungsmodus zu versetzen (d. h. sie werden nicht aktiv weiterentwickelt) und stattdessen gRPC einzusetzen.
etcd
etcd ist ein verteilter, zuverlässiger Key-Value-Speicher für die wichtigsten Daten eines verteilten Systems. Es ist eines der beliebtesten Open-Source-Projekte der CNCF und wird von vielen anderen Open-Source-Projekten wie Kubernetes übernommen. Ein Schlüsselfaktor für den Erfolg von gRPC ist die einfache, klar definierte und benutzerfreundliche API. etcd nutzt eine benutzerorientierte gRPC-API, um die volle Leistungsfähigkeit von gRPC zu nutzen.
Dropbox
Dropbox ist ein File-Hosting-Dienst, der Cloud-Speicherung, Dateisynchronisierung, eine persönliche Cloud und Client-Software anbietet. Dropbox betreibt Hunderte von polyglotten Microservices, die Millionen von Anfragen pro Sekunde austauschen. Anfangs verwendete Dropbox mehrere RPC-Frameworks, darunter ein selbst entwickeltes RPC-Framework mit einem benutzerdefinierten Protokoll für die manuelle Serialisierung und Deserialisierung, Apache Thrift, und ein altes RPC-Framework, das ein HTTP/1.1-basiertes Protokoll mit protobuf-kodierten Nachrichten war.
Dropbox hat sich für gRPC entschieden (und kann damit auch einige der bestehenden Protokollpuffer-Definitionen seiner Nachrichtenformate wiederverwenden). Es hat Courier entwickelt, ein gRPC-basiertes RPC-Framework. Courier ist kein neues RPC-Protokoll, sondern ein Projekt, das gRPC in die bestehende Infrastruktur von Dropbox integriert. Dropbox hat gRPC erweitert, um seine spezifischen Anforderungen in Bezug auf Authentifizierung, Autorisierung, Service Discovery, Servicestatistiken, Event Logging und Tracing Tools zu erfüllen.
Diese Erfolgsgeschichten von gRPC zeigen uns, dass es sich um ein einfaches, produktivitätssteigerndes und zuverlässiges Interprozess-Nachrichtenprotokoll handelt, das auf der Internet-Skala skaliert und funktioniert. Dies sind einige der bekannten frühen Anwender von gRPC, aber die Anwendungsfälle und die Akzeptanz von gRPC werden immer größer.
Zusammenfassung
Moderne Softwareanwendungen oder -dienste leben selten isoliert, und die Techniken zur Kommunikation zwischen den Prozessen, die sie verbinden, sind einer der wichtigsten Aspekte moderner verteilter Softwareanwendungen. gRPC ist eine skalierbare, lose gekoppelte und typsichere Lösung, die eine effizientere Kommunikation zwischen den Prozessen ermöglicht als die herkömmliche REST/HTTP-basierte Kommunikation. Sie ermöglicht es, verteilte heterogene Anwendungen so einfach wie einen lokalen Methodenaufruf über Netzwerktransportprotokolle wie HTTP/2 zu verbinden, aufzurufen, zu bedienen und zu debuggen.
gRPC kann auch als Weiterentwicklung herkömmlicher RPCs betrachtet werden und hat es geschafft, deren Beschränkungen zu überwinden. gRPC wird von vielen Unternehmen im Internet für die Kommunikation zwischen Prozessen eingesetzt und wird am häufigsten für die interne Kommunikation zwischen Diensten verwendet.
Das Wissen, das du in diesem Kapitel erwirbst, ist ein guter Einstieg für die restlichen Kapitel, in denen du tief in verschiedene Aspekte der gRPC-Kommunikation eintauchen wirst. Dieses Wissen wird im nächsten Kapitel in die Praxis umgesetzt, in dem wir eine reale gRPC-Anwendung von Grund auf aufbauen.
1 K. Indrasiri und P. Siriwardena, Microservices for the Enterprise (Apress, 2018).
Get gRPC: Auf und davon now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.