자바를 사용하여 gRPC 서비스 빌드

1. 개요

gRPC는 Google에서 개발한 언어 및 플랫폼 중립적인 리모트 프로시져 콜 (RPC) 프레임워크 및 도구 모음입니다. 특히 강력한 바이너리 직렬화 툴셋 및 언어인 프로토콜 버퍼를 사용하여 서비스를 정의할 수 있습니다. 그런 다음 다양한 언어로 된 서비스 정의에서 직관적인 클라이언트 및 서버 스텁을 생성할 수 있습니다.

이 Codelab에서는 gRPC 프레임워크를 사용하여 API를 노출하는 Java 기반 서비스를 빌드한 다음 생성된 gRPC 클라이언트 측 스텁을 사용할 클라이언트를 작성하는 방법을 알아봅니다.

학습할 내용

  • 프로토콜 버퍼 언어
  • 자바를 사용하여 gRPC 서비스를 구현하는 방법
  • 자바를 사용하여 gRPC 클라이언트를 구현하는 방법

이 튜토리얼을 어떻게 사용하실 계획인가요?

읽기만 할 계획입니다 읽은 다음 연습 활동을 완료할 계획입니다

귀하의 Node.js 앱 빌드 경험을 평가해 주세요.

초급 중급 고급

Go 앱을 빌드한 경험을 평가해 주세요.

<ph type="x-smartling-placeholder"></ph> 초보자 중급 숙련도

2. 설정 및 요구사항

자습형 환경 설정

  1. Cloud 콘솔에 로그인하고 새 프로젝트를 만들거나 기존 프로젝트를 다시 사용합니다. 아직 Gmail이나 Google Workspace 계정이 없는 경우 계정을 만들어야 합니다.

96a9c957bc475304.png

b9a10ebdf5b5a448.png

a1e3c01a38fa61c2.png

모든 Google Cloud 프로젝트에서 고유한 이름인 프로젝트 ID를 기억하세요(위의 이름은 이미 사용되었으므로 사용할 수 없습니다). 이 ID는 나중에 이 Codelab에서 PROJECT_ID라고 부릅니다.

  1. 그런 후 Google Cloud 리소스를 사용할 수 있도록 Cloud Console에서 결제를 사용 설정해야 합니다.

이 Codelab 실행에는 많은 비용이 들지 않습니다. 이 가이드를 마친 후 비용이 결제되지 않도록 리소스 종료 방법을 알려주는 '삭제' 섹션의 안내를 따르세요. Google Cloud 신규 사용자에게는 미화$300 상당의 무료 체험판 프로그램에 참여할 수 있는 자격이 부여됩니다.

Google Cloud Shell

이 Codelab은 컴퓨터에서 실행할 수 있지만 이 Codelab에서는 Cloud에서 실행되는 명령줄 환경인 Google Cloud Shell을 사용합니다.

이 Debian 기반 가상 머신에는 필요한 모든 개발 도구가 로드되어 있습니다. 영구적인 5GB 홈 디렉터리를 제공하고 Google Cloud에서 실행되므로 네트워크 성능과 인증이 크게 개선됩니다. 즉, 이 Codelab에 필요한 것은 브라우저뿐입니다(Chromebook에서도 작동 가능).

  1. Cloud Console에서 Cloud Shell을 활성화하려면 단순히 Cloud Shell 활성화a8460e837e9f5fda.png를 클릭합니다. 환경을 프로비저닝하고 연결하는 데 몇 정도만 소요됩니다.

b532b2f19ab85dda.png

Screen Shot 2017-06-14 at 10.13.43 PM.png

Cloud Shell에 연결되면 사용자 인증이 이미 완료되었고 프로젝트가 내 PROJECT_ID에 설정되어 있음을 확인할 수 있습니다.

gcloud auth list

명령어 결과

Credentialed accounts:
 - <myaccount>@<mydomain>.com (active)
gcloud config list project

명령어 결과

[core]
project = <PROJECT_ID>

어떤 이유로든 프로젝트가 설정되지 않았으면 다음 명령어를 실행하면 됩니다.

