Wprowadzenie

Grpc to nowoczesna platforma RPC o wysokiej wydajności, która może działać w dowolnym środowisku. Projekt został stworzony na licencji open-source przez firmę Google. Jest częścią Cloud Native Computation Foundation (CNCF). Z użyciem gRPC aplikacja kliencka może bezpośrednio wywołać metodę w aplikacji serwera na innej maszynie, tak jakby była obiektem lokalnym. Podobnie jak w wielu systemach RPC, gRPC polega na definiowaniu usług, określaniu metod, które mogą być wywoływane zdalnie, wraz z parametrami żądań i typami zwracanymi. Klient oraz serwer gRPC mogą być uruchamiane i komunikować ze sobą w różnych środowiskach. Dodatkowym atutem jest niezależność od języka. Kod serwera oraz klienta mogą być implementowane w dowolnym języku programowania wspieranym przez gRPC np. możemy napisać kod serwera z wykorzystaniem języka Java oraz kod klienta np. w językach Go, Python lub innym wspieranym przez gRPC.

Dlaczego warto używać gRPC

Obecnie systemy informatyczne projektowane są w architekturze mikroserwisów. Mikroserwisy najczęściej komunikują się z wykorzystaniem REST bazującym na protokole HTTP/1.1, wymieniając dane z formacie JSON. REST jest łatwy, wygodny oraz szeroko używany do wymiany informacji między mikroserwisami. Posiada jednak następujące problemy:

  • wykorzystuje tekstowy protokół HTTP/1.1, wymiana informacji na podstawie dużych ładunków JSON,
  • HTTP jest protokołem bezstanowym, dodatkowe informacje przekazywane są w nieskompresowanych nagłówkach,
  • bazując na protokole HTTP/1.1. wysyłamy żądanie, a następnie oczekujemy na odpowiedź. Dopóki nie otrzymamy odpowiedzi, nie możemy wysłać kolejnego żądania.

Wszystkie wymienione problemy mogą skutkować znacznym spadkiem wydajności naszych systemów. REST jest bardzo dobry do komunikacji pomiędzy przeglądarką a serwerem, natomiast od komunikacji między mikroserwisami potrzebujemy czegoś bardziej wydajnego. gRPC jest szybszy niż REST. Wzrost wydajności osiągamy, dzięki wbudowanym narzędziom, z których wewnętrznie korzysta gRPC:

  • Protocol Buffers
  • HTTP/2

Protocol Buffers

Rozpoczęcie przygody z gRPC, a wiec definiowania zawartości obiektów żądań oraz odpowiedzi, wymaga zapoznania się z Protocol Buffers. Jest to niezależny od języka, rozszerzalny mechanizm serializacji danych strukturalnych pomiędzy usługami. Wykorzystując Protocol Buffers definiujemy kontrakt komunikacji pomiędzy dwoma systemami w postaci pliku o rozszerzeniu .proto, na podstawie którego możemy wygenerować kod w wybranym języku programowania wspieranym przez gRPC. Zalety użycia Protocol Buffers:

  • łatwość tworzenia API,
  • definicja API niezależna od implementacji,
  • duże ilości kodu możliwe do wygenerowania dla wielu języków programowania wspieranych przez gRPC,
  • dane przesyłane są w postaci binarnej, co daje wysoką wydajność przysyłania, odbierania danych,
  • mała intensywność procesora podczas parsowania oraz odparsowywania danych

HTTP/2

GRPC wykorzystuje protokół HTTP/2 jako szkielet komunikacji, który jest nowym standardem do komunikacji w Internecie. Z wykorzystaniem HTTP/2 klient oraz serwer mogą wysyłać wiadomości równolegle z wykorzystaniem tego samego połączenia TCP w przeciwieństwie do HTTP/1.1, który tworzy nowe połączenie dla każdego żądania do serwera, co efektywnie zmniejsza opóźnienia transmisji. HTTP/2 wprowadza binarny format komunikacji oraz zaawansowaną kompresję nagłówków.   HTTP/1.1 vs HTTP/2

