Tworzenie usługi gRPC za pomocą Javy

1. Przegląd

gRPC to niezależna od języka i platformy struktura zdalnych wywołań procedur (RPC) oraz zestaw narzędzi opracowany w Google. Umożliwia zdefiniowanie usługi za pomocą buforów protokołu, czyli szczególnie zaawansowanego zestawu narzędzi i języka do serializacji binarnej. Umożliwia to generowanie idiomatycznych stubów klienta i serwera z definicji usługi w różnych językach.

W tym ćwiczeniu dowiesz się, jak utworzyć usługę opartą na Javie, która udostępnia interfejs API za pomocą platformy gRPC, a następnie napisać klienta, który będzie używać wygenerowanego stuba po stronie klienta gRPC.

Czego się nauczysz

  • Język bufora protokołu
  • Implementowanie usługi gRPC za pomocą Javy
  • Jak zaimplementować klienta gRPC za pomocą języka Java

Jak zamierzasz korzystać z tego samouczka?

Tylko przeczytaj Przeczytaj i wykonaj ćwiczenia

Jak oceniasz swoje doświadczenie w tworzeniu aplikacji Node.js?

Początkujący Średnio zaawansowany Zaawansowany

Jak oceniasz tworzenie aplikacji w Go?

Początkujący Średnio zaawansowany Zaawansowany

2. Konfiguracja i wymagania

Samodzielne konfigurowanie środowiska

  1. Zaloguj się w konsoli Google Cloud i utwórz nowy projekt lub użyj istniejącego. Jeśli nie masz jeszcze konta Gmail lub Google Workspace, musisz je utworzyć.

96a9c957bc475304.pngb9a10ebdf5b5a448.pnga1e3c01a38fa61c2.png

Zapamiętaj identyfikator projektu, czyli unikalną nazwę we wszystkich projektach Google Cloud (podana powyżej nazwa jest już zajęta i nie będzie działać w Twoim przypadku). W dalszej części tego laboratorium będzie on nazywany PROJECT_ID.

  1. Następnie musisz włączyć rozliczenia w Cloud Console, aby korzystać z zasobów Google Cloud.

Ukończenie tego laboratorium nie powinno wiązać się z dużymi kosztami, a nawet z żadnymi. Wykonaj instrukcje z sekcji „Czyszczenie”, w której znajdziesz informacje o tym, jak wyłączyć zasoby, aby uniknąć naliczenia opłat po zakończeniu tego samouczka. Nowi użytkownicy Google Cloud mogą skorzystać z programu bezpłatnego okresu próbnego, w którym mają do dyspozycji środki w wysokości 300 USD.

Google Cloud Shell

To ćwiczenie możesz wykonać na swoim komputerze, ale w tym przypadku będziemy używać Google Cloud Shell, czyli środowiska wiersza poleceń działającego w chmurze.

Ta maszyna wirtualna oparta na Debianie zawiera wszystkie potrzebne narzędzia dla programistów. Zawiera również stały katalog domowy o pojemności 5 GB i działa w Google Cloud, co znacznie zwiększa wydajność sieci i usprawnia proces uwierzytelniania. Oznacza to, że do ukończenia tego ćwiczenia potrzebujesz tylko przeglądarki (działa ona na Chromebooku).

  1. Aby aktywować Cloud Shell w konsoli Cloud, kliknij Aktywuj Cloud Shell a8460e837e9f5fda.png (udostępnienie środowiska i połączenie się z nim powinno zająć tylko kilka chwil).

b532b2f19ab85dda.pngScreen Shot 2017-06-14 at 10.13.43 PM.png

Po połączeniu z Cloud Shell zobaczysz, że uwierzytelnianie zostało już przeprowadzone, a projekt jest już ustawiony na Twój identyfikator projektu PROJECT_ID.

gcloud auth list

Wynik polecenia

Credentialed accounts:
 - <myaccount>@<mydomain>.com (active)
gcloud config list project

Wynik polecenia

[core]
project = <PROJECT_ID>

Jeśli z jakiegoś powodu projekt nie jest ustawiony, po prostu wydaj to polecenie:

