• ¡Obtenga la seguridad de la aplicación de la manera correcta! Detectar, proteger, monitorear, acelerar y más ...
  • Exploremos cómo implementar gRPC en Java.

    gRPC (Llamada a procedimiento remoto de Google): gRPC es una arquitectura RPC de código abierto desarrollada por Google para permitir la comunicación de alta velocidad entre microservicios. gRPC permite a los desarrolladores integrar servicios escritos en diferentes lenguajes. gRPC usa el formato de mensajería Protobuf (Protocol Buffers), un formato de mensajería altamente eficiente y empaquetado para serializar datos estructurados.

    Para algunos casos de uso, la API de gRPC puede ser más eficiente que la REST API.

    Intentemos escribir un servidor en gRPC. Primero, necesitamos escribir varios .proto archivos que describen servicios y modelos (DTO). Para un servidor simple, usaremos ProfileService y ProfileDescriptor.

    ProfileService tiene este aspecto:

    syntax = "proto3";
    package com.deft.grpc;
    import "google/protobuf/empty.proto";
    import "profile_descriptor.proto";
    service ProfileService {
      rpc GetCurrentProfile (google.protobuf.Empty) returns (ProfileDescriptor) {}
      rpc clientStream (stream ProfileDescriptor) returns (google.protobuf.Empty) {}
      rpc serverStream (google.protobuf.Empty) returns (stream ProfileDescriptor) {}
      rpc biDirectionalStream (stream ProfileDescriptor) returns (stream 	ProfileDescriptor) {}
    }
    

    gRPC admite una variedad de opciones de comunicación cliente-servidor. Los desglosaremos todos:

    • Llamada normal al servidor: solicitud / respuesta.
    • Transmisión de cliente a servidor.
    • Transmisión de servidor a cliente.
    • Y, por supuesto, el flujo bidireccional.

    El servicio ProfileService utiliza ProfileDescriptor, que se especifica en la sección de importación:

    syntax = "proto3";
    package com.deft.grpc;
    message ProfileDescriptor {
      int64 profile_id = 1;
      string name = 2;
    }
    
    • int64 es Long para Java. Deje que la identificación del perfil pertenezca.
    • Cordón - al igual que en Java, esta es una variable de cadena.

    Puede usar Gradle o maven para construir el proyecto. Es más conveniente para mí usar maven. Y además estará el código usando maven. Esto es lo suficientemente importante como para decirlo porque para Gradle, la futura generación de .proto será ligeramente diferente y el archivo de compilación deberá configurarse de manera diferente. Para escribir un servidor gRPC simple, solo necesitamos una dependencia:

    <dependency>
        <groupId>io.github.lognet</groupId>
        <artifactId>grpc-spring-boot-starter</artifactId>
        <version>4.5.4</version>
    </dependency>
    

    Es simplemente increíble. Este entrante hace una gran cantidad de trabajo para nosotros.

    El proyecto que crearemos se verá así:

    Necesitamos GrpcServerApplication para iniciar la aplicación Spring Boot. Y GrpcProfileService, que implementará métodos del .proto Servicio. Para usar protocolos y generar clases a partir de archivos .proto escritos, agregue protobuf-maven-plugin a pom.xml. La sección de construcción se verá así:

    <build>
            <extensions>
                <extension>
                    <groupId>kr.motd.maven</groupId>
                    <artifactId>os-maven-plugin</artifactId>
                    <version>1.6.2</version>
                </extension>
            </extensions>
            <plugins>
                <plugin>
                    <groupId>org.xolstice.maven.plugins</groupId>
                    <artifactId>protobuf-maven-plugin</artifactId>
                    <version>0.6.1</version>
                    <configuration>
                        <protoSourceRoot>${project.basedir}/src/main/proto</protoSourceRoot>
                        <outputDirectory>${basedir}/target/generated-sources/grpc-java</outputDirectory>
                        <protocArtifact>com.google.protobuf:protoc:3.12.0:exe:${os.detected.classifier}</protocArtifact>
                        <pluginId>grpc-java</pluginId>
                        <pluginArtifact>io.grpc:protoc-gen-grpc-java:1.38.0:exe:${os.detected.classifier}</pluginArtifact>
                        <clearOutputDirectory>false</clearOutputDirectory>
                    </configuration>
                    <executions>
                        <execution>
                            <goals>
                                <goal>compile</goal>
                                <goal>compile-custom</goal>
                            </goals>
                        </execution>
                    </executions>
                </plugin>
            </plugins>
        </build>
    
    • protoSourceRoot - especificando el directorio donde se encuentran los archivos .proto.
    • directorio de salida - seleccione el directorio donde se generarán los archivos.
    • clearOutputDirectory - una bandera que indica no borrar los archivos generados.

    En esta etapa, puede construir un proyecto. A continuación, debe ir a la carpeta que especificamos en el directorio de salida. Los archivos generados estarán allí. Ahora puedes implementar gradualmente GrpcProfileService.

    La declaración de clase se verá así:

    @GRpcService
    public class GrpcProfileService extends ProfileServiceGrpc.ProfileServiceImplBase
    

    GRpcService anotación: marca la clase como un bean grpc-service.

    Dado que heredamos nuestro servicio de ProfileServiceGrpc, ProfileServiceImplBase, podemos anular los métodos de la clase padre. El primer método que anularemos es getCurrentProfile:

        @Override
        public void getCurrentProfile(Empty request, StreamObserver<ProfileDescriptorOuterClass.ProfileDescriptor> responseObserver) {
            System.out.println("getCurrentProfile");
            responseObserver.onNext(ProfileDescriptorOuterClass.ProfileDescriptor
                    .newBuilder()
                    .setProfileId(1)
                    .setName("test")
                    .build());
            responseObserver.onCompleted();
        }
    

    Para responder al cliente, debe llamar al En el siguiente en el StreamObserver pasado. Después de enviar la respuesta, envíe una señal al cliente de que el servidor ha terminado de funcionar. onCompleted. Al enviar una solicitud al servidor getCurrentProfile, la respuesta será:

    {
      "profile_id": "1",
      "name": "test"
    }
    

    A continuación, echemos un vistazo a la transmisión del servidor. Con este enfoque de mensajería, el cliente envía una solicitud al servidor, el servidor responde al cliente con un flujo de mensajes. Por ejemplo, envía cinco solicitudes en un bucle. Cuando se completa el envío, el servidor envía un mensaje al cliente sobre la finalización exitosa de la transmisión.

    El método de transmisión del servidor anulado se verá así:

    @Override
        public void serverStream(Empty request, StreamObserver<ProfileDescriptorOuterClass.ProfileDescriptor> responseObserver) {
            for (int i = 0; i < 5; i++) {
                responseObserver.onNext(ProfileDescriptorOuterClass.ProfileDescriptor
                        .newBuilder()
                        .setProfileId(i)
                        .build());
            }
            responseObserver.onCompleted();
        }
    

    Así, el cliente recibirá cinco mensajes con un ProfileId, igual al número de respuesta.

    {
      "profile_id": "0",
      "name": ""
    }
    {
      "profile_id": "1",
      "name": ""
    }
    …
    {
      "profile_id": "4",
      "name": ""
    }
    

    El flujo del cliente es muy similar al flujo del servidor. Solo ahora el cliente transmite un flujo de mensajes y el servidor los procesa. El servidor puede procesar los mensajes inmediatamente o esperar todas las solicitudes del cliente y luego procesarlas.

        @Override
        public StreamObserver<ProfileDescriptorOuterClass.ProfileDescriptor> clientStream(StreamObserver<Empty> responseObserver) {
            return new StreamObserver<>() {
    
                @Override
                public void onNext(ProfileDescriptorOuterClass.ProfileDescriptor profileDescriptor) {
                    log.info("ProfileDescriptor from client. Profile id: {}", profileDescriptor.getProfileId());
                }
    
                @Override
                public void onError(Throwable throwable) {
    
                }
    
                @Override
                public void onCompleted() {
                    responseObserver.onCompleted();
                }
            };
        }
    

    En la secuencia del cliente, debe devolver el StreamObserver al cliente, al que el servidor recibirá mensajes. Se llamará al método onError si ocurre un error en la secuencia. Por ejemplo, terminó incorrectamente.

    Para implementar un flujo bidireccional, es necesario combinar la creación de un flujo desde el servidor y el cliente.

    @Override
        public StreamObserver<ProfileDescriptorOuterClass.ProfileDescriptor> biDirectionalStream(
                StreamObserver<ProfileDescriptorOuterClass.ProfileDescriptor> responseObserver) {
    
            return new StreamObserver<>() {
                int pointCount = 0;
                @Override
                public void onNext(ProfileDescriptorOuterClass.ProfileDescriptor profileDescriptor) {
                    log.info("biDirectionalStream, pointCount {}", pointCount);
                    responseObserver.onNext(ProfileDescriptorOuterClass.ProfileDescriptor
                            .newBuilder()
                            .setProfileId(pointCount++)
                            .build());
                }
    
                @Override
                public void onError(Throwable throwable) {
    
                }
    
                @Override
                public void onCompleted() {
                    responseObserver.onCompleted();
                }
            };
        } 
    

    En este ejemplo, en respuesta al mensaje del cliente, el servidor devolverá un perfil con un aumento pointCount.

    Conclusión

    Hemos cubierto las opciones básicas para la mensajería entre un cliente y un servidor usando gRPC: flujo de servidor implementado, flujo de cliente, flujo bidireccional.

    El artículo fue escrito por Sergey Golitsyn