Логіка gRPC в середовищі мікросервісів

Вступ до gRPC

У середовищі мікросервісів, де сервіси повинні ефективно взаємодіяти, gRPC (gRPC Remote Procedure Call) — потужний фреймворк, який забезпечує комунікацію з низькою затримкою та високою продуктивністю між розподіленими системами. Заснований на HTTP/2 та Protocol Buffers (protobuf), gRPC дозволяє мікросервісам викликати методи інших сервісів так, ніби це локальні методи.

pic

1. Чому варто використовувати gRPC в мікросервісах?

Переваги gRPC порівняно з REST:

  1. Ефективна серіалізація: gRPC використовує Protocol Buffers (Protobuf), що дозволяє серіалізувати дані швидше та в меншому бінарному форматі порівняно з JSON.
  2. Підтримка стрімінгу: gRPC підтримує унарні запити, серверний стрімінг, клієнтський стрімінг та двосторонній стрімінг.
  3. Підтримка HTTP/2: gRPC використовує HTTP/2, що забезпечує мультиплексування, зменшену затримку та стиснення заголовків.
  4. Сильно типізовані контракти: Protobuf забезпечує сильну типізацію та валідацію контрактів.
  5. Крос-платформна підтримка: gRPC підтримує кілька мов програмування (Java, Python, Go тощо), що робить його ідеальним для середовищ з багатьма мовами програмування (polyglot microservices environments).

2. Ключові концепції gRPC

  1. Protocol Buffers (Protobuf): Мова схеми для визначення структури запитів та відповідей.
  2. Визначення сервісу: Визначає RPC методи у файлі .proto.
  3. Унарний RPC: Один запит, на який йде одна відповідь.
  4. Серверний стрімінг: Сервер відправляє потік відповідей клієнту.
  5. Клієнтський стрімінг: Клієнт відправляє потік запитів на сервер.
  6. Двосторонній стрімінг: І клієнт, і сервер відправляють потоки даних один одному.

3. Приклад використання gRPC в середовищі мікросервісів

Варіант використання: Система управління користувачами

Ми створимо два мікросервіси:

  1. User Service: Керує інформацією про користувачів.
  2. Notification Service: Відправляє сповіщення користувачам.

1. Опис Protobuf (user.proto)

Цей файл .proto визначає gRPC сервіс та повідомлення, які обмінюються.

syntax = "proto3";  

package user;  

service UserService {  
 // Унарний RPC  
 rpc GetUser (UserRequest) returns (UserResponse);  

 // Серверний стрімінг RPC  
 rpc ListUsers (Empty) returns (stream UserResponse);  

 // Клієнтський стрімінг RPC  
 rpc AddUsers (stream UserRequest) returns (AddUsersResponse);  

 // Двосторонній стрімінг RPC  
 rpc Chat (stream ChatMessage) returns (stream ChatMessage);  
}  

message UserRequest {  
 string user_id = 1;  
}  

message UserResponse {  
 string user_id = 1;  
 string name = 2;  
 int32 age = 3;  
}  

message AddUsersResponse {  
 int32 success_count = 1;  
}  

message ChatMessage {  
 string sender = 1;  
 string message = 2;  
}  

message Empty {}

2. Генерація Java коду з Protobuf

Встановіть protoc (компілятор Protocol Buffers) та додайте плагін grpc-java:

protoc --java_out=src/main/java --grpc-java_out=src/main/java --proto_path=src/main/proto user.proto

# Реалізація на стороні сервера

## Реалізація UserService (Java)

package com.atk.userservice;

import io.grpc.stub.StreamObserver;
import user.UserServiceGrpc;
import user.UserRequest;
import user.UserResponse;
import user.AddUsersResponse;
import user.ChatMessage;
import user.Empty;

import java.util.ArrayList;
import java.util.List;

