1. 소개
이 Codelab에서는 gRPC-Java를 사용하여 Java로 작성된 경로 매핑 애플리케이션의 기반을 형성하는 클라이언트와 서버를 만듭니다.
튜토리얼이 끝나면 gRPC를 사용하여 원격 서버에 연결하여 지도에서 특정 좌표에 있는 항목의 이름이나 우편 주소를 가져오는 클라이언트가 있습니다. 완전한 애플리케이션은 이 클라이언트-서버 설계를 사용하여 경로를 따라 관심 장소를 열거하거나 요약할 수 있습니다.
서버의 API는 프로토콜 버퍼 파일에 정의되어 있으며, 이 파일은 클라이언트와 서버가 서로 통신할 수 있도록 상용구 코드를 생성하는 데 사용되므로 이 기능을 구현하는 데 시간과 노력을 절약할 수 있습니다.
이 생성된 코드는 서버와 클라이언트 간의 복잡한 통신뿐만 아니라 데이터 직렬화 및 역직렬화도 처리합니다.
학습할 내용
- 프로토콜 버퍼를 사용하여 서비스 API를 정의하는 방법
- 자동 코드 생성을 사용하여 프로토콜 버퍼 정의에서 gRPC 기반 클라이언트와 서버를 빌드하는 방법
- gRPC를 사용한 클라이언트-서버 통신에 대한 이해
이 Codelab은 gRPC를 처음 접하거나 gRPC를 다시 배우려는 Java 개발자 또는 분산 시스템을 빌드하는 데 관심이 있는 모든 사용자를 대상으로 합니다. 이전 gRPC 경험은 필요하지 않습니다.
2. 시작하기 전에
기본 요건
- JDK 버전 8 이상
코드 가져오기
처음부터 완전히 시작하지 않아도 되도록 이 Codelab에서는 애플리케이션 소스 코드의 스캐폴드를 제공하여 이를 완성할 수 있습니다. 다음 단계에서는 프로토콜 버퍼 컴파일러 플러그인을 사용하여 상용구 gRPC 코드를 생성하는 등 애플리케이션을 완료하는 방법을 보여줍니다.
먼저 Codelab 작업 디렉터리를 만들고 해당 디렉터리로 이동합니다.
mkdir grpc-java-getting-started && cd grpc-java-getting-started
Codelab을 다운로드하고 압축을 풉니다.
curl -sL https://github.com/grpc-ecosystem/grpc-codelabs/archive/refs/heads/v1.tar.gz \
| tar xvz --strip-components=4 \
grpc-codelabs-1/codelabs/grpc-java-getting-started/start_here
또는 codelab 디렉터리만 포함된 .zip 파일을 다운로드하고 직접 압축을 해제할 수 있습니다.
구현을 입력하는 것을 건너뛰려면 완료된 소스 코드를 GitHub에서 확인하세요.
3. 서비스 정의
첫 번째 단계는 프로토콜 버퍼를 사용하여 애플리케이션의 gRPC 서비스, RPC 메서드, 요청 및 응답 메시지 유형을 정의하는 것입니다. 서비스에서 제공하는 기능:
- 서버가 구현하고 클라이언트가 호출하는 RPC 메서드
GetFeature
GetFeature
메서드를 사용할 때 클라이언트와 서버 간에 교환되는 데이터 구조인 메시지 유형Point
및Feature
클라이언트는 서버에 대한GetFeature
요청에서 지도 좌표를Point
로 제공하고 서버는 해당 좌표에 있는 항목을 설명하는 해당Feature
로 응답합니다.
이 RPC 메서드와 메시지 유형은 모두 제공된 소스 코드의 src/main/proto/routeguide/route_guide.proto
파일에 정의됩니다.
프로토콜 버퍼는 일반적으로 protobufs로 알려져 있습니다. gRPC 용어에 대한 자세한 내용은 gRPC의 핵심 개념, 아키텍처, 수명 주기를 참고하세요.
이 예에서는 Java 코드를 생성하므로 java_package
파일 옵션과 Java 클래스 이름을 .proto
에 지정했습니다.
option java_package = "io.grpc.examples.routeguide";
option java_outer_classname = "RouteGuideProto";
메시지 유형
소스 코드의 routeguide/route_guide.proto
파일에서 먼저 Point
메시지 유형을 정의합니다. Point
는 지도상의 위도-경도 좌표 쌍을 나타냅니다. 이 Codelab에서는 좌표에 정수를 사용합니다.
message Point {
int32 latitude = 1;
int32 longitude = 2;
}
1
및 2
은 message
구조의 각 필드에 대한 고유 ID 번호입니다.
다음으로 Feature
메시지 유형을 정의합니다. Feature
는 Point
로 지정된 위치에 있는 항목의 이름이나 우편 주소에 string
필드를 사용합니다.
message Feature {
// The name or address of the feature.
string name = 1;
// The point where the feature is located.
Point location = 2;
}
서비스 메서드
route_guide.proto
파일에는 애플리케이션 서비스에서 제공하는 하나 이상의 메서드를 정의하는 RouteGuide
라는 service
구조가 있습니다.
RouteGuide
정의 내에 rpc
메서드 GetFeature
를 추가합니다. 앞서 설명한 것처럼 이 메서드는 지정된 좌표 집합에서 위치의 이름이나 주소를 조회하므로 GetFeature
가 지정된 Point
에 대해 Feature
를 반환하도록 합니다.
service RouteGuide {
// Definition of the service goes here
// Obtains the feature at a given position.
rpc GetFeature(Point) returns (Feature) {}
}
이는 단항 RPC 메서드입니다. 클라이언트가 서버에 요청을 보내고 로컬 함수 호출과 마찬가지로 응답이 돌아오기를 기다리는 간단한 RPC입니다.
4. 클라이언트 및 서버 코드 생성
다음으로 .proto
서비스 정의에서 gRPC 클라이언트 및 서버 인터페이스를 생성해야 합니다. 이를 위해 특수 gRPC Java 플러그인과 함께 프로토콜 버퍼 컴파일러 protoc
를 사용합니다. gRPC 서비스를 생성하려면 proto3 컴파일러 (proto2 및 proto3 구문 모두 지원)를 사용해야 합니다.
Gradle 또는 Maven을 사용하는 경우 protoc
빌드 플러그인이 빌드의 일부로 필요한 코드를 생성할 수 있습니다. 자체 .proto
파일에서 코드를 생성하는 방법은 grpc-java README를 참고하세요.
이 프로젝트를 빌드할 수 있도록 Codelab의 소스 코드에 Gradle 환경과 구성이 제공됩니다.
grpc-java-getting-started
디렉터리 내에서 다음 명령어를 실행합니다.
$ chmod +x gradlew $ ./gradlew generateProto
다음 클래스는 서비스 정의에서 생성됩니다.
Feature.java
,Point.java
등 요청 및 응답 메시지 유형을 채우고 직렬화하고 검색하는 모든 프로토콜 버퍼 코드가 포함되어 있습니다.RouteGuideGrpc.java
:RouteGuide
서버가 구현할 기본 클래스인RouteGuideGrpc.RouteGuideImplBase
이 포함되어 있습니다 (다른 유용한 코드와 함께). 여기에는RouteGuide
서비스에 정의된 모든 메서드와 클라이언트가 사용할 스터브 클래스가 있습니다.
5. 서버 구현
먼저 RouteGuide
서버를 만드는 방법을 살펴보겠습니다. RouteGuide
서비스가 작업을 수행하도록 하는 방법에는 두 가지가 있습니다.
- 서비스 정의에서 생성된 서비스 인터페이스를 구현합니다. 이 인터페이스는 서비스의 실제 '작업'을 실행합니다.
- 클라이언트의 요청을 수신하고 올바른 서비스 구현으로 디스패치하는 gRPC 서버를 실행합니다.
RouteGuide 구현
보시다시피 서버에는 생성된 RouteGuideGrpc.RouteGuideImplBase
추상 클래스를 확장하는 RouteGuideService
클래스가 있습니다.
private static class RouteGuideService extends RouteGuideGrpc.RouteGuideImplBase {
...
}
기능으로 서버를 초기화하기 위해 다음 2개 파일이 제공되었습니다.
./src/main/java/io/grpc/examples/routeguide/RouteGuideUtil.java
./src/main/resources/io/grpc/examples/routeguide/route_guide_db.json
간단한 RPC 구현을 자세히 살펴보겠습니다.
단항 RPC
RouteGuideService
는 모든 서비스 메서드를 구현합니다. 이 경우 GetFeature()
는 클라이언트에서 Point
메시지를 가져와 알려진 장소 목록에서 해당하는 위치 정보를 Feature
메시지로 반환합니다.
@Override
public void getFeature(Point request, StreamObserver<Feature> responseObserver) {
responseObserver.onNext(checkFeature(request));
responseObserver.onCompleted();
}
getFeature()
메서드는 다음 두 매개변수를 사용합니다.
Point
: 요청입니다.StreamObserver<Feature>
: 서버가 응답과 함께 호출하는 특수 인터페이스인 응답 관찰자
클라이언트에게 응답을 반환하고 통화를 완료하려면 다음 단계를 따르세요.
- 서비스 정의에 지정된 대로 클라이언트에 반환할
Feature
응답 객체를 구성하고 채웁니다. 이 예에서는 별도의 비공개checkFeature()
메서드에서 이 작업을 실행합니다. onNext()
를 반환하기 위해 응답 관찰자의onNext()
메서드를 사용합니다.Feature
- 응답 관찰자의
onCompleted()
메서드를 사용하여 RPC 처리가 완료되었음을 지정합니다.
서버 시작
모든 서비스 메서드를 구현한 후에는 클라이언트가 실제로 서비스를 사용할 수 있도록 gRPC 서버를 시작해야 합니다. 상용구에는 ServerBuilder 객체 생성이 포함되어 있습니다.
ServerBuilder.forPort(port), port, RouteGuideUtil.parseFeatures(featureFile)
생성자에서 서비스를 빌드합니다.
- 빌더의
forPort()
메서드를 사용하여 클라이언트 요청을 수신하는 데 사용할 포트를 지정합니다 (와일드카드 주소를 사용함). - 서비스 구현 클래스
RouteGuideService
의 인스턴스를 만들고 빌더의addService()
메서드에 전달합니다. - 빌더에서
build()
를 호출하여 서비스의 RPC 서버를 만듭니다.
다음 스니펫은 ServerBuilder
객체를 만드는 방법을 보여줍니다.
/** Create a RouteGuide server listening on {@code port} using {@code featureFile} database. */
public RouteGuideServer(int port, URL featureFile) throws IOException {
this(Grpc.newServerBuilderForPort(port, InsecureServerCredentials.create()),
port, RouteGuideUtil.parseFeatures(featureFile));
}
다음 스니펫은 RouteGuide
서비스의 서버 객체를 만드는 방법을 보여줍니다.
/** Create a RouteGuide server using serverBuilder as a base and features as data. */
public RouteGuideServer(ServerBuilder<?> serverBuilder, int port, Collection<Feature> features) {
this.port = port;
server = serverBuilder.addService(new RouteGuideService(features))
.build();
}
위에서 만든 서버에서 start
를 호출하는 시작 메서드를 구현합니다.
public void start() throws IOException {
server.start();
logger.info("Server started, listening on " + port);
}
서버가 완료될 때까지 기다리는 메서드를 구현하여 즉시 종료되지 않도록 합니다.
/** Await termination on the main thread since the grpc library uses daemon threads. */
private void blockUntilShutdown() throws InterruptedException {
if (server != null) {
server.awaitTermination();
}
}
보시다시피 ServerBuilder
를 사용하여 서버를 빌드하고 시작합니다.
main 메서드에서 다음 작업을 실행합니다.
RouteGuideServer
인스턴스를 만듭니다.start()
를 호출하여 서비스의 RPC 서버를 활성화합니다.blockUntilShutdown()
를 호출하여 서비스가 중지될 때까지 기다립니다.
public static void main(String[] args) throws Exception {
RouteGuideServer server = new RouteGuideServer(8980);
server.start();
server.blockUntilShutdown();
}
6. 클라이언트 만들기
이 섹션에서는 RouteGuide
서비스의 클라이언트를 만드는 방법을 살펴봅니다.
스터브 인스턴스화
서비스 메서드를 호출하려면 먼저 스텁을 만들어야 합니다. 스텁에는 두 가지 유형이 있지만 이 Codelab에서는 차단 스텁만 사용하면 됩니다. 두 가지 유형은 다음과 같습니다.
- RPC 호출을 하고 서버가 응답할 때까지 기다리며 응답을 반환하거나 예외를 발생시키는 차단/동기 스텁
- 서버에 비차단 호출을 실행하는 비차단/비동기 스텁. 응답은 비동기식으로 반환됩니다. 비동기 스텁을 사용해야만 특정 유형의 스트리밍 호출을 할 수 있습니다.
먼저 gRPC 채널을 만든 다음 채널을 사용하여 스텁을 만들어야 합니다.
ManagedChannelBuilder
를 직접 사용하여 채널을 만들 수도 있습니다.
ManagedChannelBuilder.forAddress(host, port).usePlaintext().build
하지만 hostname:port
가 포함된 문자열을 사용하는 유틸리티 메서드를 사용해 보겠습니다.
Grpc.newChannelBuilder(target, InsecureChannelCredentials.create()).build();
이제 채널을 사용하여 차단 스텁을 만들 수 있습니다. 이 Codelab에서는 차단 RPC만 있으므로 .proto
에서 생성한 RouteGuideGrpc
클래스에 제공된 newBlockingStub
메서드를 사용합니다.
blockingStub = RouteGuideGrpc.newBlockingStub(channel);
서비스 메서드 호출
이제 서비스 메서드를 호출하는 방법을 살펴보겠습니다.
Simple RPC
간단한 RPC GetFeature
를 호출하는 것은 로컬 메서드를 호출하는 것만큼 간단합니다.
요청 프로토콜 버퍼 객체 (이 경우 Point
)를 만들어 채우고 차단 스텁의 getFeature()
메서드에 전달하여 Feature
를 다시 가져옵니다.
오류가 발생하면 Status
로 인코딩되며, 이는 StatusRuntimeException
에서 가져올 수 있습니다.
Point request = Point.newBuilder().setLatitude(lat).setLongitude(lon).build();
Feature feature;
try {
feature = blockingStub.getFeature(request);
} catch (StatusRuntimeException e) {
logger.log(Level.WARNING, "RPC failed: {0}", e.getStatus());
return;
}
상용구는 지정된 시점에 기능이 있었는지 여부에 따라 콘텐츠가 포함된 메시지를 로깅합니다.
7. 직접 사용해 보세요.
start_here
디렉터리 내에서 다음 명령어를 실행합니다.
$ ./gradlew installDist
이렇게 하면 코드가 컴파일되고, jar로 패키징되며, 예시를 실행하는 스크립트가 생성됩니다. build/install/start_here/bin/
디렉터리에 생성됩니다. 스크립트는 route-guide-server
및 route-guide-client
입니다.
클라이언트를 시작하기 전에 서버가 실행 중이어야 합니다.
- 서버를 실행합니다.
$ ./build/install/start_here/bin/route-guide-server
- 클라이언트를 실행합니다.
$ ./build/install/start_here/bin/route-guide-client
다음과 같은 출력이 표시됩니다. 명확성을 위해 타임스탬프는 생략했습니다.
INFO: *** GetFeature: lat=409,146,138 lon=-746,188,906 INFO: Found feature called "Berkshire Valley Management Area Trail, Jefferson, NJ, USA" at 40.915, -74.619 INFO: *** GetFeature: lat=0 lon=0 INFO: Found no feature at 0, 0