gcloud config set project <PROJECT_ID>

Szukasz urządzenia PROJECT_ID? Sprawdź, jakiego identyfikatora użyto w krokach konfiguracji, lub wyszukaj go w panelu konsoli Cloud:

2485e00c1223af09.png

Cloud Shell domyślnie ustawia też niektóre zmienne środowiskowe, które mogą być przydatne podczas wykonywania kolejnych poleceń.

echo $GOOGLE_CLOUD_PROJECT

Wynik polecenia

<PROJECT_ID>
  1. Na koniec ustaw domyślną strefę i konfigurację projektu.
gcloud config set compute/zone us-central1-f

Możesz wybrać różne strefy. Więcej informacji znajdziesz w artykule Regiony i strefy.

3. Tworzenie usługi gRPC

Utwórz nowy projekt w języku Java za pomocą Maven:

$ mvn archetype:generate -DgroupId=com.example.grpc \
 -DartifactId=grpc-hello-server \
 -DarchetypeArtifactId=maven-archetype-quickstart \
 -DinteractiveMode=false
$ cd grpc-hello-server

Dodawanie pliku definicji gRPC

W gRPC ładunki usług (żądania i odpowiedzi) oraz operacje usług muszą być rejestrowane w IDL (Interface Definition Language). gRPC używa składni Protobuffer 3 do definiowania ładunków wiadomości i operacji. Utwórzmy plik proto dla prostej usługi Greeting Service z żądaniem Hello Request i odpowiedzią Hello Response.

Najpierw utwórz nowy katalog proto, w którym będzie przechowywany nowy plik proto:

$ mkdir -p src/main/proto

Następnie utwórz nowy plik proto src/main/proto/GreetingService.proto.

Aby edytować plik, możesz użyć vim,nano,lub emacs:

src/main/proto/GreetingService.proto

syntax = "proto3";
package com.example.grpc;

// Request payload
message HelloRequest {
  // Each message attribute is strongly typed.
  // You also must assign a "tag" number.
  // Each tag number is unique within the message.
  string name = 1;

  // This defines a strongly typed list of String
  repeated string hobbies = 2;

  // There are many more basics types, like Enum, Map
  // See https://developers.google.com/protocol-buffers/docs/proto3
  // for more information.
}

message HelloResponse {
  string greeting = 1;
}

// Defining a Service, a Service can have multiple RPC operations
service GreetingService {
  // Define a RPC operation
  rpc greeting(HelloRequest) returns (HelloResponse);
}

Dodawanie zależności i wtyczki gRPC

Gdy będziesz mieć definicję, możemy wygenerować z tego pliku zarówno stub po stronie serwera, jak i stub po stronie klienta. Musisz dodać zależności i wtyczki gRPC.

Najpierw dodaj zależności gRPC do pliku pom.xml:

pom.xml

<project>
  ...
  <dependencies>
    <dependency>
      <groupId>io.grpc</groupId>
      <artifactId>grpc-netty-shaded</artifactId>
      <version>1.24.0</version>
    </dependency>
    <dependency>
      <groupId>io.grpc</groupId>
      <artifactId>grpc-protobuf</artifactId>
      <version>1.24.0</version>
    </dependency>
    <dependency>
      <groupId>io.grpc</groupId>
      <artifactId>grpc-stub</artifactId>
      <version>1.24.0</version>
    </dependency>
    <dependency>
      <groupId>javax.annotation</groupId>
      <artifactId>javax.annotation-api</artifactId>
      <version>1.3.2</version>
    </dependency>
    ...
  </dependencies>
  ...
</project>

Następnie dodaj wtyczkę:

pom.xml

<project>
  ...
  <dependencies>
    ...
  </dependencies>
  <build>
    <extensions>
      <extension>
        <groupId>kr.motd.maven</groupId>
        <artifactId>os-maven-plugin</artifactId>
        <version>1.6.2</version>
      </extension>
    </extensions>
    <plugins>
      <plugin>
        <groupId>org.xolstice.maven.plugins</groupId>
        <artifactId>protobuf-maven-plugin</artifactId>
        <version>0.6.1</version>
        <configuration>
          <protocArtifact>com.google.protobuf:protoc:3.9.0:exe:${os.detected.classifier}</protocArtifact>
          <pluginId>grpc-java</pluginId>
          <pluginArtifact>io.grpc:protoc-gen-grpc-java:1.24.0:exe:${os.detected.classifier}</pluginArtifact>
        </configuration>
        <executions>
          <execution>
            <goals>
              <goal>compile</goal>
              <goal>compile-custom</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>

