1. 소개

gRPC 는 Google에서 처음 개발한 고성능 오픈 소스 RPC 프레임워크입니다. 이는 상용구 코드를 제거하는 데 도움이 되며 데이터 센터 내부 및 데이터 센터 간에 다중 언어 서비스를 연결하는 데 도움이 됩니다.

2. 개요

프레임워크는 원격 프로시저 호출의 클라이언트-서버 모델을 기반으로 합니다. 클라이언트 응용 프로그램은 마치 로컬 개체인 것처럼 서버 응용 프로그램의 메서드를 직접 호출할 수 있습니다.

이 문서에서는 다음 단계를 사용하여 gRPC를 사용하여 일반적인 클라이언트-서버 애플리케이션을 만듭니다.

  1. .proto 파일 에서 서비스 정의
  2. 프로토콜 버퍼 컴파일러를 사용하여 서버 및 클라이언트 코드 생성
  3. 서버 애플리케이션 생성, 생성된 서비스 인터페이스 구현 및 gRPC 서버 생성
  4. 생성된 스텁을 사용하여 RPC 호출을 수행하여 클라이언트 애플리케이션을 생성합니다.

이름과 성을 교환하여 인사말을 반환 하는 간단한 HelloService 를 정의해 보겠습니다.

3. 메이븐 의존성

grpc-netty , grpc-protobufgrpc-stub 의존성 을 추가해 보겠습니다.

<dependency>
    <groupId>io.grpc</groupId>
    <artifactId>grpc-netty</artifactId>
    <version>1.16.1</version>
</dependency>
<dependency>
    <groupId>io.grpc</groupId>
    <artifactId>grpc-protobuf</artifactId>
    <version>1.16.1</version>
</dependency>
<dependency>
    <groupId>io.grpc</groupId>
    <artifactId>grpc-stub</artifactId>
    <version>1.16.1</version>
</dependency>

4. 서비스 정의

매개변수 및 반환 유형과 함께 원격으로 호출할 수 있는 메서드를 지정 하여 서비스를 정의하는 것으로 시작합니다 .

이것은 프로토콜 버퍼 를 사용하여 .proto 파일 에서 수행됩니다 . 또한 페이로드 메시지의 구조를 설명하는 데 사용됩니다.

4.1. 기본 구성

샘플 HelloService 에 대한 HelloService.proto 파일을 생성해 보겠습니다 . 몇 가지 기본 구성 세부 정보를 추가하여 시작합니다.

syntax = "proto3";
option java_multiple_files = true;
package org.baeldung.grpc;

첫 번째 줄은 컴파일러에게 이 파일에서 사용되는 구문을 알려줍니다. 기본적으로 컴파일러는 단일 Java 파일에 모든 Java 코드를 생성합니다. 두 번째 줄은 이 설정을 무시하고 모든 것이 개별 파일에서 생성됩니다.

마지막으로 생성된 Java 클래스에 사용할 패키지를 지정합니다.

4.2. 메시지 구조 정의

다음으로 메시지를 정의합니다.

message HelloRequest {
    string firstName = 1;
    string lastName = 2;
}

이는 요청 페이로드를 정의합니다. 여기서 메시지에 들어가는 각 속성은 해당 유형과 함께 정의됩니다.

태그라고 하는 각 속성에 고유한 번호를 지정해야 합니다. 이 태그는 속성 이름을 사용하는 대신 속성을 나타내기 위해 프로토콜 버퍼에서 사용됩니다.

따라서 매번 속성 이름 firstName 을 전달하는 JSON과 달리 프로토콜 버퍼는 숫자 1을 사용하여 firstName 을 나타냅니다 . 응답 페이로드 정의는 요청과 유사합니다.

여러 메시지 유형에서 동일한 태그를 사용할 수 있습니다.

message HelloResponse {
    string greeting = 1;
}

4.3. 서비스 계약 정의

마지막으로 서비스 계약을 정의하겠습니다. HelloService 의 경우 hello() 작업 을 정의합니다 .

service HelloService {
    rpc hello(HelloRequest) returns (HelloResponse);
}

hello() 작업 은 단항 요청을 수락하고 단항 응답을 반환합니다. gRPC는 또한 요청 및 응답에 stream 키워드를 접두사로 지정하여 스트리밍을 지원합니다.

5. 코드 생성

이제 HelloService.proto 파일을 프로토콜 버퍼 컴파일러 protoc 에 전달하여 Java 파일을 생성합니다. 이를 트리거하는 방법에는 여러 가지가 있습니다.

5.1. 프로토콜 버퍼 컴파일러 사용

먼저 프로토콜 버퍼 컴파일러가 필요합니다. 여기 에서 사용할 수 있는 미리 컴파일된 많은 바이너리 중에서 선택할 수 있습니다 .

또한 gRPC Java Codegen 플러그인 을 가져와야 합니다 .

마지막으로 다음 명령을 사용하여 코드를 생성할 수 있습니다.

protoc --plugin=protoc-gen-grpc-java=$PATH_TO_PLUGIN -I=$SRC_DIR 
  --java_out=$DST_DIR --grpc-java_out=$DST_DIR $SRC_DIR/HelloService.proto

5.2. 메이븐 플러그인 사용

개발자는 코드 생성이 빌드 시스템과 긴밀하게 통합되기를 원할 것입니다. gRPC는 Maven 빌드 시스템을 위한 protobuf-maven-plugin 을 제공합니다.