gcloud config set project <PROJECT_ID>

PROJECT_ID를 찾고 계신가요? 설정 단계에서 사용한 ID를 확인하거나 Cloud Console 대시보드에서 확인하세요.

2485e00c1223af09.png

또한 Cloud Shell은 기본적으로 이후 명령어를 실행할 때 유용할 수 있는 몇 가지 환경 변수를 설정합니다.

echo $GOOGLE_CLOUD_PROJECT

명령어 결과

<PROJECT_ID>
  1. 마지막으로 기본 영역 및 프로젝트 구성을 설정합니다.
gcloud config set compute/zone us-central1-f

다양한 영역을 선택할 수 있습니다. 자세한 내용은 리전 및 영역을 참조하세요.

3. gRPC 서비스 만들기

Maven으로 새 Java 프로젝트를 만듭니다.

$ mvn archetype:generate -DgroupId=com.example.grpc \
 -DartifactId=grpc-hello-server \
 -DarchetypeArtifactId=maven-archetype-quickstart \
 -DinteractiveMode=false
$ cd grpc-hello-server

gRPC 정의 파일 추가

gRPC에서는 서비스 페이로드 (요청 및 응답)와 서비스 작업을 IDL (인터페이스 정의 언어)로 캡처해야 합니다. gRPC는 Protobuffer 3 문법을 사용하여 메시지 페이로드와 작업을 정의합니다. Hello 요청과 Hello 응답이 포함된 간단한 인사말 서비스의 proto 파일을 만들어 보겠습니다.

먼저 새 proto 파일을 저장할 새 proto 디렉터리를 만듭니다.

$ mkdir -p src/main/proto

그런 다음 새 proto 파일 src/main/proto/GreetingService.proto를 만듭니다.

vim,nano, 또는 emacs를 사용하여 파일을 수정할 수 있습니다.

src/main/proto/GreetingService.proto

syntax = "proto3";
package com.example.grpc;

// Request payload
message HelloRequest {
  // Each message attribute is strongly typed.
  // You also must assign a "tag" number.
  // Each tag number is unique within the message.
  string name = 1;

  // This defines a strongly typed list of String
  repeated string hobbies = 2;

  // There are many more basics types, like Enum, Map
  // See https://developers.google.com/protocol-buffers/docs/proto3
  // for more information.
}

message HelloResponse {
  string greeting = 1;
}

// Defining a Service, a Service can have multiple RPC operations
service GreetingService {
  // Define a RPC operation
  rpc greeting(HelloRequest) returns (HelloResponse);
}

gRPC 종속 항목 및 플러그인 추가

정의가 지정되면 이 파일에서 서버 측 스텁과 클라이언트 측 스텁을 모두 생성할 수 있습니다. gRPC 종속 항목과 플러그인을 추가해야 합니다.

먼저 pom.xml에 gRPC 종속 항목을 추가합니다.

pom.xml

<project>
  ...
  <dependencies>
    <dependency>
      <groupId>io.grpc</groupId>
      <artifactId>grpc-netty-shaded</artifactId>
      <version>1.24.0</version>
    </dependency>
    <dependency>
      <groupId>io.grpc</groupId>
      <artifactId>grpc-protobuf</artifactId>
      <version>1.24.0</version>
    </dependency>
    <dependency>
      <groupId>io.grpc</groupId>
      <artifactId>grpc-stub</artifactId>
      <version>1.24.0</version>
    </dependency>
    <dependency>
      <groupId>javax.annotation</groupId>
      <artifactId>javax.annotation-api</artifactId>
      <version>1.3.2</version>
    </dependency>
    ...
  </dependencies>
  ...
</project>

그런 다음 플러그인을 추가합니다.

pom.xml

<project>
  ...
  <dependencies>
    ...
  </dependencies>
  <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>
          <protocArtifact>com.google.protobuf:protoc:3.9.0:exe:${os.detected.classifier}</protocArtifact>
          <pluginId>grpc-java</pluginId>
          <pluginArtifact>io.grpc:protoc-gen-grpc-java:1.24.0:exe:${os.detected.classifier}</pluginArtifact>
        </configuration>
        <executions>
          <execution>
            <goals>
              <goal>compile</goal>
              <goal>compile-custom</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>