</project>

Generowanie stubów

Podczas kompilowania aplikacji wtyczka przekonwertuje definicje proto na kod Java.

$ mvn -DskipTests package

Aby wyświetlić wygenerowane pliki:

$ find target/generated-sources

Wdrażanie Usługi

Najpierw utwórz nową klasę GreetingServiceImpl, która będzie implementować operację greeting:

src/main/java/com/example/grpc/GreetingServiceImpl.java

package com.example.grpc;

import io.grpc.stub.StreamObserver;

public class GreetingServiceImpl extends GreetingServiceGrpc.GreetingServiceImplBase {
  @Override
  public void greeting(GreetingServiceOuterClass.HelloRequest request,
        StreamObserver<GreetingServiceOuterClass.HelloResponse> responseObserver) {
  // HelloRequest has toString auto-generated.
    System.out.println(request);

    // You must use a builder to construct a new Protobuffer object
    GreetingServiceOuterClass.HelloResponse response = GreetingServiceOuterClass.HelloResponse.newBuilder()
      .setGreeting("Hello there, " + request.getName())
      .build();

    // Use responseObserver to send a single response back
    responseObserver.onNext(response);

    // When you are done, you must call onCompleted.
    responseObserver.onCompleted();
  }
}

Implementowanie serwera

Na koniec musisz uruchomić serwer, aby nasłuchiwał na porcie i zarejestrować tę implementację usługi. Edytuj klasę App i jej główną metodę:

src/main/java/com/example/grpc/App.java

package com.example.grpc;

import io.grpc.*;

public class App
{
    public static void main( String[] args ) throws Exception
    {
      // Create a new server to listen on port 8080
      Server server = ServerBuilder.forPort(8080)
        .addService(new GreetingServiceImpl())
        .build();

      // Start the server
      server.start();

      // Server threads are running in the background.
      System.out.println("Server started");
      // Don't exit the main thread. Wait until server is terminated.
      server.awaitTermination();
    }
}

Na koniec uruchom serwer:

$ mvn -DskipTests package exec:java -Dexec.mainClass=com.example.grpc.App
...
Server Started

4. Korzystanie z usługi

Generator wygenerował już wszystkie stuby po stronie klienta. Aby uprościć to ćwiczenie, użyjemy tego samego projektu Maven, ale po prostu dodamy nową klasę Client z nową metodą główną.

Najpierw kliknij +, aby otworzyć nową sesję Cloud Shell, dzięki czemu nie musisz zamykać serwera:

1ff0fda960b9adfc.png

W nowej sesji przejdź do katalogu grpc-hello-server:

$ cd grpc-hello-server

Następnie dodaj nowe zajęcia Client:

src/main/java/com/example/grpc/Client.java

package com.example.grpc;

import io.grpc.*;

public class Client
{
    public static void main( String[] args ) throws Exception
    {
      // Channel is the abstraction to connect to a service endpoint
      // Let's use plaintext communication because we don't have certs
      final ManagedChannel channel = ManagedChannelBuilder.forTarget("localhost:8080")
        .usePlaintext(true)
        .build();

      // It is up to the client to determine whether to block the call
      // Here we create a blocking stub, but an async stub,
      // or an async stub with Future are always possible.
      GreetingServiceGrpc.GreetingServiceBlockingStub stub = GreetingServiceGrpc.newBlockingStub(channel);
      GreetingServiceOuterClass.HelloRequest request =
        GreetingServiceOuterClass.HelloRequest.newBuilder()
          .setName("Ray")
          .build();

      // Finally, make the call using the stub
      GreetingServiceOuterClass.HelloResponse response = 
        stub.greeting(request);

      System.out.println(response);

      // A Channel should be shutdown before stopping the process.
      channel.shutdownNow();
    }
}