<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.3.0:exe:${os.detected.classifier}
        </protocArtifact>
        <pluginId>grpc-java</pluginId>
        <pluginArtifact>
          io.grpc:protoc-gen-grpc-java:1.4.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} 와 같은 다양하고 유용한 플랫폼 종속 프로젝트 속성을 생성합니다 .

6. 서버 생성

코드 생성에 사용하는 방법에 관계없이 다음 키 파일이 생성됩니다.

  • HelloRequest.java – HelloRequest 유형 정의 를 포함합니다.
  • HelloResponse.java 여기에는 HelleResponse 유형 정의 가 포함됩니다.
  • HelloServiceImplBase.java 여기에는 서비스 인터페이스에서 정의한 모든 작업의 ​​구현을 제공하는 추상 클래스 HelloServiceImplBase 가 포함됩니다.

6.1. 서비스 기본 클래스 재정의

추상 클래스 HelloServiceImplBase 의 기본 구현은 메소드가 구현되지 않았음을 알리는 런타임 예외 io.grpc.StatusRuntimeException 을 발생시키는 것입니다.

이 클래스를 확장 하고 서비스 정의에 언급된 hello() 메서드를 재정의합니다.

public class HelloServiceImpl extends HelloServiceImplBase {

    @Override
    public void hello(
      HelloRequest request, StreamObserver<HelloResponse> responseObserver) {

        String greeting = new StringBuilder()
          .append("Hello, ")
          .append(request.getFirstName())
          .append(" ")
          .append(request.getLastName())
          .toString();

        HelloResponse response = HelloResponse.newBuilder()
          .setGreeting(greeting)
          .build();

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

hello() 의 서명을 HellService.proto 파일 에 작성한 서명과 비교하면 HelloResponse 를 반환하지 않는다는 것을 알 수 있습니다. 대신 두 번째 인수를 StreamObserver<HelloResponse> 로 사용합니다. 이는 응답 관찰자이며 서버가 응답을 호출하기 위한 콜백입니다.

이 방법 으로 클라이언트는 차단 호출 또는 비차단 호출을 수행할 수 있는 옵션을 얻습니다 .

gRPC는 빌더를 사용하여 개체를 만듭니다. HelloResponse.newBuilder() 를 사용 하고 인사말 텍스트를 설정하여 HelloResponse 객체를 빌드합니다. 이 객체를 responseObserver의 onNext() 메서드로 설정하여 클라이언트에 보냅니다.

마지막으로 onCompleted() 를 호출 하여 RPC 처리를 완료했음을 지정해야 합니다. 그렇지 않으면 연결이 중단되고 클라이언트는 추가 정보가 들어올 때까지 기다립니다.

6.2. Grpc 서버 실행

다음으로 들어오는 요청을 수신하기 위해 gRPC 서버를 시작해야 합니다.

public class GrpcServer {
    public static void main(String[] args) {
        Server server = ServerBuilder
          .forPort(8080)
          .addService(new HelloServiceImpl()).build();

        server.start();
        server.awaitTermination();
    }
}

여기서 다시 빌더를 사용하여 포트 8080에서 gRPC 서버를 생성하고 정의한 HelloServiceImpl 서비스를 추가합니다. start() 는 서버를 시작합니다. 이 예제에서는 awaitTermination() 을 호출 하여 프롬프트를 차단하는 포그라운드에서 서버를 계속 실행합니다.

7. 클라이언트 만들기

gRPC는 연결, 연결 풀링, 부하 분산 등과 같은 기본 세부 정보를 추상화하는 채널 구조를 제공합니다 .

ManagedChannelBuilder 를 사용하여 채널을 생성합니다 . 여기에서 서버 주소와 포트를 지정합니다.

암호화 없이 일반 텍스트를 사용합니다.

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

        HelloServiceGrpc.HelloServiceBlockingStub stub 
          = HelloServiceGrpc.newBlockingStub(channel);

        HelloResponse helloResponse = stub.hello(HelloRequest.newBuilder()
          .setFirstName("Baeldung")
          .setLastName("gRPC")
          .build());

        channel.shutdown();
    }
}

다음으로, 우리는 hello() 에 대한 실제 원격 호출을 만드는 데 사용할 스텁을 생성해야 합니다 . 스텁은 클라이언트가 서버와 상호 작용하는 기본 방법입니다. 자동 생성된 스텁을 사용할 때 스텁 클래스에는 채널을 래핑하기 위한 생성자가 있습니다.

여기서는 RPC 호출이 서버의 응답을 기다리고 응답을 반환하거나 예외를 발생시키도록 차단/동기 스텁을 사용하고 있습니다. 비차단/비동기 호출을 용이하게 하는 gRPC에서 제공하는 다른 두 가지 유형의 스텁이 있습니다.

마지막으로 hello() RPC 호출을 할 시간입니다. 여기에서 HelloRequest 를 전달합니다 . 자동 생성된 setter를 사용 하여 HelloRequest 객체 의 firstName , lastName 속성 을 설정할 수 있습니다.

우리 는 서버에서 반환된 HelloResponse 개체를 다시 얻습니다.

8. 결론

이 사용방법(예제)에서는 gRPC를 사용하여 서비스를 정의하고 gRPC가 모든 상용구 코드를 처리하도록 함으로써 두 서비스 간의 통신 개발을 용이하게 하는 방법을 살펴보았습니다.

평소와 같이 GitHub 에서 소스를 찾을 수 있습니다 .

Generic footer banner