1. Wprowadzenie
W tym module praktycznym użyjesz gRPC-Java do utworzenia klienta i serwera, które będą stanowić podstawę aplikacji do mapowania tras napisanej w języku Java.
Po ukończeniu tego samouczka będziesz mieć klienta, który łączy się z serwerem zdalnym za pomocą gRPC, aby uzyskać nazwę lub adres pocztowy miejsca znajdującego się pod określonymi współrzędnymi na mapie. W pełni funkcjonalna aplikacja może korzystać z tego modelu klient-serwer, aby wyliczać lub podsumowywać ważne miejsca na trasie.
Interfejs API serwera jest zdefiniowany w pliku Protocol Buffers, który będzie używany do generowania kodu szablonowego dla klienta i serwera, aby mogły się ze sobą komunikować. Dzięki temu zaoszczędzisz czas i wysiłek potrzebny na wdrożenie tej funkcji.
Wygenerowany kod obsługuje nie tylko złożoność komunikacji między serwerem a klientem, ale także serializację i deserializację danych.
Czego się nauczysz
- Jak używać buforów protokołu do definiowania interfejsu API usługi.
- Jak utworzyć klienta i serwer oparte na gRPC na podstawie definicji Protocol Buffers za pomocą automatycznego generowania kodu.
- Znajomość komunikacji klient-serwer z użyciem gRPC.
Te warsztaty są przeznaczone dla programistów Java, którzy dopiero zaczynają korzystać z gRPC lub chcą sobie przypomnieć podstawy tej technologii, a także dla wszystkich innych osób zainteresowanych tworzeniem systemów rozproszonych. Nie musisz mieć wcześniejszego doświadczenia z gRPC.
2. Zanim zaczniesz
Wymagania wstępne
- JDK w wersji 8 lub nowszej
Pobierz kod
Aby nie trzeba było zaczynać od zera, w tym ćwiczeniu znajdziesz szkielet kodu źródłowego aplikacji, który możesz uzupełnić. Z tych instrukcji dowiesz się, jak dokończyć aplikację, w tym jak użyć wtyczek kompilatora buforów protokołów do wygenerowania kodu gRPC.
Najpierw utwórz katalog roboczy z ćwiczeniami i przejdź do niego:
mkdir grpc-java-getting-started && cd grpc-java-getting-started
Pobierz i rozpakuj ćwiczenia z programowania:
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
Możesz też pobrać plik ZIP zawierający tylko katalog z instrukcjami i rozpakować go ręcznie.
Jeśli nie chcesz wpisywać implementacji, gotowy kod źródłowy jest dostępny na GitHubie.
3. Określ usługę
Pierwszym krokiem jest zdefiniowanie usługi gRPC aplikacji, jej metody RPC oraz typów wiadomości żądania i odpowiedzi za pomocą buforów protokołu. Twoja usługa będzie zapewniać:
- Metoda RPC o nazwie
GetFeature
, która jest zaimplementowana na serwerze i wywoływana przez klienta. - Typy wiadomości
Point
iFeature
to struktury danych wymieniane między klientem a serwerem podczas korzystania z metodyGetFeature
. Klient podaje współrzędne mapy jakoPoint
w żądaniuGetFeature
wysyłanym do serwera, a serwer odpowiada, przesyłając odpowiedniFeature
, który opisuje wszystko, co znajduje się pod tymi współrzędnymi.
Ta metoda RPC i jej typy wiadomości będą zdefiniowane w pliku src/main/proto/routeguide/route_guide.proto
podanego kodu źródłowego.
Protokoły buforów są powszechnie znane jako protobufy. Więcej informacji o terminologii gRPC znajdziesz w artykule Podstawowe koncepcje, architektura i cykl życia.
W tym przykładzie generujemy kod w języku Java, dlatego w pliku .proto
określiliśmy opcję pliku java_package
i nazwę klasy Java:
option java_package = "io.grpc.examples.routeguide";
option java_outer_classname = "RouteGuideProto";
Rodzaje wiadomości
W routeguide/route_guide.proto
pliku kodu źródłowego najpierw zdefiniuj Point
typ wiadomości. Symbol Point
reprezentuje parę współrzędnych szerokości i długości geograficznej na mapie. W tym ćwiczeniu używaj liczb całkowitych jako współrzędnych:
message Point {
int32 latitude = 1;
int32 longitude = 2;
}
Numery 1
i 2
to unikalne identyfikatory poszczególnych pól w strukturze message
.
Następnie określ Feature
typ wiadomości. Feature
używa pola string
na nazwę lub adres pocztowy czegoś w lokalizacji określonej przez Point
:
message Feature {
// The name or address of the feature.
string name = 1;
// The point where the feature is located.
Point location = 2;
}
Metoda usługi
Plik route_guide.proto
ma strukturę service
o nazwie RouteGuide
, która definiuje co najmniej 1 metodę udostępnianą przez usługę aplikacji.
Dodaj metodę rpc
GetFeature
w definicji RouteGuide
. Jak wspomnieliśmy wcześniej, ta metoda wyszukuje nazwę lub adres lokalizacji na podstawie podanego zestawu współrzędnych, więc w przypadku danego Point
zwraca GetFeature
Feature
:
service RouteGuide {
// Definition of the service goes here
// Obtains the feature at a given position.
rpc GetFeature(Point) returns (Feature) {}
}
Jest to metoda RPC typu unary: prosta procedura RPC, w której klient wysyła żądanie do serwera i czeka na odpowiedź, podobnie jak w przypadku lokalnego wywołania funkcji.
4. Generowanie kodu klienta i serwera
Następnie musimy wygenerować interfejsy klienta i serwera gRPC z naszej definicji usługi .proto
. W tym celu używamy kompilatora buforów protokołu protoc
ze specjalną wtyczką gRPC Java. Aby wygenerować usługi gRPC, musisz użyć kompilatora proto3 (który obsługuje składnię proto2 i proto3).
Jeśli używasz Gradle lub Maven, protoc
wtyczka do kompilacji może wygenerować niezbędny kod w ramach kompilacji. Więcej informacji o generowaniu kodu z własnych plików .proto
znajdziesz w pliku README pakietu grpc-java.
W kodzie źródłowym laboratorium udostępniliśmy środowisko i konfigurację Gradle, które umożliwiają utworzenie tego projektu.
W katalogu grpc-java-getting-started
uruchom to polecenie:
$ chmod +x gradlew $ ./gradlew generateProto
Z definicji usługi generowane są te klasy:
Feature.java
,Point.java
i inne, które zawierają cały kod bufora protokołu do wypełniania, serializowania i pobierania typów wiadomości żądań i odpowiedzi.RouteGuideGrpc.java
, który zawiera (oprócz innego przydatnego kodu) klasę bazową do implementacji serwerówRouteGuide
,RouteGuideGrpc.RouteGuideImplBase
ze wszystkimi metodami zdefiniowanymi w klasach usług i klasach stubówRouteGuide
, z których mogą korzystać klienci.
5. Wdrażanie serwera
Najpierw zobaczmy, jak utworzyć RouteGuide
serwerRouteGuide
. Aby usługa RouteGuide
działała prawidłowo, musisz wykonać 2 czynności:
- Implementacja interfejsu usługi wygenerowanego na podstawie definicji usługi, który wykonuje rzeczywistą „pracę” usługi.
- Uruchomienie serwera gRPC, który nasłuchuje żądań od klientów i przekazuje je do właściwej implementacji usługi.
Implementacja RouteGuide
Jak widać, nasz serwer ma klasę RouteGuideService
, która rozszerza wygenerowaną klasę abstrakcyjną RouteGuideGrpc.RouteGuideImplBase
:
private static class RouteGuideService extends RouteGuideGrpc.RouteGuideImplBase {
...
}
Aby zainicjować serwer za pomocą funkcji, udostępniliśmy te 2 pliki:
./src/main/java/io/grpc/examples/routeguide/RouteGuideUtil.java
./src/main/resources/io/grpc/examples/routeguide/route_guide_db.json
Przyjrzyjmy się szczegółowo prostej implementacji RPC.
Wywołanie RPC typu unary
RouteGuideService
wdraża wszystkie nasze metody obsługi. W tym przypadku jest to tylko GetFeature()
, które pobiera wiadomość Point
od klienta i zwraca w wiadomości Feature
odpowiednie informacje o lokalizacji z listy znanych miejsc.
@Override
public void getFeature(Point request, StreamObserver<Feature> responseObserver) {
responseObserver.onNext(checkFeature(request));
responseObserver.onCompleted();
}
Metoda getFeature()
przyjmuje 2 parametry:
Point
: prośba.StreamObserver<Feature>
: obserwator odpowiedzi, czyli specjalny interfejs, za pomocą którego serwer może wywołać odpowiedź.
Aby zwrócić odpowiedź do klienta i zakończyć połączenie:
- Tworzymy i wypełniamy obiekt odpowiedzi
Feature
, aby zwrócić go do klienta zgodnie z definicją usługi. W tym przykładzie robimy to w osobnej prywatnej metodziecheckFeature()
. - Używamy metody
onNext()
obserwatora odpowiedzi, aby zwrócićFeature
. - Używamy metody
onCompleted()
obserwatora odpowiedzi, aby określić, że zakończyliśmy obsługę RPC.
Uruchamianie serwera
Po zaimplementowaniu wszystkich metod usługi musimy uruchomić serwer gRPC, aby klienci mogli z niej korzystać. W naszym szablonie uwzględniamy tworzenie obiektu ServerBuilder:
ServerBuilder.forPort(port), port, RouteGuideUtil.parseFeatures(featureFile)
Usługę tworzymy w konstruktorze:
- Określ port, którego chcemy używać do nasłuchiwania żądań klientów, za pomocą metody
forPort()
narzędzia do tworzenia (będzie ono używać adresu wieloznacznego). - Utwórz instancję klasy implementacji usługi
RouteGuideService
i przekaż ją do metodyaddService()
konstruktora. - Wywołaj
build()
na konstruktorze, aby utworzyć serwer RPC dla naszej usługi.
Poniższy fragment kodu pokazuje, jak tworzymy obiekt ServerBuilder
.
/** 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));
}
Poniższy fragment kodu pokazuje, jak utworzyć obiekt serwera dla usługi RouteGuide
.
/** 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();
}
Zaimplementuj metodę start, która wywołuje start
na serwerze utworzonym powyżej.
public void start() throws IOException {
server.start();
logger.info("Server started, listening on " + port);
}
Zaimplementuj metodę oczekiwania na zakończenie działania serwera, aby nie zamykał się on od razu.
/** Await termination on the main thread since the grpc library uses daemon threads. */
private void blockUntilShutdown() throws InterruptedException {
if (server != null) {
server.awaitTermination();
}
}
Jak widać, tworzymy i uruchamiamy serwer za pomocą ServerBuilder
.
W metodzie głównej:
- Utwórz instancję
RouteGuideServer
. - Zadzwoń pod numer
start()
, aby aktywować serwer RPC dla naszej usługi. - Poczekaj, aż usługa zostanie zatrzymana przez wywołanie funkcji
blockUntilShutdown()
.
public static void main(String[] args) throws Exception {
RouteGuideServer server = new RouteGuideServer(8980);
server.start();
server.blockUntilShutdown();
}
6. Tworzenie klienta
W tej sekcji zajmiemy się tworzeniem klienta dla usługi RouteGuide
.
Utwórz obiekt zastępczy
Aby wywoływać metody usługi, musimy najpierw utworzyć stub. Istnieją 2 rodzaje atrap, ale w tym samouczku potrzebujemy tylko atrapy blokującej. Wyróżniamy 2 rodzaje:
- blokujący/synchroniczny element zastępczy, który wykonuje wywołanie RPC i czeka na odpowiedź serwera, a następnie zwraca odpowiedź lub zgłasza wyjątek;
- nieblokujący/asynchroniczny stub, który wykonuje nieblokujące wywołania serwera, a odpowiedź jest zwracana asynchronicznie. Niektóre typy połączeń strumieniowych można wykonywać tylko za pomocą asynchronicznego elementu zastępczego.
Najpierw musimy utworzyć kanał gRPC, a potem użyć go do utworzenia naszego stuba.
Mogliśmy użyć bezpośrednio ManagedChannelBuilder
, aby utworzyć kanał.
ManagedChannelBuilder.forAddress(host, port).usePlaintext().build
Użyjmy jednak metody narzędziowej, która przyjmuje ciąg znaków z symbolem hostname:port
.
Grpc.newChannelBuilder(target, InsecureChannelCredentials.create()).build();
Teraz możemy użyć kanału do utworzenia bloku zastępczego. W tym ćwiczeniu w Codelabs mamy tylko blokujące wywołania RPC, więc używamy metody newBlockingStub
udostępnionej w klasie RouteGuideGrpc
wygenerowanej na podstawie pliku .proto
.
blockingStub = RouteGuideGrpc.newBlockingStub(channel);
Wywoływanie metod usługi
Zobaczmy teraz, jak wywołujemy metody usługi.
Simple RPC
Wywołanie prostego RPC GetFeature
jest prawie tak proste jak wywołanie metody lokalnej.
Tworzymy i wypełniamy obiekt bufora protokołu żądania (w naszym przypadku Point
), przekazujemy go do metody getFeature()
w naszym blokującym stubie i otrzymujemy odpowiedź Feature
.
Jeśli wystąpi błąd, jest on kodowany jako Status
, który możemy uzyskać z StatusRuntimeException
.
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;
}
Kod standardowy rejestruje wiadomość zawierającą treści w zależności od tego, czy w określonym punkcie występuje funkcja.
7. Spróbuj!
- W katalogu
start_here
uruchom to polecenie:
$ ./gradlew installDist
Spowoduje to skompilowanie kodu, spakowanie go w plik JAR i utworzenie skryptów, które uruchamiają przykład. Zostaną one utworzone w katalogu build/install/start_here/bin/
. Są to skrypty route-guide-server
i route-guide-client
.
Przed uruchomieniem klienta serwer musi być włączony.
- Uruchom serwer:
$ ./build/install/start_here/bin/route-guide-server
- Uruchom klienta:
$ ./build/install/start_here/bin/route-guide-client
Zobaczysz dane wyjściowe podobne do tych (sygnatury czasowe zostały pominięte dla przejrzystości):
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. Co dalej?
- Dowiedz się, jak działa gRPC, z artykułów Wprowadzenie do gRPC i Podstawowe pojęcia.
- Zapoznaj się z samouczkiem dotyczącym podstaw.
- Zapoznaj się z dokumentacją interfejsu API.