1. Einführung
In diesem Codelab verwenden Sie gRPC-Java, um einen Client und einen Server zu erstellen, die die Grundlage einer in Java geschriebenen Routenplanungsanwendung bilden.
Am Ende des Tutorials haben Sie einen Client, der über gRPC eine Verbindung zu einem Remote-Server herstellt, um den Namen oder die Postanschrift des Objekts an bestimmten Koordinaten auf einer Karte abzurufen. Eine vollwertige Anwendung könnte dieses Client-Server-Design verwenden, um POIs entlang einer Route aufzulisten oder zusammenzufassen.
Die API des Servers ist in einer Protocol Buffers-Datei definiert, die zum Generieren von Boilerplate-Code für den Client und den Server verwendet wird, damit sie miteinander kommunizieren können. So sparen Sie Zeit und Aufwand bei der Implementierung dieser Funktion.
Dieser generierte Code kümmert sich nicht nur um die Komplexität der Kommunikation zwischen Server und Client, sondern auch um die Serialisierung und Deserialisierung von Daten.
Lerninhalte
- Wie Sie Protocol Buffers zum Definieren einer Dienst-API verwenden.
- Hier erfahren Sie, wie Sie einen gRPC-basierten Client und Server aus einer Protocol Buffers-Definition mithilfe der automatischen Codegenerierung erstellen.
- Kenntnisse der Client-Server-Kommunikation mit gRPC
Dieses Codelab richtet sich an Java-Entwickler, die neu in gRPC sind oder ihr Wissen zu gRPC auffrischen möchten, sowie an alle anderen, die sich für die Entwicklung verteilter Systeme interessieren. Es sind keine Vorkenntnisse in gRPC erforderlich.
2. Hinweis
Vorbereitung
- JDK-Version 8 oder höher
Code abrufen
Damit Sie nicht ganz von vorn anfangen müssen, enthält dieses Codelab ein Gerüst des Quellcodes der Anwendung, das Sie vervollständigen können. In den folgenden Schritten erfahren Sie, wie Sie die Anwendung fertigstellen, einschließlich der Verwendung der Protocol Buffer-Compiler-Plug-ins zum Generieren des Boilerplate-gRPC-Codes.
Erstellen Sie zuerst das Arbeitsverzeichnis für das Codelab und wechseln Sie in dieses Verzeichnis:
mkdir grpc-java-getting-started && cd grpc-java-getting-started
Laden Sie das Codelab herunter und extrahieren Sie es:
curl -sL https://github.com/grpc-ecosystem/grpc-codelabs/archive/refs/heads/v1.tar.gz \
| tar xvz --strip-components=4 \
grpc-codelabs-1/codelabs/grpc-java-getting-started/start_here
Alternativ können Sie die ZIP-Datei herunterladen, die nur das Codelab-Verzeichnis enthält, und sie manuell entpacken.
Der vollständige Quellcode ist auf GitHub verfügbar, wenn Sie die Eingabe einer Implementierung überspringen möchten.
3. Dienst definieren
Als Erstes müssen Sie den gRPC-Dienst der Anwendung, die RPC-Methode sowie die Anfrage- und Antwortnachrichtentypen mit Protokollpuffern definieren. Ihr Dienst bietet Folgendes:
- Eine RPC-Methode namens
GetFeature
, die vom Server implementiert und vom Client aufgerufen wird. - Die Nachrichtentypen
Point
undFeature
sind Datenstrukturen, die beim Verwenden der MethodeGetFeature
zwischen dem Client und dem Server ausgetauscht werden. Der Client stellt Kartenkoordinaten alsPoint
in seinerGetFeature
-Anfrage an den Server bereit und der Server antwortet mit einem entsprechendenFeature
, das beschreibt, was sich an diesen Koordinaten befindet.
Diese RPC-Methode und ihre Nachrichtentypen werden alle in der Datei src/main/proto/routeguide/route_guide.proto
des bereitgestellten Quellcodes definiert.
Protocol Buffers werden allgemein als Protobufs bezeichnet. Weitere Informationen zur gRPC-Terminologie finden Sie unter Core concepts, architecture, and lifecycle.
Da wir in diesem Beispiel Java-Code generieren, haben wir eine java_package
-Dateioption und einen Namen für die Java-Klasse in unserer .proto
angegeben:
option java_package = "io.grpc.examples.routeguide";
option java_outer_classname = "RouteGuideProto";
Mitteilungstypen
Definieren Sie zuerst den Nachrichtentyp Point
in der Datei routeguide/route_guide.proto
des Quellcodes. Ein Point
stellt ein Paar aus Breiten- und Längengrad auf einer Karte dar. Verwenden Sie für dieses Codelab Ganzzahlen für die Koordinaten:
message Point {
int32 latitude = 1;
int32 longitude = 2;
}
Die Zahlen 1
und 2
sind eindeutige ID-Nummern für die einzelnen Felder in der message
-Struktur.
Als Nächstes definieren Sie den Nachrichtentyp Feature
. Bei einem Feature
wird ein string
-Feld für den Namen oder die Postanschrift von etwas an einem Standort verwendet, der durch ein Point
angegeben wird:
message Feature {
// The name or address of the feature.
string name = 1;
// The point where the feature is located.
Point location = 2;
}
Servicemethode
Die Datei route_guide.proto
hat eine service
-Struktur mit dem Namen RouteGuide
, die eine oder mehrere Methoden definiert, die vom Dienst der Anwendung bereitgestellt werden.
Fügen Sie die Methode rpc
GetFeature
in die Definition von RouteGuide
ein. Wie bereits erwähnt, wird mit dieser Methode der Name oder die Adresse eines Orts anhand einer bestimmten Menge von Koordinaten ermittelt. Lassen Sie also von GetFeature
ein Feature
für ein bestimmtes Point
zurückgeben:
service RouteGuide {
// Definition of the service goes here
// Obtains the feature at a given position.
rpc GetFeature(Point) returns (Feature) {}
}
Dies ist eine unäre RPC-Methode: ein einfacher RPC, bei dem der Client eine Anfrage an den Server sendet und auf eine Antwort wartet, genau wie bei einem lokalen Funktionsaufruf.
4. Client- und Servercode generieren
Als Nächstes müssen wir die gRPC-Client- und ‑Serverschnittstellen aus unserer .proto
-Dienstdefinition generieren. Dazu verwenden wir den Protokollpuffer-Compiler protoc
mit einem speziellen gRPC-Java-Plug-in. Sie müssen den proto3-Compiler (der sowohl die proto2- als auch die proto3-Syntax unterstützt) verwenden, um gRPC-Dienste zu generieren.
Wenn Sie Gradle oder Maven verwenden, kann das protoc
-Build-Plug-in den erforderlichen Code im Rahmen des Builds generieren. In der README-Datei für grpc-java finden Sie Informationen dazu, wie Sie Code aus Ihren eigenen .proto
-Dateien generieren.
Wir haben im Quellcode des Codelabs eine Gradle-Umgebung und -Konfiguration für die Erstellung dieses Projekts bereitgestellt.
Führen Sie im Verzeichnis grpc-java-getting-started
den folgenden Befehl aus:
$ chmod +x gradlew $ ./gradlew generateProto
Die folgenden Klassen werden aus unserer Dienstdefinition generiert:
Feature.java
,Point.java
und andere, die den gesamten Protocol Buffer-Code zum Erstellen, Serialisieren und Abrufen unserer Anfrage- und Antwortnachrichtentypen enthalten.RouteGuideGrpc.java
, das (neben anderem nützlichen Code) eine Basisklasse für die Implementierung vonRouteGuide
-Servern,RouteGuideGrpc.RouteGuideImplBase
, mit allen imRouteGuide
-Dienst definierten Methoden und Stub-Klassen für die Verwendung durch Clients enthält.
5. Server implementieren
Sehen wir uns zuerst an, wie wir einen RouteGuide
-Server erstellen. Damit unser RouteGuide
-Dienst seine Aufgabe erfüllen kann, sind zwei Dinge erforderlich:
- Implementierung der Dienstschnittstelle, die aus unserer Dienstdefinition generiert wird und die eigentliche „Arbeit“ unseres Dienstes erledigt.
- Ausführen eines gRPC-Servers, der auf Anfragen von Clients wartet und sie an die richtige Dienstimplementierung weiterleitet.
RouteGuide implementieren
Wie Sie sehen, hat unser Server eine RouteGuideService
-Klasse, die die generierte abstrakte RouteGuideGrpc.RouteGuideImplBase
-Klasse erweitert:
private static class RouteGuideService extends RouteGuideGrpc.RouteGuideImplBase {
...
}
Wir haben die folgenden zwei Dateien zum Initialisieren Ihres Servers mit Funktionen bereitgestellt:
./src/main/java/io/grpc/examples/routeguide/RouteGuideUtil.java
./src/main/resources/io/grpc/examples/routeguide/route_guide_db.json
Sehen wir uns eine einfache RPC-Implementierung genauer an.
Unäre RPC
RouteGuideService
implementiert alle unsere Dienstmethoden. In diesem Fall ist es nur GetFeature()
. Es wird eine Point
-Nachricht vom Client empfangen und in einer Feature
-Nachricht werden die entsprechenden Standortinformationen aus einer Liste bekannter Orte zurückgegeben.
@Override
public void getFeature(Point request, StreamObserver<Feature> responseObserver) {
responseObserver.onNext(checkFeature(request));
responseObserver.onCompleted();
}
Die Methode getFeature()
hat zwei Parameter:
Point
: die Anfrage.StreamObserver<Feature>
: Ein Antwort-Observer, eine spezielle Schnittstelle, die der Server mit seiner Antwort aufrufen kann.
So geben Sie unsere Antwort an den Kunden zurück und beenden den Anruf:
- Wir erstellen und füllen ein
Feature
-Antwortobjekt, das gemäß unserer Dienstdefinition an den Client zurückgegeben wird. In diesem Beispiel erfolgt dies in einer separaten privaten MethodecheckFeature()
. - Wir verwenden die Methode
onNext()
des Antwort-Observers, um dieFeature
zurückzugeben. - Mit der Methode
onCompleted()
des Antwort-Observers geben wir an, dass wir den RPC abgeschlossen haben.
Server starten
Nachdem wir alle Dienstmethoden implementiert haben, müssen wir einen gRPC-Server starten, damit Clients unseren Dienst verwenden können. In unserem Boilerplate-Code ist die Erstellung des ServerBuilder-Objekts enthalten:
ServerBuilder.forPort(port), port, RouteGuideUtil.parseFeatures(featureFile)
Wir erstellen den Dienst im Konstruktor:
- Geben Sie den Port an, den wir verwenden möchten, um auf Clientanfragen zu warten. Verwenden Sie dazu die
forPort()
-Methode des Builders (es wird die Platzhalteradresse verwendet). - Erstellen Sie eine Instanz unserer Dienstimplementierungsklasse
RouteGuideService
und übergeben Sie sie an dieaddService()
-Methode des Builders. - Rufen Sie
build()
für den Builder auf, um einen RPC-Server für unseren Dienst zu erstellen.
Im folgenden Snippet sehen Sie, wie ein ServerBuilder
-Objekt erstellt wird.
/** Create a RouteGuide server listening on {@code port} using {@code featureFile} database. */
public RouteGuideServer(int port, URL featureFile) throws IOException {
this(Grpc.newServerBuilderForPort(port, InsecureServerCredentials.create()),
port, RouteGuideUtil.parseFeatures(featureFile));
}
Das folgende Snippet zeigt, wie wir ein Serverobjekt für unseren RouteGuide
-Dienst erstellen.
/** Create a RouteGuide server using serverBuilder as a base and features as data. */
public RouteGuideServer(ServerBuilder<?> serverBuilder, int port, Collection<Feature> features) {
this.port = port;
server = serverBuilder.addService(new RouteGuideService(features))
.build();
}
Implementieren Sie eine Startmethode, die start
auf dem oben erstellten Server aufruft.
public void start() throws IOException {
server.start();
logger.info("Server started, listening on " + port);
}
Implementieren Sie eine Methode, mit der Sie warten, bis der Server abgeschlossen ist, damit er nicht sofort beendet wird.
/** Await termination on the main thread since the grpc library uses daemon threads. */
private void blockUntilShutdown() throws InterruptedException {
if (server != null) {
server.awaitTermination();
}
}
Wie Sie sehen, erstellen und starten wir unseren Server mit einem ServerBuilder
.
In der Hauptmethode führen wir folgende Schritte aus:
- Erstellen Sie eine
RouteGuideServer
-Instanz. - Rufen Sie
start()
auf, um einen RPC-Server für unseren Dienst zu aktivieren. - Warten Sie, bis der Dienst durch Aufrufen von
blockUntilShutdown()
beendet wird.
public static void main(String[] args) throws Exception {
RouteGuideServer server = new RouteGuideServer(8980);
server.start();
server.blockUntilShutdown();
}
6. Client erstellen
In diesem Abschnitt sehen wir uns an, wie wir einen Client für unseren RouteGuide
-Dienst erstellen.
Stub instanziieren
Um Dienstmethoden aufzurufen, müssen wir zuerst einen Stub erstellen. Es gibt zwei Arten von Stubs, aber für dieses Codelab benötigen wir nur den blockierenden. Es gibt zwei Arten:
- Ein blockierender/synchroner Stub, der einen RPC-Aufruf ausführt und auf die Antwort des Servers wartet. Er gibt entweder eine Antwort zurück oder löst eine Ausnahme aus.
- ein nicht blockierender/asynchroner Stub, der nicht blockierende Aufrufe an den Server ausführt, wobei die Antwort asynchron zurückgegeben wird. Bestimmte Arten von Streaminganrufen können nur über den asynchronen Stub erfolgen.
Zuerst müssen wir einen gRPC-Channel erstellen und dann den Channel verwenden, um unseren Stub zu erstellen.
Wir hätten auch direkt eine ManagedChannelBuilder
verwenden können, um den Channel zu erstellen.
ManagedChannelBuilder.forAddress(host, port).usePlaintext().build
Wir verwenden jedoch eine Hilfsmethode, die einen String mit hostname:port
akzeptiert.
Grpc.newChannelBuilder(target, InsecureChannelCredentials.create()).build();
Jetzt können wir den Kanal verwenden, um den Blockierungs-Stub zu erstellen. In diesem Codelab haben wir nur blockierende RPCs. Daher verwenden wir die newBlockingStub
-Methode, die in der RouteGuideGrpc
-Klasse bereitgestellt wird, die wir aus unserem .proto
generiert haben.
blockingStub = RouteGuideGrpc.newBlockingStub(channel);
Dienstmethoden aufrufen
Sehen wir uns nun an, wie wir unsere Dienstmethoden aufrufen.
Einfache RPC
Der Aufruf des einfachen RPC GetFeature
ist fast so einfach wie der Aufruf einer lokalen Methode.
Wir erstellen und füllen ein Protokollzwischenspeicherobjekt für die Anfrage (in unserem Fall Point
), übergeben es an die Methode getFeature()
in unserem blockierenden Stub und erhalten eine Feature
zurück.
Wenn ein Fehler auftritt, wird er als Status
codiert, die wir aus dem StatusRuntimeException
abrufen können.
Point request = Point.newBuilder().setLatitude(lat).setLongitude(lon).build();
Feature feature;
try {
feature = blockingStub.getFeature(request);
} catch (StatusRuntimeException e) {
logger.log(Level.WARNING, "RPC failed: {0}", e.getStatus());
return;
}
Im Boilerplate-Code wird eine Nachricht mit dem Inhalt protokolliert, je nachdem, ob an der angegebenen Stelle ein Feature vorhanden war oder nicht.
7. Probier es gleich aus!
- Führen Sie im Verzeichnis
start_here
den folgenden Befehl aus:
$ ./gradlew installDist
Dadurch wird Ihr Code kompiliert, in einem JAR verpackt und die Skripts zum Ausführen des Beispiels werden erstellt. Sie werden im Verzeichnis build/install/start_here/bin/
erstellt. Die Skripts sind: route-guide-server
und route-guide-client
.
Der Server muss ausgeführt werden, bevor der Client gestartet wird.
- Führen Sie den Server aus:
$ ./build/install/start_here/bin/route-guide-server
- Führen Sie den Client aus:
$ ./build/install/start_here/bin/route-guide-client
Die Ausgabe sieht so aus (Zeitstempel wurden der Übersichtlichkeit halber weggelassen):
INFO: *** GetFeature: lat=409,146,138 lon=-746,188,906 INFO: Found feature called "Berkshire Valley Management Area Trail, Jefferson, NJ, USA" at 40.915, -74.619 INFO: *** GetFeature: lat=0 lon=0 INFO: Found no feature at 0, 0
8. Nächste Schritte
- Informationen zur Funktionsweise von gRPC finden Sie unter Einführung in gRPC und Zentrale Konzepte.
- Grundlagen-Anleitung durcharbeiten
- API-Referenz