Tech story/Cloud

gRPC의 내부 구조 파헤치기: HTTP/2, Protobuf 그리고 스트리밍

kt cloud 테크블로그 2024. 10. 29. 09:33

[kt cloud 플랫폼Innovation팀 강솔 님]

 

gRPC의 내부 구조 파헤치기: HTTP/2, Protobuf 그리고 스트리밍

 

두 번의 포스팅을 통해 gRPC의 내부 동작 원리와 사용법에 대해 단계적으로 설명하고자 합니다. 

 

첫 번째 포스팅에서는 Protobuf를 사용해 gRPC 서비스 및 메세지를 정의하는 방법과 gRPC의 통신 방식, 그리고 4가지 통신 패턴에 대해 다룹니다. 이를 통해 gRPC의 구조와 효율적인 통신 원리를 명확하게 이해할 수 있도록 합니다.


두 번째 포스팅에서는 Channel과 Stub을 중심으로, gRPC에서 서버와 클라이언트가 어떻게 연결되고 메서드를 호출하는지를 살펴봅니다.

 

 

1.서비스 정의하기 - gRPC와 Protocol Buffers(Protobuf)

 

1.1. Protocol Buffers란?

앞선 포스팅에서 살펴본 것처럼, REST와 달리 gRPC는 protobuf라는 데이터 포맷을 사용합니다. Protobuf는 gRPC와 독립적으로 Google에서 개발한 직렬화 프레임워크로 데이터를 바이너리 형식으로 변환하기 용이하도록 설계되었습니다.
따라서 Json이나 XML에 비해 데이터 크기가 작고, (역)직렬화 속도가 빨라 네트워크 통신에 상당히 유리합니다. 이런 특징으로 gRPC는 Protobuf를 직렬화 포맷으로 채택했습니다.


Protobuf의 주요 특징

  • 경량화된 바이너리 포맷
    • Json이나 XML과 같은 텍스트가 아닌 바이너리 데이터
  • 다양한 언어와 플랫폼 지원
    • Java, C++, Python, Go, 등… 여러 언어 지원
    • protoc를 통해 컴파일할 때 자동으로 언어에 맞는 코드를 생성
  • 스키마 기반 메세지 구조
    • 스키마(.proto파일)를 사용해 메세지 구조와 필드 타입 명확하게 정의
  • 효율적인 직렬화 및 역직렬화


동작 원리


 

1. 컴파일러 다운로드

a. protoc를 설치, 이를 통해 .proto 파일을 각 언어에 맞는 코드로 변환

#MacOS 
brew install protobuf
#Linux
sudo apt-get install -y protobuf-compiler
#Windows
https://github.com/protocolbuffers/protobuf/releases
위의 페이지에서 Windows용 바이너리를 다운로드하고, 환경 변수에 추가 후 protoc 명령 실행 확인.

 

2. 스키마 정의

syntax = "proto3";
message Person {
  int32 id = 1;
  string name = 2;
  string email = 3;
}

 

3. 컴파일

a. protoc를 활용하여 원하는 언어로 컴파일

 protoc --java_out=. person.proto

 

4. 직렬화 및 데이터 전송

Person person = Person.newBuilder().setId(1).setName("Alice").build();
byte[] data = person.toByteArray();
// 바이너리 데이터 전송
 

5. 역직렬화

a. 수신된 바이너리 객체를 원본 객체로 복원

 
Person receivedPerson = Person.parseFrom(data);

 

1.2. Protobuf를 통해 데이터 스키마(서비스 및 메세지) 정의하기

 

gRPC에서는 데이터 스키마를 Protobuf(.proto)를 통해 정의합니다. gRPC에서는 아래 순서로 데이터 스키마를 작성하며, Protobuf에 대한 세부적인 문법은 공식 가이드 문서(https://protobuf.dev/programming-guides/proto2/ )를 참고합니다.

 

1. Syntax 설정

   a. proto는 2,3 버전이 존재하며 주로 최신 버전 proto3를 사용합니다.

 
syntax = "proto3";

 

2. 패키지 설정

   a. 네임스페이스 충돌을 방지하기 위하여 패키지 설정이 필요합니다.

package example;
 

3. 메세지 정의

   a. 메세지 구조를 정의하여 데이터가 포함할 필드와 타입을 정의합니다.

   b. 이 때, 필드 번호는 직렬화 시 필드 식별을 위한 값으로 하나의 메세지 내에서 Unique해야 합니다.

message Person {
  int32 id = 1;          // 정수형 ID (필드 번호 1)
  string name = 2;        // 이름 (필드 번호 2)
  string email = 3;       // 이메일 (필드 번호 3)
  bool is_active = 4;     // 활성화 여부 (필드 번호 4)
  repeated int32 scores = 5; // 여러 개의 정수형 점수 (필드 번호 5)
}

   c. 주요 데이터 타입(vs Java 자료형)

      1) Java와의 가장 큰 차이점은 부호 없는 정수 자료형(uint32, 64)이 따로 존재합니다.

      2) 바이너리 데이터의 경우에도 자바에서는 바이트 배열(byte[])을 사용합니다.


 