HTTP/1.1 HTTP/2
Format tekstowy Format binarny
Nagłówki przesyłane jako zwykły tekst Skompresowane nagłówki
Żądanie oraz odpowiedź w jednym połączeniu TCP To samo połączenie TCP można ponownie wykorzystać, wysyłając kilka żądań

Typy definicji API gRPC

Wykorzystując gRPC możemy definiować 4 typy API:

  • Unary – klasyczny model komunikacji żądanie -> odpowiedź,
  • Server Streaming – klient wysyła żądanie do serwera, a następnie może otrzymać strumień odpowiedzi,
  • Client Streaming – klient otwiera strumień żądań oraz wysyła kilka wiadomości, a serwer po zakończeniu zwraca odpowiedź,
  • Bi Directional Streaming – serwer oraz klient mogą wysłać wiele żądań, odpowiedzi asynchronicznie.

Przykład implementacji

Implementację stworzono w języku programowania Java z wykorzystaniem frameworka SpringBoot. Projekt został podzielony na następujące moduły:

1. proto-module

Zawiera plik calculator.proto, który zawiera definicję API kalkulatora – dodawania dwóch liczb.

syntax = „proto3”;

package calculator;
option java_package = „com.proto.calculator”;
option java_multiple_files = true;

message SumRequest {
  int64 first_number = 1;
  int64 second_number = 2;
}

message SumResponse {
  int64 result = 1;
}

service CalculatorService {
  rpc Sum(SumRequest) returns (SumResponse) {};
}

Zdefiniowane obiekty:

  • SumRequest – obiekt żądania, przyjmuje dwie liczby typu long,
  • SumResponse – obiekt odpowiedzi, zawiera rezultat dodawania,
  • CalculatorService – definiuje metodę Sum, która przyjmuję SumRequest i zwraca obiekt SumRespone.

2. service-module

Aplikacja serwera, która definiuje logikę kalkulatora. Po stronie serwera należy zaimplementować metodę sum, rozszerzając konkretną klasę oraz nadpisując implementację wybranej metody.

@GrpcService
public class CalculatorService extends CalculatorServiceGrpc.CalculatorServiceImplBase {

  @Override
  public void sum(SumRequest request, StreamObserver responseObserver) {
    long firstNumber = request.getFirstNumber();
    long secondNumber = request.getSecondNumber();

    SumResponse response = SumResponse.newBuilder()
    .setResult(firstNumber + secondNumber)
    .build();

    responseObserver.onNext(response);
    responseObserver.onCompleted();
  }
}

Dodatkowo w pliku application.properties należy zdefiniować port serwera:

grpc.server.port=6565

3. client-module

Ostatnim krokiem jest zdefiniowanie aplikacji klienta. Wygenerowany kod dostarcza wzorca Builder, z użyciem którego możemy stworzyć obiekt SumRequest, a następnie przekazać jako parametr wywołania metody.

@Service 
public class CalculatorService { 

  @GrpcClient(„calculator-service”) 
  private CalculatorServiceGrpc.CalculatorServiceBlockingStub blockingStub; 

  public long sum(long firstNumber, long secondNumber) { 
    SumRequest sumRequest = SumRequest.newBuilder() 
    .setFirstNumber(firstNumber) 
    .setSecondNumber(secondNumber) 
    .build(); 
    SumResponse sum = blockingStub.sum(sumRequest); 
    return sum.getResult(); 
  } 
}

W pliku application.yaml zdefiniowano szczegóły dostępu serwera gRPC, do którego odwołuje się aplikacja klienta.

server:
  port: 8080
grpc:
  client:
    calculator-service:
      address: static://localhost:6565
      negotiationType: plaintext

Podsumowanie

W artykule przedstawiono nowy sposób komunikacji między mikroserwisami – gRPC. Przedstawiono modele API jakie mogą zostać stworzone z jego użyciem. Przykładowa implementacja przedstawia typowy model komunikacji żądanie – odpowiedź (unary api), natomiast nic nie stoi na przeszkodzie, aby w podobny sposób zaimplementować inne modele API. Kompletny kod źródłowy dostępny tutaj