Пример gRPC в Java
Google выпустила gRPC в качестве новой платформы с открытым исходным кодом в 2015 году, с тех пор она изменила способ обмена данными между сервисами в нескольких центрах обработки данных. Если вы хотите создать действительно независимый от языка микросервис, который будет надежным, высокопроизводительным и легко масштабируемым в распределенной среде. Тогда вам определенно стоит выбрать gRPC.
Что такое gRPC?
В gRPC клиентское приложение может напрямую вызывать методы сервера, как если бы они были локальными для клиента. Это упрощает задачу разработчика по созданию распределенных сервисов.
Проще говоря, вы создаете прототип службы с ее запросом и ответом, разделяете его между сервером и клиентом, и они генерируют соответствующие методы и реализуют их, теперь клиент вызывает метод с запросом, который передается на сервер, метод выполняется сервер и возвращает ответ клиенту, но на стороне клиента это будет выглядеть как работа с локальным методом.
В реальном мире самой большой проблемой является работа с базами данных в микросервисах, и наличие распределенного уровня персистентности может творить чудеса.
Основная особенность
Некоторые важные особенности gRPC:
- Он поддерживает более 10 языков, вы можете написать свой сервис на Java, и даже клиент Python сможет получить к нему доступ.
- Он использует протокол http 2, который очень эффективен для работы с большими объемами данных.
- Он предоставляет очень простое и понятное объявление сервиса.
- Он поддерживает двунаправленные потоки.
- Он предоставляет множество плагинов, таких как аутентификация, трассировка, балансировка нагрузки и проверка работоспособности.
Зависимости Maven
Давайте добавим зависимости grpc:
<dependencies> <dependency> <groupId>io.grpc</groupId> <artifactId>grpc-all</artifactId> <version>1.18.0</version> </dependency> </dependencies>
Определить сервис
Давайте создадим простой сервис калькулятора, чтобы добавить два значения:
Мы начнем с определения метода с параметрами и возвращаемым значением, но это должен быть прототип.
Мы делаем это, создавая файл .proto с использованием буферов протокола , которые используются для описания сообщений.
Итак, мы создаем файл Calculator.proto в /src/main/proto.
syntax = "proto3"; //this directs compiler to use version 3 option java_multiple_files = true; //we want generate different java files after compilation. By default all the classes are generated in a single file. package in.kuros.grpc; // defines the package structure of generated classes. //request payload message OperandRequest { int32 X = 1; // message with type, along with tag: 1 int32 Y = 2; // tag: 2 } //response payload message AddResponse { int64 result = 1; } // service contract service Calculator { rpc add(OperandRequest) returns (AddResponse); // method name: add, parameter type: OperandRequest, response type: AddResponse }
Каждому атрибуту необходимо присвоить уникальный номер, называемый тегом. Этот тег используется буфером протокола для представления атрибута вместо использования имени атрибута. Таким образом, в отличие от JSON , где мы каждый раз передавали имя атрибута X , буфер протокола будет использовать число 1 для представления X. Определение полезной нагрузки ответа аналогично запросу.
Итак, в конце после генерации у нас будет три класса (с точки зрения Java): Operands, AddResponse и Calculator, который принимает операнды и возвращает addResponse;
Генерация файлов классов для Java.
Теперь мы передаем файл HelloService.proto в протокол компилятора буфера протокола для создания файлов Java. Есть несколько способов вызвать это.
Компилятор буфера протокола
Загрузите компилятор и следуйте readme.txt.
Используйте команду для генерации кода
$ protoc -I=$SRC_DIR --java_out=$DST_DIR $SRC_DIR/proto/calculator.proto
Плагин компилятора Maven
Вы не хотите каждый раз выполнять команду для генерации кода, поэтому мы будем использовать плагин maven для выполнения во время сборки.
<build> <extensions> <extension> <groupId>kr.motd.maven</groupId> <artifactId>os-maven-plugin</artifactId> <version>1.6.1</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.5.1:exe:${os.detected.classifier} </protocArtifact> <pluginId>grpc-java</pluginId> <pluginArtifact> io.grpc:protoc-gen-grpc-java:1.18.0:exe:${os.detected.classifier} </pluginArtifact> </configuration> <executions> <execution> <goals> <goal>compile</goal> <goal>compile-custom</goal> </goals> </execution> </executions> </plugin> </plugins> </build>
Расширение / плагин os-maven- plugin генерирует различные полезные свойства проекта, зависящие от платформы, такие как ${os.detected.classifier}
Сгенерированные файлы
После запуска генерации некоторые ключевые файлы будут созданы в папке /target/generated-sources/protobuf.
- OperandRequest.java — содержит определение OperandRequest.
- AddResponse.java — содержит определение AddResponse.
- CalculatorGrpc.java — содержит абстрактный внутренний класс CalculatorImplBase, который предоставляет оболочку реализации.
Реализация на стороне сервера
Теперь пришло время написать логику сложения на стороне сервера. поэтому мы создадим класс реализации CalculatorImpl , который расширит CalculatorImplBase и обеспечит реализацию.
import in.kuros.grpc.AddResponse; import in.kuros.grpc.CalculatorGrpc.CalculatorImplBase; import in.kuros.grpc.OperandRequest; import io.grpc.stub.StreamObserver; public class CalculatorImpl extends CalculatorImplBase { @Override public void add(final OperandRequest request, final StreamObserver<AddResponse> responseObserver) { final long sum = request.getX() + request.getY(); final AddResponse addResponse = AddResponse .newBuilder() .setResult(sum) .build(); try { System.out.println("Sleeping to 5 sec"); Thread.sleep(5 * 1000); } catch (InterruptedException e) { e.printStackTrace(); } responseObserver.onNext(addResponse); responseObserver.onCompleted(); } }
Мы предоставили 5-секундный режим сна для имитации длительных операций.
Запустить сервер gRPC
Далее нам нужно запустить сервер gRPC для прослушивания входящих запросов:
import io.grpc.Server; import io.grpc.ServerBuilder; public class GrpcServer { public static void main(String[] args) throws Exception { final Server server = ServerBuilder.forPort(8080) .addService(new CalculatorImpl()) .build(); server.start(); server.awaitTermination(); } }
Итак, здесь мы создали подачу для прослушивания входящего запроса на порту 8080, зарегистрировали наш сервис калькулятора. В нашем примере мы вызовем awaitTermination(), чтобы сервер работал на переднем плане, блокируя приглашение.
Создание клиента
Сначала нам нужно создать канал gRPC для нашей заглушки, указав адрес сервера и порт, к которому мы хотим подключиться.
Для создания канала мы используем ManagedChannelBuilder .
ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost", 8080) .usePlaintext() .build();
Теперь мы можем использовать канал для создания наших заглушек с помощью методов newStub и newBlockingStub, предоставленных в классе RouteGuideGrpc, который мы сгенерировали из нашего .proto.
Синхронизация вызовов
Создаем блокирующую заглушку
final CalculatorGrpc.CalculatorBlockingStub blockingStub = CalculatorGrpc.newBlockingStub(channel); System.out.println("Making blocking call"); final AddResponse blockResponse = blockingStub.add(OperandRequest.newBuilder().setX(10).setY(20).build()); System.out.println("blocking call result: " + blockResponse.getResult());
Выполнение асинхронных вызовов
Мы используем метод newStub для выполнения вызовов aysnc, но нам нужно предоставить ему реализацию StreamObserver.
final CalculatorGrpc.CalculatorStub asyncStub = CalculatorGrpc.newStub(channel); StreamObserver<AddResponse> streamObserver = new StreamObserver<AddResponse>() { public void onNext(final AddResponse addResponse) { System.out.println("async call result: " + addResponse.getResult()); } public void onError(final Throwable throwable) { System.out.println(throwable); } public void onCompleted() { System.out.println("Async call stopped listening"); } }; System.out.println("Making blocking call"); asyncStub.add(OperandRequest.newBuilder().setX(10).setY(20).build(), streamObserver); System.out.println("Async invoked" + blockResponse.getResult()); channel.awaitTermination(10, TimeUnit.SECONDS);
полный код клиента:
import in.kuros.grpc.AddResponse; import in.kuros.grpc.CalculatorGrpc; import in.kuros.grpc.OperandRequest; import io.grpc.ManagedChannel; import io.grpc.ManagedChannelBuilder; import io.grpc.stub.StreamObserver; import java.util.concurrent.TimeUnit; public class GrpcClient { public static void main(String[] args) throws InterruptedException { ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost", 8080) .usePlaintext() .build(); final CalculatorGrpc.CalculatorBlockingStub blockingStub = CalculatorGrpc.newBlockingStub(channel); System.out.println("Making blocking call"); final AddResponse blockResponse = blockingStub.add(OperandRequest.newBuilder().setX(10).setY(20).build()); System.out.println("blocking call result: " + blockResponse.getResult()); final CalculatorGrpc.CalculatorStub asyncStub = CalculatorGrpc.newStub(channel); StreamObserver<AddResponse> streamObserver = new StreamObserver<AddResponse>() { public void onNext(final AddResponse addResponse) { System.out.println("async call result: " + addResponse.getResult()); } public void onError(final Throwable throwable) { System.out.println(throwable); } public void onCompleted() { System.out.println("Async call stopped listening"); } }; System.out.println("Making blocking call"); asyncStub.add(OperandRequest.newBuilder().setX(10).setY(20).build(), streamObserver); System.out.println("Async invoked" + blockResponse.getResult()); channel.awaitTermination(10, TimeUnit.SECONDS); } }
Далее нам нужно создать заглушку, которую мы будем использовать для фактического удаленного вызова hello(). Заглушка — это основной способ взаимодействия клиентов с сервером . При использовании заглушек автоматического создания класс заглушки будет иметь конструкторы для переноса канала.
Заключение
В этом посте мы узнали, как работать с сервером/клиентом gRPC в Java.