4. 서비스 정의

   a. gRPC 인터페이스를 정의하며, 이 때 rpc 메소드에 필요한 요청 답에 대해서도 메세지로 정의가 필요합니다.

service PersonService {
  rpc GetPerson (PersonRequest) returns (PersonResponse);
}
message PersonRequest {
  int32 id = 1;
}
message PersonResponse {
  Person person = 1;
}

 

2. gRPC 통신 방식: HTTP/2

gRPC는 HTTP/2 프로토콜을 기반으로 효율적인 네트워크 통신을 제공합니다. HTTP/2가 HTTP/1.1과 다른 주요 특징과 gRPC 통신에서 어떤 역할을 하는지 그리고 살펴보겠습니다.

출처: gRPC 시작에서 운영까지(카순 인드라시리)

 

  1. HTTP/1.1과의 주요 차이점

 

a. 멀티플렉싱

  1. HTTP/2 하나의 연결로 여러 요청과 응답을 동시에 처리
  2. TCP 연결 비용 최소화하여 네트워크 성능 향상
  3. 클라이언트가 여러 개의 gRPC 메서드를 동시 호출하는 경우에도 단일 연결로 처리합니다.

b. 서버 푸시

  1. HTTP/1.1에서는 클라이언트 요청에만 서버가 응답할 수 있지만, HTTP/2에서는 서버가 능동적으로 데이터를 전송할 수 있습니다.
  2. gRPC의 서버 스트리밍은 이런 서버 푸시 기능을 활용하여 실시간 데이터 전송이 가능합니다.

c. 바이너리 프레이밍(Binary Framing)

  1. HTTP/2는 바이너리 프레임으로 데이터를 전송하기 때문에 텍스트 기반의 HTTP/1.1보다 더 효율적인 처리가 가능합니다.
  2. Protobuf의 바이너리 데이터를 사용하기 때문에 전송 속도가 극대화됩니다.
     

3. gRPC 4가지 통신 패턴

HTTP/2의 이러한 특징 덕분에 gRPC는 네트워크 통신에서 4가지 패턴을 제공합니다.

 

1. 단일(Unary) RPC

출처: gRPC 시작에서 운영까지(카순 인드라시리)

  a. REST와 마찬가지로 1:1 요청-응답 방식입니다.

  b. Unary RPC의 경우에도 HTTP/2 멀티플렉싱을 활용하므로, 여러 개의 요청을 하나의 TCP 연결로 처리할 수 있습니다.

 

 
 

2. 서버 스트리밍 RPC

출처: gRPC 시작에서 운영까지(카순 인드라시리)

 

 

 

  a. 클라이언트 단일 요청에 대해 서버가 여러 개의 응답을 순차적으로 스트리밍합니다.

  b. HTTP/2 서버 푸시와 유사하며, 로그나 실시간 데이터 전송에 유리합니다.

 

3. 클라이언트 스트리밍 RPC

출처: gRPC 시작에서 운영까지(카순 인드라시리)

 

  a. 클라이언트가 여러 데이터를 순차적으로 전송하고, 서버가 한 번의 응답을 반환합니다.

  b. 대용량 데이터 업로드 시 유용합니다.

 

4. 양방향 스트리밍 RPC

출처: gRPC 시작에서 운영까지(카순 인드라시리)

 

  a. 클라이언트와 서버가 동시에 데이터를 주고받을 수 있는 양방향 스트리밍을 제공합니다.

  b. HTTP/2의 비동기 멀티플렉싱 덕분에 순서에 구애받지 않고 데이터를 주고받을 수 있습니다.

  c. 실시간 채팅 등 지속적인 데이터 교환 시 유리합니다.

 


gRPC 스트리밍 패턴의 경우 데이터 연결을 지속적으로 유지하기 때문에 멀티 파드 환경에서는 아래와 같이 추가적인 고려 사항에 대한 검토가 필요합니다.


멀티 파드 환경에서 고려 사항

  • 부하 분산 이슈
    • 특정 파드로 스트림이 유지되며 트래픽 집중 발생
    • 클라이언트 재연결 시 파드 교체 이슈
  • 스트리밍 중단 시 재연결 및 복원 처리 이슈
    • 재연결 시에도 데이터 일관성 유지 필요
  • 파드 간 상태 공유 이슈



멀티 파드 환경에서는 추가로 고려할 사항이 많기 때문에 현재 Unary 패턴으로만 gRPC를 사용하고 있습니다. 그러나 HTTP/2 멀티플렉싱 기능에 의해 기존 REST에 비해 여전히 향상된 성능을 기대할 수 있습니다.
추후 실시간 데이터 처리나 특수한 통신 요구사항이 발생할 경우, 스트리밍 패턴의 제한적 사용에 대해서도 고려해 볼 수 있을 것 같습니다.

 

 

 

관련글