인프런 "파이썬으로 쉽게 배우는 gRPC!" 강의 내용 정리
gRPC
gRPC란?
- google에서 개발한 RPC
- 네트워크를 통해 원격 시스템의 함수를 내 함수처럼 쓰는 기술
gRPC 기본 아키텍쳐
- Proto라는 부분은 gRPC에서 사용할 데이터와 제공되는 서비스를 정의하는 부분
- Protocol Buffers라는 언어로 작성하는데 메시지 직렬화 방식이다.
- 이컬 통해 실제로 전송되는 데이터나 제공되는 서비스 언어의 코드로 자동 생성되어 이 자동 생성된 코드 속에는 메시지 직렬화, 역직렬화, 서비스 인터페이스 등 다양한 코드를 포함한다.
HTTP 2.0
- gRPC는 HTTP 2.0 사용
- gRPC는 멀티플렉싱 기능을 지원한다. 멀티플렉싱은 여러 개의 요청과 응답을 하나의 TCP 연결로 처리하는 방식을 말한다.
- TCP 연결 수립은 꽤나 복잡하고 어려운 작업. HTTP2는 여러 개의 요청을 하나의 TCP 연결로 처리함으로써 지연 시간을 확 줄인다.
- HTTP 2.0은 압축 알고리즘을 사용헤서 헤더 크기 자체를 줄인다.
- 서버 푸시 기능을 지원한다. 서버 푸시란 서버가 클라이언트의 요청 없이도 필요한 데이터를 미리 전송하는 기능
- 1.1 버전은 텍스트 기반 프로토콜인데, 2.0은 컴퓨터에 최적화된 바이너리 프레임을 사용하기 때문에 데이터 처리 속도를 향상시킨다.
프로토 버퍼
- 프로토콜 버퍼는 강력한 타입 시스템과 효율적인 데이터 직렬화를 자랑하는 메커니즘이다. 프로토버퍼는 타입 시스템이 굉장히 강력하다. 데이터의 구조와 타입, 그리고 제공되는 서비스를 명확하게 정의함으로써 서비스의 인터페이스와 서비스 간의 인터페이스를 명확하게 한다.
- Protocol Buffers로 한번 데이터를 정의하고 나면, 특정 개발 언어에 맞는 코드로 자동 컴파일 할 수 있다.
통신적인 측면
- gRPC는 양방향 스트리밍 지원한다.
- 양방향 스트리밍은 서버와 클라이언트가 데이터를 스트림 형태로 편하게 주고받을 수 있다.
gRPC 단점
- 브라우저의 지원이 제한된다.
- gRPC 웹이라는 새로운 프레임워크가 존재하긴 함.
- 디버깅이 어렵다. 성능을 위해서 바이너리 형식으로 다루다보니 응답과 요청이 직관적이지 못하다.
- 학습 곡선이 있기는 있다.
gRPC의 모델
통신 모델
- gRPC는 기본적으로 서버 클라이언트 모델로 동작한다.
- gRPC에서는 세가지 구성요소가 존재한다.
- 서비스
- 서비스는 서버가 클라이언트에게 제공하는 기능 또는 작업을 의미한다.
- 메시지
- 클라이언트가 서버에게 요청하거나 서버가 클라이언트에게 응답할 때 데이터를 담는 일종의 컨테이너
- RPC
- RPC는 클라이언트가 서버의 메서드를 호출하는 방식을 말한다.
- 원격 프로시저 호출은 기존의 함수 호출과 매우 유사하게 느껴진다. 클라이언트는 마치 로컬에서 메서드를 호출하듯이 서버의 메서드를 호출할 수 있다.
통신 구성요소
- 채널
- gRPC에서 서버와의 연결 통로를 의미한다.
- 클라이언트가 서버에 접속해서 메서드를 호출하고 데이터를 주고받기 위해서는 채널을 통해야 한다.
- 채널은 기본적으로 서버의 IP 주소와 포트 번호를 사용해서 생성할 수 있다.
- 채널을 생성할 때는 보안 채널, 비보안 채널 등 다양한 옵션을 줄수 있다.
- 이러한 채널은 스텁을 만들 때 필요하다.
- 스텁
- 스텁은 클라이언트 대신 서버랑 대화해주는 놈
- 서버와 연결 통로를 만들었는데 그 밖에도 서버를 관리해줄 부분이 많은데 서버랑 통신하는 복잡한 부분은 스텁이 알아서 처리해준다.
- 스텁은 프로토 파일의 서비스 인터페이스를 정의하면 컴파일 시 자동으로 생성된다.
- 프로토 버퍼
- gRPC의 메시지를 정의하고 gRPC의 서비스를 정의하는 인터페이스 정의 언어
- 데이터를 압축할 수 있고, 주고받을 때 사용할 수도 있는 다목적 포맷
- 프로토파일이라는 파일 속에 프로토콜 버퍼 문법을 사용해서 메시지와 서비스를 정의할 수 있다.
채널
- 프로토콜 버퍼와는 직접적인 연관은 없지만 이 채널은 프로토콜 버퍼가 만들어내는 스텁과 관련이 있다.
- 스텁을 만들 때 채널을 통해 만들 수 있는데 이 채널을 전달만 해주면 복잡한 통신과 관련된 부분은 모두 스텁이 알아서 치리해준다.
Protocol Buffers
프로토콜 버퍼란
- 데이터 직렬화 매커니즘
- 메시지와 서비스를 정의하고 이걸 컴파일해서 특정 언어에 맞는 서버와 클라이언트 코드를 자동으로 생성할 수 있다.
- 하나의 프로토 파일로 다양한 클라이언트 서버 코드가 자동 생성되기 때문에 타입 불일치로 인한 에러 가능성이 확 줄어들게 된다.
- 또한 바이너리 형식으로 데이터를 다루기 때문에 빠르고 효율적인 통신이 가능하게 한다.
- 데이터 타입을 자동으로 생성해주다 보니 다른 언어 간에 통신할 때도 데이터 불일치로 인한 에러 가능성이 거의 없다.
프로토 버퍼 기본 구조
- 가장 위에는 문법적으로 어떤 문법을 사용할지 정의한다.
- syntax = "proto3"
- 패키지 이름을 정의하는 부분: 패키지 이름은 이름 공간을 분리해준다. 메시지를 정의할 때는 필드나 메시지 이름이 겹치는 경우가 상당히 많다. 패키지 이름을 정의하면 이름 공간을 분리하게 되면 이름 충돌 가능성이 줄어든다.
- import는 다른 프로토 파일에서 정의한 메시지를 사용할 필요가 있을 때 사용한다.
메시지
- 메시지를 정의하는 방법은 message 키워드를 사용해서 할 수 있다.
- 하나의 메시지는 다양한 필드를 가질 수 있는데 필드는 타입, 이름, 식별자 순으로 정의되어 있다.
- 타입은 해당 데이터의 타입
식별자
string name = 1; 이라고 되어 있으면 name이라는 필드에 1이 들어가는게 아니라 name이라는 필드의 정의 순서는 1번이라는 식별자를 부여한 것.
- 식별자는 다른 필드와 겹쳐서는 안된다.
데이터 타입
기본 데이터 타입
- 프로토버퍼의 타입과 각 언어의 타입을 찾아보면된다.
배열
- 기본 데이터 타입 앞에 repeated 키워드를 붙이면 배열이 된다.
repeated string hobbies = 4;
열거형
- 아래와 같이 정의하면 된다.
- 옆에 있는 숫자는 식별자다.
enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK = 2;
}
중첩 메시지
message PhoneNumber {
string number = 1;
PhoneType type = 2;
}
message Person {
repeated PhoneNumber phones = 0;
}
옵션
- 서비스나 데이터에 추가적인 정보를 제공하는 방법
- 옵션은 괄호 ([])안에 지정되며, 다음의 형식을 갖는다.
deprecated
- 특정 필드를 더 이상 사용하지 않는다고 생각해보자. 이걸 프로토파일을 수정해서 새로 컴파일을 진행하면 기존 코드들은 호환성이 유지되지 않기 때문에 새로 구현을 진행해야 한다. 근데 이걸 막기 위해 deprecated 옵션을 사용한다.
packed
- 반복되는 필드의 인코딩을 최적화한다.
default
- 필드의 기본값을 설정한다.
필드 예약하기
- 나중에 사용할 필드를 미리 예약할 수 있다.
- 호환성 유지에도 사용된다. (가장 큰 이유)
message MyMessage {
reserved 2, 15, 9 to 11; // 필드 번호 2, 15, 9부터 11까지 예약
reserved "foo", "bar"; // 필드 이름 예약
맵
- 키-값 쌍을 저장하는 데 사용되는 데이터 타입
message MyMessage {
map<string, int32> scores = 1; // 문자열 key와 값 int32를 가지는 맵
gRPC 서비스 정의
- .proto 파일에서 서비스 및 RPC 메서드를 정의한다.
service Greeter {
rpc SayHello (HelloRequest) returns (HelloRepy) {}
}
- service: 서비스 이름을 정의한다.
- rpc: RPC 메서드를 정의한다. (메서드 이름 요청 메시지 유형, 응답 메시지 유형)
컴파일
- .proto 파일에서 서비스 및 RPC 메서드를 정의한다.
- 각 언어에 맞는 컴파일 방법을 통해 컴파일한다.
gRPC 통신 패턴
스트리밍이란
- 클라이언트와 서버 간에 연속적인 데이터 흐름을 주고받는 방식
- 네 가지 스트리밍 방법을 지원
- gRPC의 스트리밍은 실시간 데이터 전송, 대용량 데이터 처리, 비동기 통신 등에 유용하다.
Unary RPC
가장 기본적인 통신 방식
- 클라이언트가 서버에 요청을 보내고 서버가 이에 대한 응답을 한 번 반환!
- HTTP의 Request-Response 모델과 매우 유사!
장점
- 간단하고 직관적이다.
- 오버헤드가 적다.
- 효율적이다.
단점
- 제한된 데이터 전송
- RPC의 장점을 살리지 못하는 통신 방식
동작 방식
- 클라이언트가 요청한다. -> 서버가 처리한다. -> 서버가 응답한다. -> 클라이언트가 응답을 수신한다.
활용 사례!
Server-Client, Client-Server streaming
Server streaming과 Client Streaming
- 한 쪽에서 계속해서 데이터를 보내고 다른 한 쪽은 하나의 데이터만 보내는 방식
- 여러 개의 요청과 하나의 응답
- 하나의 요청과 여러 개의 응답
장점
- 많은 데이터를 효과적으로 전송 가능!
- 실시간 데이터 전송이 가능!
단점
Bidirectional Streaming RPC
클라이언트와 서버가 서로 스트림 형태로 데이터를 주고받는 방식
- 가장 gRPC의 장점을 잘 활용하는 스트리밍 방식
장점
- 실시간 데이터 전송이 가능!
- 대화형 애플리케이션
- 상호 작용
단점
동작 방식
- 클라이언트 요청 스트리밍
- 서버 수신 및 처리
- 서버 응답 스트리밍
- 클라이언트 수신 및 처리
활용 사례
gRPC 파이썬/자바 용어 정리
| 역할 | Python 용어 | Java 용어 | 설명 |
|---|
| 서버와의 연결 | grpc.insecure_channel() | ManagedChannelBuilder.forAddress() | gRPC 서버에 연결하는 채널 객체 |
| 클라이언트 스텁 | Stub, FutureStub, AsyncStub | HelloServiceGrpc.HelloServiceBlockingStub 등 | 클라이언트에서 서버 메서드를 호출하는 객체 |
| 서버 등록 | add_XXXServicer_to_server() | extends HelloServiceGrpc.HelloServiceImplBase | 서버 구현을 gRPC에 등록 |
| 메시지 객체 | HelloRequest() | HelloRequest.newBuilder() | 요청/응답 메시지 클래스 |
| 서버 | grpc.server() | GrpcServerApplication 및 @Service 구현체 | gRPC 서버 인스턴스 |
| 응답 보내기 | context.set_code(), context.set_details() | responseObserver.onNext(), onCompleted() | 응답 반환 방식 |
| 코드 생성 도구 | grpc_tools.protoc | protobuf-maven-plugin 및 protoc | .proto 파일을 소스 코드로 변환하는 도구 |
| 요청 방식 | stub.SayHello() | blockingStub.sayHello() | gRPC 호출 방식 |
gRPC 통신 패턴 마다의 차이 정리
Unary RPC
.proto service 메서드 정의 방법
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply);
}
Server Streaming RPC
.proto service 메서드 정의 방법
service UserService {
rpc GetUserList (UserFilter) returns (stream User);
}
Client Streaming RPC
.proto service 메서드 정의 방법
service FileService {
rpc UploadFile (stream FileChunk) returns (UploadStatus);
}
Bidirectional Streaming RPC
.proto service 메서드 정의 방법
service ChatService {
rpc Chat (stream ChatMessage) returns (stream ChatMessage);
}