Einführung in gRPC

1. Einleitung

gRPC ist ein leistungsstarkes Open-Source-RPC-Framework, das ursprünglich von Google entwickelt wurde. Es hilft bei der Eliminierung von Boilerplate-Code und bei der Verbindung von Polyglot-Diensten in und zwischen Rechenzentren.

2. Übersicht

Das Framework basiert auf einem Client-Server-Modell von Remoteprozeduraufrufen. Eine Clientanwendung kann Methoden in einer Serveranwendung direkt aufrufen, als wäre es ein lokales Objekt.

In diesem Artikel werden die folgenden Schritte verwendet, um eine typische Client-Server-Anwendung mit gRPC zu erstellen:

  1. Definieren Sie einen Dienst in einer .proto- Datei
  2. Generieren Sie Server- und Clientcode mit dem Protokollpuffer-Compiler
  3. Erstellen Sie die Serveranwendung, implementieren Sie die generierten Dienstschnittstellen und erzeugen Sie den gRPC-Server
  4. Erstellen Sie die Clientanwendung und führen Sie RPC-Aufrufe mit generierten Stubs durch

Definieren wir einen einfachen HelloService, der im Austausch für den Vor- und Nachnamen Grüße zurückgibt.

3. Maven-Abhängigkeiten

Fügen wir die Abhängigkeiten grpc-netty, grpc-protobuf und grpc-stub hinzu:

 io.grpc grpc-netty 1.16.1   io.grpc grpc-protobuf 1.16.1   io.grpc grpc-stub 1.16.1  

4. Service definieren

Wir beginnen mit der Definition eines Dienstes und geben Methoden an, die zusammen mit ihren Parametern und Rückgabetypen remote aufgerufen werden können .

Dies erfolgt in der .proto- Datei mithilfe der Protokollpuffer. Sie werden auch zur Beschreibung der Struktur der Nutzlastnachrichten verwendet.

4.1. Grundkonfigurationen

Lassen Sie uns eine HelloService.proto- Datei für unser Beispiel HelloService erstellen . Wir beginnen mit dem Hinzufügen einiger grundlegender Konfigurationsdetails:

syntax = "proto3"; option java_multiple_files = true; package org.baeldung.grpc;

Die erste Zeile teilt dem Compiler mit, welche Syntax in dieser Datei verwendet wird. Standardmäßig generiert der Compiler den gesamten Java-Code in einer einzigen Java-Datei. Die zweite Zeile überschreibt diese Einstellung und alles wird in einzelnen Dateien generiert.

Schließlich geben wir das Paket an, das wir für unsere generierten Java-Klassen verwenden möchten.

4.2. Definieren der Nachrichtenstruktur

Als nächstes definieren wir die Nachricht:

message HelloRequest { string firstName = 1; string lastName = 2; }

Dies definiert die Anforderungsnutzlast. Hier wird jedes Attribut, das in die Nachricht eingeht, zusammen mit seinem Typ definiert.

Jedem Attribut muss eine eindeutige Nummer zugewiesen werden, die als Tag bezeichnet wird. Dieses Tag wird vom Protokollpuffer verwendet, um das Attribut darzustellen, anstatt den Attributnamen zu verwenden.

Im Gegensatz zu JSON, bei dem der Attributname jedes Mal der Vorname übergeben wird, verwendet der Protokollpuffer die Nummer 1, um den Vornamen darzustellen . Die Definition der Antwortnutzdaten ähnelt der Anforderung.

Beachten Sie, dass wir dasselbe Tag für mehrere Nachrichtentypen verwenden können:

message HelloResponse { string greeting = 1; }

4.3. Servicevertrag definieren

Lassen Sie uns abschließend den Servicevertrag definieren. Für unseren HelloService definieren wir eine hello () Operation:

service HelloService { rpc hello(HelloRequest) returns (HelloResponse); }

Die Operation hello () akzeptiert eine unäre Anforderung und gibt eine unäre Antwort zurück. gRPC unterstützt auch das Streaming, indem der Anforderung und Antwort das Stream- Schlüsselwort vorangestellt wird .

5. Code generieren

Jetzt übergeben wir die Datei HelloService.proto an den Protokollpuffer-Compiler protoc , um die Java-Dateien zu generieren. Es gibt mehrere Möglichkeiten, dies auszulösen.

5.1. Verwenden des Protokollpuffer-Compilers

Zunächst benötigen wir den Protocol Buffer Compiler. Wir können aus vielen vorkompilierten Binärdateien auswählen, die hier verfügbar sind.

Zusätzlich benötigen wir das gRPC Java Codegen Plugin.

Schließlich können wir den folgenden Befehl verwenden, um den Code zu generieren:

protoc --plugin=protoc-gen-grpc-java=$PATH_TO_PLUGIN -I=$SRC_DIR --java_out=$DST_DIR --grpc-java_out=$DST_DIR $SRC_DIR/HelloService.proto

5.2. Verwenden des Maven Plugins

Als Entwickler möchten Sie, dass die Codegenerierung eng in Ihr Build-System integriert wird. gRPC bietet ein Protobuf-Maven-Plugin für das Maven-Build-System:

   kr.motd.maven os-maven-plugin 1.6.1     org.xolstice.maven.plugins protobuf-maven-plugin 0.6.1   com.google.protobuf:protoc:3.3.0:exe:${os.detected.classifier}  grpc-java  io.grpc:protoc-gen-grpc-java:1.4.0:exe:${os.detected.classifier}      compile compile-custom      

Die Erweiterung / das Plugin des os-maven-plugins generiert verschiedene nützliche plattformabhängige Projekteigenschaften wie $ {os.detected.classifier}

6. Server erstellen

Unabhängig davon, welche Methode Sie zur Codegenerierung verwenden, werden die folgenden Schlüsseldateien generiert:

  • HelloRequest.java - enthält die HelloRequest Typdefinition
  • HelloResponse.java - enthält die Definition des HelleResponse- Typs
  • HelloServiceImplBase.java - enthält die abstrakte Klasse HelloServiceImplBase, die eine Implementierung aller Operationen bereitstellt, die wir in der Serviceschnittstelle definiert haben

6.1. Überschreiben der Service-Basisklasse

Die Standardimplementierung der abstrakten Klasse HelloServiceImplBase besteht darin, die Laufzeitausnahme io.grpc.StatusRuntimeException auszulösen , die besagt , dass die Methode nicht implementiert ist.

We shall extend this class and override the hello() method mentioned in our service definition:

public class HelloServiceImpl extends HelloServiceImplBase { @Override public void hello( HelloRequest request, StreamObserver responseObserver) { String greeting = new StringBuilder() .append("Hello, ") .append(request.getFirstName()) .append(" ") .append(request.getLastName()) .toString(); HelloResponse response = HelloResponse.newBuilder() .setGreeting(greeting) .build(); responseObserver.onNext(response); responseObserver.onCompleted(); } }

If we compare the signature of hello() with the one we wrote in the HellService.proto file, we'll notice that it does not return HelloResponse. Instead, it takes the second argument as StreamObserver, which is a response observer, a call back for the server to call with its response.

This way the client gets an option to make a blocking call or a non-blocking call.

gRPC uses builders for creating objects. We use HelloResponse.newBuilder() and set the greeting text to build a HelloResponse object. We set this object to the responseObserver's onNext() method to send it to the client.

Finally, we need to call onCompleted() to specify that we’ve finished dealing with the RPC, else the connection will be hung, and the client will just wait for more information to come in.

6.2. Running the Grpc Server

Next, we need to start the gRPC server to listen for incoming requests:

public class GrpcServer { public static void main(String[] args) { Server server = ServerBuilder .forPort(8080) .addService(new HelloServiceImpl()).build(); server.start(); server.awaitTermination(); } }

Here, again we use the builder to create a gRPC server on port 8080 and add the HelloServiceImpl service that we defined. start() would start the server. In our example, we will call awaitTermination() to keep the server running in the foreground blocking the prompt.

7. Creating the Client

gRPC provides a channel construct that abstracts out the underlying details like connection, connection pooling, load balancing, etc.

We'll create a channel using ManagedChannelBuilder. Here, we specify the server address and port.

We'll be using plain text without any encryption:

public class GrpcClient { public static void main(String[] args) { ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost", 8080) .usePlaintext() .build(); HelloServiceGrpc.HelloServiceBlockingStub stub = HelloServiceGrpc.newBlockingStub(channel); HelloResponse helloResponse = stub.hello(HelloRequest.newBuilder() .setFirstName("Baeldung") .setLastName("gRPC") .build()); channel.shutdown(); } }

Next, we need to create a stub which we'll use to make the actual remote call to hello(). The stub is the primary way for clients to interacts with the server. When using auto-generated stubs, the stub class will have constructors for wrapping the channel.

Here we're using a blocking/synchronous stub so that the RPC call waits for the server to respond, and will either return a response or raise an exception. There are two other types of stubs provided by gRPC, which facilitate non-blocking/asynchronous calls.

Endlich Zeit für den hallo () RPC-Aufruf. Hier übergeben wir die HelloRequest . Wir können die automatisch generierten Setter verwenden, um die Attribute firstName , lastName des HelloRequest- Objekts festzulegen .

Wir erhalten das vom Server zurückgegebene HelloResponse- Objekt zurück.

8. Fazit

In diesem Tutorial haben wir gesehen, wie wir mit gRPC die Entwicklung der Kommunikation zwischen zwei Diensten vereinfachen können, indem wir uns darauf konzentrieren, den Dienst zu definieren und den gRPC den gesamten Code für das Boilerplate verarbeiten zu lassen.

Wie üblich finden Sie die Quellen auf GitHub.