public class UserServiceImpl extends UserServiceGrpc.UserServiceImplBase {

private final List users = new ArrayList<>();

// Унарний RPC
@Override
public void getUser(UserRequest request, StreamObserver responseObserver) {
UserResponse user = users.stream()
.filter(u -> u.getUserId().equals(request.getUserId()))
.findFirst()
.orElse(UserResponse.newBuilder().setUserId("0").setName("Unknown").setAge(0).build());

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

// Серверний стрімінг RPC
@Override
public void listUsers(Empty request, StreamObserver responseObserver) {
for (UserResponse user : users) {
responseObserver.onNext(user);
}
responseObserver.onCompleted();
}

// Клієнтський стрімінг RPC
@Override
public StreamObserver addUsers(StreamObserver responseObserver) {
return new StreamObserver<>() {
int count = 0;

@Override
public void onNext(UserRequest request) {
users.add(UserResponse.newBuilder()
.setUserId(request.getUserId())
.setName("User " + request.getUserId())
.setAge(25)
.build());
count++;
}

@Override
public void onError(Throwable t) {
System.out.println("Сталася помилка: " + t.getMessage());
}

@Override
public void onCompleted() {
responseObserver.onNext(AddUsersResponse.newBuilder().setSuccessCount(count).build());
responseObserver.onCompleted();
}
};
}

// Двосторонній стрімінг RPC
@Override
public StreamObserver chat(StreamObserver responseObserver) {
return new StreamObserver<>() {
@Override
public void onNext(ChatMessage message) {
System.out.println("Отримано повідомлення: " + message.getMessage());
responseObserver.onNext(ChatMessage.newBuilder()
.setSender("Сервер")
.setMessage("Підтверджено: " + message.getMessage())
.build());
}

@Override
public void onError(Throwable t) {
System.out.println("Помилка чату: " + t.getMessage());
}

@Override
public void onCompleted() {
responseObserver.onCompleted();
}
};
}
```

4. Налаштування gRPC сервера

package com.example.userservice;  

import io.grpc.Server;  
import io.grpc.ServerBuilder;  

public class UserServer {  
 public static void main(String[] args) throws Exception {  
 Server server = ServerBuilder.forPort(9090)  
 .addService(new UserServiceImpl())  
 .build();  

 System.out.println("Запуск gRPC сервера...");  
 server.start();  
 server.awaitTermination();  
 }  
}

5. Реалізація клієнта

package com.example.userclient;  

import io.grpc.ManagedChannel;  
import io.grpc.ManagedChannelBuilder;  
import user.UserServiceGrpc;  
import user.UserRequest;  
import user.UserResponse;  

public class UserClient {  
 public static void main(String[] args) {  
 ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost", 9090)  
 .usePlaintext()  
 .build();  

 UserServiceGrpc.UserServiceBlockingStub stub = UserServiceGrpc.newBlockingStub(channel);  

 // Унарний виклик  
 UserResponse response = stub.getUser(UserRequest.newBuilder().setUserId("1").build());  
 System.out.println("Отримано користувача: " + response.getName());  

 channel.shutdown();  
 }  
}

6. Типи комунікації в gRPC

  1. Унарний RPC: Один запит → Одна відповідь.
  2. Серверний стрімінг RPC: Один запит → Потік відповідей.
  3. Клієнтський стрімінг RPC: Потік запитів → Одна відповідь.
    4.
    Двосторонній стрімінг RPC: Потік запитів ↔ Потік відповідей.

7. Переваги gRPC у мікросервісах

  1. Висока продуктивність: Швидший і компактніший за REST завдяки бінарній серіалізації.
  2. Стрімінг: Підтримує з'єднання з тривалим життєвим циклом і двосторонню комунікацію.
  3. Підтримка різних мов програмування: gRPC підтримує кілька мов програмування.
  4. Сильне типізування: Protobuf забезпечує сувору перевірку контрактів.
  5. Вбудована генерація коду: Автоматичне генерування клієнтських і серверних стубів.

8. Виклики при використанні gRPC

  1. Більший поріг навчання: Потрібно розуміти protobuf і специфічні шаблони gRPC.
  2. Обмежена підтримка в браузерах: gRPC не підтримується в браузерах за замовчуванням.
  3. Налагодження: Бінарні дані складніше налагоджувати, ніж людськочитний JSON.

Висновок

gRPC є потужним вибором для середовищ мікросервісів, де критично важливі продуктивність, низька затримка та підтримка різних мов програмування. Це забезпечує ефективну комунікацію за допомогою таких функцій, як HTTP/2, стрімінг і сильні контракти. Хоча це додає складності порівняно з REST, переваги gRPC роблять його ідеальним для великих систем, де важлива ефективність мережі.

Перекладено з: gRPC Logic in Microservices Environment

Leave a Reply

Your email address will not be published. Required fields are marked *