Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 

Repository files navigation

gRPC / protobuf 실습 학습 가이드

학습 순서

01-hello-world  →  02-streaming  →  03-protobuf-bench
   (기본 구조)      (4가지 RPC)      (바이너리 원리)

전체 개념 지도

내 코드 (Go)
    │
    │  .proto 파일 정의
    ▼
protoc (컴파일러)
    │  .pb.go   → 메시지 직렬화/역직렬화 코드
    │  _grpc.pb.go → 서버/클라이언트 인터페이스 코드
    ▼
gRPC 런타임
    │
    │  Protocol Buffers (바이너리 직렬화)
    │  HTTP/2 (전송 프로토콜)
    ▼
네트워크

왜 RPC인가: 문제에서 시작하기

서비스 A에서 서비스 B의 함수를 호출하려면?

소켓 직접 쓰는 방법:

// 이걸 매번 써야 한다
conn, _ := net.Dial("tcp", "b:8080")
json.NewEncoder(conn).Encode(map[string]any{"name": "Mando"})
var resp map[string]any
json.NewDecoder(conn).Decode(&resp)

RPC 방법:

// 로컬 함수 호출처럼 쓴다
resp, _ := client.SayHello(ctx, &pb.HelloRequest{Name: "Mando"})

RPC(Remote Procedure Call)는 네트워크 통신을 함수 호출처럼 감추는 추상화 계층이다. 그 안에서 직렬화, TCP 연결, 에러 처리가 자동으로 일어난다.


Stub이란?

RPC의 핵심은 **Stub (프록시)**이다.

클라이언트 코드
    │
    ▼
Client Stub (자동 생성)
  - 인자를 바이트로 직렬화
  - 네트워크로 전송
  - 응답 바이트를 역직렬화해서 반환
    │
    │  ← 네트워크 →
    ▼
Server Stub (자동 생성)
  - 요청 바이트를 역직렬화
  - 실제 함수 호출
  - 결과를 바이트로 직렬화해서 반환
    │
    ▼
실제 서버 함수

gRPC에서 이 Stub 코드가 protoc가 자동 생성하는 _grpc.pb.go 파일이다.


gRPC = RPC + HTTP/2 + protobuf

역할 기술
RPC 프레임워크 gRPC
전송 프로토콜 HTTP/2
직렬화 포맷 Protocol Buffers

HTTP/2가 HTTP/1.1과 다른 핵심 3가지

1. 멀티플렉싱

HTTP/1.1:  요청1 → 응답1 → 요청2 → 응답2  (순서대로)
HTTP/2:    요청1 ┐
           요청2 ├→ 응답1
           요청3 ┘   응답3
                     응답2     (동시에, 하나의 TCP 연결)

하나의 TCP 연결에 여러 스트림을 multiplexing → Head-of-Line Blocking 해결

2. 헤더 압축 (HPACK)

HTTP/1.1: Content-Type: application/grpc\r\n (매번 전송, ASCII)
HTTP/2:   인덱스 번호 1바이트 (이미 협상된 헤더는 참조만)

3. 바이너리 프레임

HTTP/1.1: GET /path HTTP/1.1\r\nHeader: value\r\n\r\nbody  (텍스트)
HTTP/2:   [Length 3B][Type 1B][Flags 1B][StreamID 4B][Payload]  (바이너리)

프레임 타입:

  • DATA (0x0): 실제 요청/응답 바디
  • HEADERS (0x1): HTTP 헤더
  • SETTINGS (0x4): 연결 설정
  • WINDOW_UPDATE (0x8): 흐름 제어

gRPC 요청의 실제 HTTP/2 프레임 흐름

클라이언트                          서버
    │                               │
    │──HEADERS─────────────────────▶│  :method = POST
    │   (요청 헤더)                  │  :path = /hello.Greeter/SayHello
    │                               │  content-type = application/grpc
    │                               │  grpc-encoding = identity
    │                               │
    │──DATA (+ END_STREAM)─────────▶│  [5바이트 길이 접두어] + [protobuf body]
    │   (요청 바디)                  │
    │                               │
    │◀─HEADERS──────────────────────│  :status = 200
    │   (응답 헤더)                  │  content-type = application/grpc
    │                               │
    │◀─DATA─────────────────────────│  [5바이트 길이 접두어] + [protobuf body]
    │   (응답 바디)                  │
    │                               │
    │◀─HEADERS (Trailers)───────────│  grpc-status = 0
    │   (스트림 종료)                │  grpc-message = ""
    │                               │

Length Prefix (5바이트):

[Compressed-Flag 1B][Message-Length 4B][Message]
     0 = 미압축          바이트 수
     1 = gzip/deflate

Protocol Buffers 인코딩 원리

왜 JSON보다 작고 빠른가?

JSON:

{"name": "Mando", "age": 30}
→ 28바이트 (키 이름 포함, ASCII)

protobuf:

필드 1 (name): 0A 05 4D 61 6E 64 6F  → 7바이트
필드 2 (age):  10 1E                  → 2바이트
→ 합계 9바이트 (키 이름 없음, 바이너리)

Wire Type (전선 타입)

각 필드는 (field_number << 3) | wire_type 형태의 태그로 시작한다.

Wire Type 사용처
Varint 0 int32, int64, bool, enum
64-bit 1 fixed64, double
Length-delimited 2 string, bytes, message
32-bit 5 fixed32, float

Varint 인코딩

정수를 가변 길이로 저장한다. 작은 숫자는 1바이트, 큰 숫자는 여러 바이트.

규칙: 각 바이트의 최상위 비트(MSB) = 1이면 다음 바이트가 더 있다는 신호

숫자 1:    0000 0001  → 0x01 (1바이트)
숫자 300:  1010 1100  0000 0010  → 0xAC 0x02 (2바이트)
           ↑ MSB=1    ↑ MSB=0
           (계속)      (끝)

계산: 0xAC 0x02
  → 0xAC = 1010 1100, MSB 제거 → 010 1100
  → 0x02 = 0000 0010, MSB 제거 → 000 0010
  → 이어 붙이기 (Little-Endian): 000 0010 | 010 1100 = 0001 0010 1100 = 300

실제 인코딩 예시

message HelloRequest {
  string name = 1;  // 필드 번호 = 1
}

name = "Hi" 직렬화:

태그:  (1 << 3) | 2 = 0x0A  (필드 1, wire type 2 = length-delimited)
길이:  0x02              (2바이트)
값:    0x48 0x69         ("Hi" ASCII)

결과: 0A 02 48 69

실제로 확인하는 방법 (03-protobuf-bench에서 실습):

b, _ := proto.Marshal(&pb.HelloRequest{Name: "Hi"})
fmt.Printf("%X\n", b)  // 출력: 0A024869

초기 환경 설정

1. protoc 설치

# macOS
brew install protobuf

# 설치 확인
protoc --version  # libprotoc 27.x

2. Go 플러그인 설치

go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest

# PATH에 추가 (없으면)
export PATH="$PATH:$(go env GOPATH)/bin"

실습 시작

cd 01-hello-world
cat README.md        # 단계별 가이드 확인

About

gRPC practice in Go — hello-world, streaming, protobuf benchmark

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages