Einführung in gRPC

Einführung in gRPC

1. Einführung

gRPC is a high performance, open source RPC framework initially developed by Google. Es hilft beim Eliminieren von Boilerplate-Code und beim Verbinden von Polyglot-Diensten in und zwischen Rechenzentren.

2. Überblick

Das Framework basiert auf einem Client-Server-Modell für Remoteprozeduraufrufe. A client application can directly call methods on a server application as if it was a local object.

In diesem Artikel werden die folgenden Schritte zum Erstellen einer typischen Client-Server-Anwendung mit gRPC ausgeführt:

  1. Definieren Sie einen Dienst in einer.proto-Datei

  2. Generieren Sie Server- und Client-Code mit dem Protokollpuffer-Compiler

  3. Erstellen Sie die Serveranwendung, implementieren Sie die generierten Serviceschnittstellen und starten Sie den gRPC-Server

  4. Erstellen Sie die Clientanwendung, und führen Sie RPC-Aufrufe mit generierten Stubs aus

Definieren wir ein einfachesHelloService, das im Austausch für den Vor- und Nachnamen Grüße zurückgibt.

3. Maven-Abhängigkeiten

Fügen wir die Abhängigkeitengrpc-netty,grpc-protobuf undgrpc-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,specifying methods that can be called remotely along with their parameters and return types.

Dies erfolgt in der Datei.proto mitprotocol buffers. Sie werden auch zur Beschreibung der Struktur der Nutzlastnachrichten verwendet.

4.1. Grundkonfigurationen

Lassen Sie uns eineHelloService.proto-Datei für unsere StichprobeHelloService erstellen. Zunächst werden einige grundlegende Konfigurationsdetails hinzugefügt:

syntax = "proto3";
option java_multiple_files = true;
package org.example.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. This tag is used by the protocol buffer to represent the attribute instead of using the attribute name.

Im Gegensatz zu JSON, bei dem der AttributnamefirstName jedes Mal übergeben wird, verwendet der Protokollpuffer die Nummer 1, umfirstName darzustellen. Die Definition der Antwortnutzlast ä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

Definieren wir abschließend den Servicevertrag. Für unsereHelloService definieren wir einehello() Operation:

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

Die Operationhello()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 Schlüsselwortstreamvorangestellt wird.

5. Code generieren

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

5.1. Verwenden des Protokollpuffer-Compilers

Download the compiler und folgen Sie den Anweisungen in der README-Datei.

Sie können den folgenden Befehl verwenden, um den Code zu generieren:

protoc -I=$SRC_DIR --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 liefertprotobuf-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 Pluginos-maven-plugin generiert verschiedene nützliche plattformabhängige Projekteigenschaften wie$\{os.detected.classifier}

6. Server erstellen

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

  • HelloRequest.java – enthält die Typdefinition vonHelloRequest

  • HelloResponse.java *–* enthält die Typdefinition vonHelleResponse

  • HelloServiceImplBase.java *–* enthält die abstrakte KlasseHelloServiceImplBase, die eine Implementierung aller Operationen bereitstellt, die wir in der Serviceschnittstelle definiert haben

6.1. Überschreiben der Service-Basisklasse

Diedefault implementation of the abstract class HelloServiceImplBase is to throw runtime exceptionio.grpc.StatusRuntimeException sagen, dass die Methode nicht implementiert ist.

Wir werden diese Klasse erweitern und die in unserer Service-Definition erwähntehello()-Methode überschreiben:

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();
    }
}

Wenn wir die Signatur vonhello() mit der Signatur vergleichen, die wir in die DateiHellService.proto geschrieben haben, werden wir feststellen, dassHelloResponse nicht zurückgegeben wird. Stattdessen wird das zweite Argument alsStreamObserver<HelloResponse> verwendet, bei dem es sich um einen Antwortbeobachter handelt, einen Rückruf, den der Server mit seiner Antwort aufrufen muss.

Auf diese Weisethe client gets an option to make a blocking call or a non-blocking call.

gRPC verwendet Builder zum Erstellen von Objekten. Wir verwendenHelloResponse.newBuilder() und setzen den Begrüßungstext, um einHelloResponse-Objekt zu erstellen. Wir setzen dieses Objekt auf dieonNext()-Methode des responseObservers, um es an den Client zu senden.

Schließlich müssen wironCompleted() aufrufen, um anzugeben, dass wir mit dem RPC fertig sind. Andernfalls wird die Verbindung unterbrochen und der Client wartet nur, bis weitere Informationen eingehen.

6.2. Ausführen des Grpc-Servers

Als Nächstes müssen wir den gRPC-Server starten, um auf eingehende Anforderungen zu warten:

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

        server.start();
        server.awaitTermination();
    }
}

Auch hier verwenden wir den Builder, um einen gRPC-Server auf Port 8080 zu erstellen und den von uns definierten DienstHelloServiceImplhinzuzufügen. start() würde den Server starten. In unserem Beispiel rufen wirawaitTermination() auf, um den Server im Vordergrund laufen zu lassen und die Eingabeaufforderung zu blockieren.

7. Client erstellen

gRPC provides a channel construct which abstracts out the underlying details wie Verbindung, Verbindungspooling, Lastausgleich usw.

Wir erstellen einen Kanal mitManagedChannelBuilder. Hier geben wir die Serveradresse und den Port an.

Wir verwenden einfachen Text ohne Verschlüsselung:

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("example")
          .setLastName("gRPC")
          .build());

        channel.shutdown();
    }
}

Als Nächstes müssen wir einen Stub erstellen, mit dem wir den eigentlichen Remote-Aufruf vonhello() durchführen. The stub is the primary way for clients to interacts with the server. Bei Verwendung von automatisch generierten Stubs verfügt die Stub-Klasse über Konstruktoren zum Umschließen des Kanals.

Hier verwenden wir einen blockierenden / synchronen Stub, damit der RPC-Aufruf auf die Antwort des Servers wartet und entweder eine Antwort zurückgibt oder eine Ausnahme auslöst. Es gibt zwei weitere Arten von gRPC-Stubs, die nicht blockierende / asynchrone Aufrufe ermöglichen.

Endlich Zeit für den RPC-Aufruf vonhello(). Hier übergeben wir dieHelloRequest. Wir können die automatisch generierten Setter verwenden, um die AttributefirstName,lastName des ObjektsHelloRequest festzulegen.

Wir erhalten das vom Server zurückgegebeneHelloResponse-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 des Boilerplates verarbeiten zu lassen.

Wie üblich finden Sie die Quellenover on GitHub.