Post

gRPC 소개

1. gRPC란

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

2. 개요

프레임워크는 원격 프로시저 호출의 클라이언트-서버 모델을 기반으로 한다. 클라이언트 애플리케이션은 마치 로컬 객체인 것처럼 서버 애플리케이션의 메서드를 직접 호출할 수 있다.

3. 메이븐 의존성

grpc-netty , grpc-protobuf 및 grpc-stub 종속성을 추가한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<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 파일에서 수행된다. 또한 페이로드 메시지의 구조 설명에도 사용된다.

1) 기본 구성

샘플 HelloService에 대한 HelloService.proto 파일을 만든다.

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

첫 번째 줄은 이 파일이 사용하는 구문을 컴파일러에 알려준다. 기본적으로 컴파일러는 단일 Java 파일에 모든 Java 코드를 생성한다. 두 번째 줄은 이 설정을 재정의한다. 즉, 모든 것이 개별 파일에 생성된다는 의미이다.

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

2) 메시지 구조 정의

메시지를 정의한다.

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

이는 요청 페이로드를 정의한다. 여기에서는 메시지에 포함되는 각 속성이 해당 유형과 함께 정의된다.

태그라고 하는 고유 번호를 각 속성에 할당해야 한다. 프로토콜 버퍼는 속성 이름을 사용하는 대신 이 태그를 사용하여 속성을 나타낸다.

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

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

3) 서비스 계약 정의

마지막으로 서비스 계약을 정의한다. HelloService의 경우 hello() 작업을 정의한다.

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

hello() 작업은 단항 요청을 수락하고 단항 응답을 반환한다. gRPC는 요청 및 응답 앞에 stream 키워드를 추가하여 스트리밍도 지원한다.

5. 코드 생성

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

1) Protocol Buffer Compiler 사용

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

또한 gRPC Java Codegen Plugin도 구해야 한다.

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

1
2
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

2) Maven Plugin 사용

gRPC는 Maven 빌드 시스템을 위한 protobuf-maven-plugin을 제공한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<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가 포함되어 있다.

1) 서비스 기본 클래스 재정의

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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() 메소드로 설정하여 클라이언트에 보낸다.

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

2) Grpc 서버 실행

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

1
2
3
4
5
6
7
8
9
10
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()는 서버를 시작한다. 이 예에서는 waitTermination()을 호출하여 서버를 포그라운드에서 계속 실행하고 프롬프트를 차단한다.

7. 클라이언트 생성

gRPC는 연결, 연결 풀링, 로드 밸런싱 등과 같은 기본 세부 정보를 추상화하는 채널 구성을 제공한다.

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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를 전달한다. 자동 생성된 설정자를 사용하여 HelloRequest 객체의 firstName 및 lastName 속성을 설정할 수 있다.

마지막으로 서버는 HelloResponse 객체를 반환한다.

[출처 및 참고]

This post is licensed under CC BY 4.0 by the author.