Вступ до gRPC
У середовищі мікросервісів, де сервіси повинні ефективно взаємодіяти, gRPC (gRPC Remote Procedure Call) — потужний фреймворк, який забезпечує комунікацію з низькою затримкою та високою продуктивністю між розподіленими системами. Заснований на HTTP/2 та Protocol Buffers (protobuf), gRPC дозволяє мікросервісам викликати методи інших сервісів так, ніби це локальні методи.
1. Чому варто використовувати gRPC в мікросервісах?
Переваги gRPC порівняно з REST:
- Ефективна серіалізація: gRPC використовує Protocol Buffers (Protobuf), що дозволяє серіалізувати дані швидше та в меншому бінарному форматі порівняно з JSON.
- Підтримка стрімінгу: gRPC підтримує унарні запити, серверний стрімінг, клієнтський стрімінг та двосторонній стрімінг.
- Підтримка HTTP/2: gRPC використовує HTTP/2, що забезпечує мультиплексування, зменшену затримку та стиснення заголовків.
- Сильно типізовані контракти: Protobuf забезпечує сильну типізацію та валідацію контрактів.
- Крос-платформна підтримка: gRPC підтримує кілька мов програмування (Java, Python, Go тощо), що робить його ідеальним для середовищ з багатьма мовами програмування (polyglot microservices environments).
2. Ключові концепції gRPC
- Protocol Buffers (Protobuf): Мова схеми для визначення структури запитів та відповідей.
- Визначення сервісу: Визначає RPC методи у файлі .proto.
- Унарний RPC: Один запит, на який йде одна відповідь.
- Серверний стрімінг: Сервер відправляє потік відповідей клієнту.
- Клієнтський стрімінг: Клієнт відправляє потік запитів на сервер.
- Двосторонній стрімінг: І клієнт, і сервер відправляють потоки даних один одному.
3. Приклад використання gRPC в середовищі мікросервісів
Варіант використання: Система управління користувачами
Ми створимо два мікросервіси:
- User Service: Керує інформацією про користувачів.
- 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
- Унарний RPC: Один запит → Одна відповідь.
- Серверний стрімінг RPC: Один запит → Потік відповідей.
- Клієнтський стрімінг RPC: Потік запитів → Одна відповідь.
4.
Двосторонній стрімінг RPC: Потік запитів ↔ Потік відповідей.
7. Переваги gRPC у мікросервісах
- Висока продуктивність: Швидший і компактніший за REST завдяки бінарній серіалізації.
- Стрімінг: Підтримує з'єднання з тривалим життєвим циклом і двосторонню комунікацію.
- Підтримка різних мов програмування: gRPC підтримує кілька мов програмування.
- Сильне типізування: Protobuf забезпечує сувору перевірку контрактів.
- Вбудована генерація коду: Автоматичне генерування клієнтських і серверних стубів.
8. Виклики при використанні gRPC
- Більший поріг навчання: Потрібно розуміти protobuf і специфічні шаблони gRPC.
- Обмежена підтримка в браузерах: gRPC не підтримується в браузерах за замовчуванням.
- Налагодження: Бінарні дані складніше налагоджувати, ніж людськочитний JSON.
Висновок
gRPC є потужним вибором для середовищ мікросервісів, де критично важливі продуктивність, низька затримка та підтримка різних мов програмування. Це забезпечує ефективну комунікацію за допомогою таких функцій, як HTTP/2, стрімінг і сильні контракти. Хоча це додає складності порівняно з REST, переваги gRPC роблять його ідеальним для великих систем, де важлива ефективність мережі.
Перекладено з: gRPC Logic in Microservices Environment