</project>

스텁 생성

애플리케이션을 빌드할 때 플러그인은 proto 정의를 Java 코드로 변환합니다.

$ mvn -DskipTests package

생성된 파일을 보려면 다음 안내를 따르세요.

$ find target/generated-sources

서비스 구현

먼저 greeting 작업을 구현할 새 GreetingServiceImpl 클래스를 만듭니다.

src/main/java/com/example/grpc/GreetingServiceImpl.java

package com.example.grpc;

import io.grpc.stub.StreamObserver;

public class GreetingServiceImpl extends GreetingServiceGrpc.GreetingServiceImplBase {
  @Override
  public void greeting(GreetingServiceOuterClass.HelloRequest request,
        StreamObserver<GreetingServiceOuterClass.HelloResponse> responseObserver) {
  // HelloRequest has toString auto-generated.
    System.out.println(request);

    // You must use a builder to construct a new Protobuffer object
    GreetingServiceOuterClass.HelloResponse response = GreetingServiceOuterClass.HelloResponse.newBuilder()
      .setGreeting("Hello there, " + request.getName())
      .build();

    // Use responseObserver to send a single response back
    responseObserver.onNext(response);

    // When you are done, you must call onCompleted.
    responseObserver.onCompleted();
  }
}

서버 구현

마지막으로 포트에서 수신 대기하는 서버를 시작하고 이 서비스 구현을 등록해야 합니다. App 클래스 및 기본 메서드를 수정합니다.

src/main/java/com/example/grpc/App.java

package com.example.grpc;

import io.grpc.*;

public class App
{
    public static void main( String[] args ) throws Exception
    {
      // Create a new server to listen on port 8080
      Server server = ServerBuilder.forPort(8080)
        .addService(new GreetingServiceImpl())
        .build();

      // Start the server
      server.start();

      // Server threads are running in the background.
      System.out.println("Server started");
      // Don't exit the main thread. Wait until server is terminated.
      server.awaitTermination();
    }
}

마지막으로 서버를 실행합니다.

$ mvn -DskipTests package exec:java -Dexec.mainClass=com.example.grpc.App
...
Server Started

4. 서비스 사용

생성기에서 이미 모든 클라이언트 측 스텁을 생성했습니다. 실습의 편의를 위해 동일한 Maven 프로젝트를 사용하지만 새로운 기본 메서드를 사용하여 새 Client 클래스를 추가하기만 하면 됩니다.

먼저 +를 클릭하여 새 Cloud Shell 세션을 엽니다. 그러면 서버를 종료할 필요가 없습니다.

1ff0fda960b9adfc.png

새 세션에서 grpc-hello-server 디렉터리로 전환합니다.

$ cd grpc-hello-server

그런 다음 새 Client 클래스를 추가합니다.

src/main/java/com/example/grpc/Client.java

package com.example.grpc;

import io.grpc.*;

public class Client
{
    public static void main( String[] args ) throws Exception
    {
      // Channel is the abstraction to connect to a service endpoint
      // Let's use plaintext communication because we don't have certs
      final ManagedChannel channel = ManagedChannelBuilder.forTarget("localhost:8080")
        .usePlaintext(true)
        .build();

      // It is up to the client to determine whether to block the call
      // Here we create a blocking stub, but an async stub,
      // or an async stub with Future are always possible.
      GreetingServiceGrpc.GreetingServiceBlockingStub stub = GreetingServiceGrpc.newBlockingStub(channel);
      GreetingServiceOuterClass.HelloRequest request =
        GreetingServiceOuterClass.HelloRequest.newBuilder()
          .setName("Ray")
          .build();

      // Finally, make the call using the stub
      GreetingServiceOuterClass.HelloResponse response = 
        stub.greeting(request);

      System.out.println(response);

      // A Channel should be shutdown before stopping the process.
      channel.shutdownNow();
    }
}

마지막으로 클라이언트를 실행합니다.