Na koniec uruchom klienta:

$ mvn -DskipTests package exec:java -Dexec.mainClass=com.example.grpc.Client
...
greeting: "Hello there, Ray"

To wszystko. Proste, prawda?

5. Usługa strumieniowania

Możesz wypróbować wiele innych rozwiązań. Możesz na przykład łatwo utworzyć usługę przesyłania strumieniowego, dodając słowo kluczowe stream w pliku proto do parametru żądania lub odpowiedzi, np.

src/main/proto/GreetingService.proto

syntax = "proto3";
package com.example.grpc;

...

// Defining a Service, a Service can have multiple RPC operations
service GreetingService {
  // MODIFY HERE: Update the return to streaming return.
  rpc greeting(HelloRequest) returns (stream HelloResponse);
}

Zaktualizuj serwer, aby wysyłał wiele odpowiedzi, a nie tylko jedną. Możesz to zrobić, wykonując kilka wywołań responseObserver.onNext(...):

src/main/java/com/example/grpc/GreetingServiceImpl.java

package com.example.grpc;

import io.grpc.stub.StreamObserver;

public class GreetingServiceImpl extends GreetingServiceGrpc.GreetingServiceImplBase {
  @Override
  public void greeting(GreetingServiceOuterClass.HelloRequest request,
        StreamObserver<GreetingServiceOuterClass.HelloResponse> responseObserver) {
 
    ...

    // Feel free to construct different responses if you'd like.
    responseObserver.onNext(response);
    responseObserver.onNext(response);
    responseObserver.onNext(response);

    // When you are done, you must call onCompleted.
    responseObserver.onCompleted();
  }
}

Klient musi używać stuba asynchronicznego zamiast stuba blokującego. Zaktualizuj kod klienta, pamiętając o zmianie typu stub na GreetingServiceStub:

src/main/java/com/example/grpc/Client.java

package com.example.grpc;

import io.grpc.*;

// New import
import io.grpc.stub.*;

public class Client
{
    public static void main( String[] args ) throws Exception
    {
      final ManagedChannel channel = ManagedChannelBuilder.forTarget("localhost:8080")
        .usePlaintext(true)
        .build();

      // Replace the previous synchronous code with asynchronous code.
      // This time use an async stub:
       GreetingServiceGrpc.GreetingServiceStub stub = GreetingServiceGrpc.newStub(channel);

      // Construct a request
      GreetingServiceOuterClass.HelloRequest request =
        GreetingServiceOuterClass.HelloRequest.newBuilder()
          .setName("Ray")
          .build();

      // Make an Asynchronous call. Listen to responses w/ StreamObserver
      stub.greeting(request, new StreamObserver<GreetingServiceOuterClass.HelloResponse>() {
        public void onNext(GreetingServiceOuterClass.HelloResponse response) {
          System.out.println(response);
        }
        public void onError(Throwable t) {
        }
        public void onCompleted() {
          // Typically you'll shutdown the channel somewhere else.
          // But for the purpose of the lab, we are only making a single
          // request. We'll shutdown as soon as this request is done.
          channel.shutdownNow();
        }
      });
    }
}

Przebuduj aplikację:

$ mvn -DskipTests package

Uruchom ponownie serwer i klienta w oddzielnych sesjach Cloud Shell.

Aby uruchomić serwer:

$ mvn exec:java -Dexec.mainClass=com.example.grpc.App
...
Server Started

Aby uruchomić klienta:

$ mvn exec:java -Dexec.mainClass=com.example.grpc.Client
...
greeting: "Hello there, Ray"
greeting: "Hello there, Ray"
greeting: "Hello there, Ray"

6. Gratulacje!

Omówione zagadnienia:

  • Język bufora protokołu
  • Jak zaimplementować serwer gRPC za pomocą Javy
  • Jak zaimplementować klienta gRPC za pomocą języka Java

Dalsze kroki:

Prześlij nam swoją opinię

  • Poświęć chwilę na wypełnienie naszej krótkiej ankiety.