$ mvn -DskipTests package exec:java -Dexec.mainClass=com.example.grpc.Client
...
greeting: "Hello there, Ray"

작업이 끝났습니다. 간단하죠?

5. 스트리밍 서비스

이 외에도 다양한 방법을 시도해 볼 수 있습니다. 예를 들어 proto 파일의 stream 키워드를 요청 또는 응답 매개변수에 추가하면 간단하게 스트리밍 서비스를 빌드할 수 있습니다. 예를 들어

src/main/proto/GreetingService.proto

syntax = "proto3";
package com.example.grpc;

...

// Defining a Service, a Service can have multiple RPC operations
service GreetingService {
  // MODIFY HERE: Update the return to streaming return.
  rpc greeting(HelloRequest) returns (stream HelloResponse);
}

하나의 응답이 아닌 여러 응답을 보내도록 서버를 업데이트합니다. 이렇게 하려면 responseObserver.onNext(...)를 여러 번 호출하면 됩니다.

src/main/java/com/example/grpc/GreetingServiceImpl.java

package com.example.grpc;

import io.grpc.stub.StreamObserver;

public class GreetingServiceImpl extends GreetingServiceGrpc.GreetingServiceImplBase {
  @Override
  public void greeting(GreetingServiceOuterClass.HelloRequest request,
        StreamObserver<GreetingServiceOuterClass.HelloResponse> responseObserver) {
 
    ...

    // Feel free to construct different responses if you'd like.
    responseObserver.onNext(response);
    responseObserver.onNext(response);
    responseObserver.onNext(response);

    // When you are done, you must call onCompleted.
    responseObserver.onCompleted();
  }
}

클라이언트는 차단 스텁 대신 비동기 스텁을 사용해야 합니다. 클라이언트 코드를 업데이트하여 stub 유형을 GreetingServiceStub로 업데이트합니다.

src/main/java/com/example/grpc/Client.java

package com.example.grpc;

import io.grpc.*;

// New import
import io.grpc.stub.*;

public class Client
{
    public static void main( String[] args ) throws Exception
    {
      final ManagedChannel channel = ManagedChannelBuilder.forTarget("localhost:8080")
        .usePlaintext(true)
        .build();

      // Replace the previous synchronous code with asynchronous code.
      // This time use an async stub:
       GreetingServiceGrpc.GreetingServiceStub stub = GreetingServiceGrpc.newStub(channel);

      // Construct a request
      GreetingServiceOuterClass.HelloRequest request =
        GreetingServiceOuterClass.HelloRequest.newBuilder()
          .setName("Ray")
          .build();

      // Make an Asynchronous call. Listen to responses w/ StreamObserver
      stub.greeting(request, new StreamObserver<GreetingServiceOuterClass.HelloResponse>() {
        public void onNext(GreetingServiceOuterClass.HelloResponse response) {
          System.out.println(response);
        }
        public void onError(Throwable t) {
        }
        public void onCompleted() {
          // Typically you'll shutdown the channel somewhere else.
          // But for the purpose of the lab, we are only making a single
          // request. We'll shutdown as soon as this request is done.
          channel.shutdownNow();
        }
      });
    }
}

애플리케이션을 다시 빌드합니다.

$ mvn -DskipTests package

자체 Cloud Shell 세션에서 서버와 클라이언트를 모두 다시 시작합니다.

서버를 시작하려면 다음 안내를 따르세요.

$ mvn exec:java -Dexec.mainClass=com.example.grpc.App
...
Server Started

클라이언트를 시작하려면 다음 안내를 따르세요.

$ mvn exec:java -Dexec.mainClass=com.example.grpc.Client
...
greeting: "Hello there, Ray"
greeting: "Hello there, Ray"
greeting: "Hello there, Ray"

6. 축하합니다.

학습한 내용:

  • 프로토콜 버퍼 언어
  • 자바를 사용하여 gRPC 서버를 구현하는 방법
  • 자바를 사용하여 gRPC 클라이언트를 구현하는 방법

다음 단계:

Google에 의견 보내기

  • 잠시 시간을 내어 간단한 설문조사에 응해주시기 바랍니다.