- 이직 했다 파릇한 한달차임
회사 코드 파악하다가 rpc 처음 접해봄 + stub 메서드만 호출해도 API가 알아서 호출됨 을 겪고
-> 내부에서 뭐 어떻게 되는건지 궁금하기도 해서 겸사겸사 파악한거 안까먹게 가볍게 정리해보기
RPC (Remote Procedure Call)
네트워크 통신(HTTP, 요청/응답, 직렬화 등)을 숨기고 로컬함수 호출처럼 보이도록 추상화한 개념
= 원격호출을 함수호출처럼 취급하자 라는 아이디어
gRPC
그 아이디어를 실제 동작하게 만드는 구현체
구글이 아래 기술 조합을 묶어 RPC 개념을 구현해 제공하는 프레임워크
-
전송방식 : HTTP/2
-
직렬화방식 : Protocol Buffers(Protobuf)
-
인터페이스 정의 방식 : proto 언어로 message(데이터 구조)/ service(api 목록 + 요청응답 구조) 스펙 정의
-
호출 방식 : proto 파일 기반으로 자동 생성된 stub 통해 RPC 호출
Protocol Buffers(Protobuf)
구글이 만든 데이터 직렬화 기술
gRPC 가 기본 직렬화 방식으로 protobuf 씀 = protobuf 가 데이터를 binary 로 만들고 gRPC 가 그걸 운반함
구성요소
-
proto : idl (Interface Definition Language) 중 하나
- 이 언어로 작성한 산출물
.proto파일 : 기계가 읽는, 코드 생성을 위한 스펙문서
= 이 gRPC 서비스랑 데이터는 이렇게 생겼어 이 스펙대로 서버, 클라이언트 코드 만들면돼
- 이 언어로 작성한 산출물
-
protoc : 코드 생성기
-
proto 파일을 읽어 실제 소스코드(Java/Go/Python 등) 자동 생성
(bytecode 변환은 각 언어의 컴파일러(javac 등)가 그대로 담당) -
생성 대상
- 메시지 클래스
- 서버용 추상클래스 ImplBase
- 클라이언트용 Stub
-
-
런타임 라이브러리 (protobuf runtime) : 생성된 코드가 실제로 동작하도록 지원하는 엔진
-
메시지 직렬화/역직렬화 (객체 <-> binary buffer), 필드 관리, 타입 검증, 빌더 로직 담당
-
언어별로 별도 제공됨
-
-
binary buffer : protobuf의 binary format을 사용해서 직렬화한 실제 결과물
- byte[] 형태의 데이터로, 불필요한 텍스트 없고 스키마 고정되어 있음 (= 타입 추론없이 바로 파싱 가능) => 용량 작고 파싱 속도 빠름
gRPC 전체 Flow
1. 개발자가 proto 정의
message, service 스펙을 proto 파일에 정의
↓ protoc : 코드 생성
2. 자동 생성된 소스코드
ProductServiceGrpc.java 안에 여러클래스 중첩
- 메시지 클래스
ProductRequest,ProductResponse - 서버용 베이스클래스
ProductServiceGrpc.ProductServiceImplBase: 단순히 API 틀 만들어주는 역할 -> 서버가 상속해서 실제 비즈니스 로직 구현하는 추상클래스 - 클라이언트용 대리 호출자
ProductServiceGrpc.ProductServiceBlockingStub: client에서 API 호출 시 RPC 호출을 gRPC 런타임에 위임하는 프록시
↓ javac : 컴파일
.class
↓ gradle : 패키징
idl jar
↓ gradle : 업로드 = artifact 배포
Nexus 에
3. 서버, 클라이언트에서 사용
서버 : idl jar dependency 통해 ProductServiceImplBase 상속해서 비즈니스 로직만 구현
↕
gRPC 런타임 : 통신 관리자 (네트워크 통신, 직렬화, HTTP/2, 연결/스트림 관리)
↕
클라이언트 : idl jar dependency 통해 jar 에 포함된 stub 사용해 원격서버 호출
(코드에선 단순 메서드호출 같아보이게 하는 프록시 = 실제로는 RPC 호출을 가로채 gRPC 런타임에 위임하는 프록시)
참고)
Proxy (대리 객체 개념 전체)
│
┌─────────┴─────────┐
│ │
Smart Proxy Remote Proxy
(호출 전/후 기능) (원격 호출)
│ │
AOP Proxy gRPC Stub
참고)
-
Spring Boot 환경에서는
net.devh:grpc-client-spring-boot-starter하나로 gRPC 런타임(직렬화, 통신 관련 의존성 : grpc-protobuf, grpc-stub, grpc-netty-shaded) 모두 제공함=> Spring Boot에서는 idl jar + gRPC starter 의존성만 추가하면 됨
gRPC 쓰면 뭐가 좋을까
gRPC 를 씀으로써 얻는 장점은?
1. 스펙 관리 장점
→ proto + IDL 버전 관리 + 컴파일 타임 검증
- API 스펙이 문서가 아니라 proto 코드 자체가 기준이 됨 = 컴파일 타임에 강제할 수 있음
-
proto로 요청·응답 구조를 명확히 정의
=> 서버·클라이언트 간 스펙 불일치 거의 없음 -
IDL을 버전으로 관리
=> 의존성 버전만 올려도 변경된 API 스펙을 코드로 바로 확인 가능
-
실제로 지금 코드 이관중인데, 프로젝트마다 사용하는 idl 버전이 달라서 코드 그대로 이관하니까 바로 컴파일 에러가 떠 변경을 알 수 있었다 별도 문서 안찾아도 되어서 편하긴 했음
2. 개발 생산성 장점
→ 코드 자동 생성 + 호출 모델 단순화
- 반복적인 API 코드 감소
- 서버/클라이언트 코드(메시지, 서버 베이스, 클라이언트 stub) 자동 생성
=> proto 변경 시 코드 재생성만으로 서버·클라이언트가 동일한 스펙으로 자동 동기화됨 => 생산성 향상
- 서버/클라이언트 코드(메시지, 서버 베이스, 클라이언트 stub) 자동 생성
- 호출 모델 단순화 (RPC)
- 네트워크 통신을 메서드 호출 형태로 추상화
=> HTTP 요청/응답, 직렬화, 에러 처리 코드가 비즈니스 로직에서 사라짐
=> 코드 상에서는 단순히 메서드 호출만 남아 호출 흐름 파악이 쉬움
- 네트워크 통신을 메서드 호출 형태로 추상화
실제로 클라이언트 쪽에서 그냥 stub 주입받아 메서드 호출하듯 하면 응답객체 그대로 쓸 수 있으니 편하긴 했음
다만, 내부 흐름이 한눈에 파악 안되다보니 rpc가 익숙하지않은 입장에서는 stub 은 갑자기 어디서 만들어진애지 하면서 지금의 나처럼 추가파악이 필요하지만
3. 성능 장점
→ Protobuf binary + HTTP/2
- Protobuf 기반 binary 직렬화
=> payload 작고 파싱 빠름, 내부 트래픽 환경에서 네트워크 비용 감소에 유리
4. 아키텍처 적합성
→ 내부 서비스 간 통신, 연결 재사용, 스트리밍
- 내부 서비스 간 통신에 적합
- 스펙 고정 + 코드 자동 생성 + 연결 재사용 조합으로
=> 서비스 수가 늘어날수록 관리 부담이 줄어듦
- 스펙 고정 + 코드 자동 생성 + 연결 재사용 조합으로
- 연결 재사용 (Channel) : 물리적 연결 관리 방식
- gRPC는 RPC 호출 단위가 아니라 Channel 단위로 서버와 연결을 관리함
- application 생명주기 동안 동일한 연결을 재사용하며 여러 RPC(unary 포함)를 처리함 = 매 요청마다 새로 연결을 여닫지 않음
- Spring Boot 환경에서는 gRPC starter가 채널 생명주기를 자동 관리함
- 스트리밍도 지원함
- gRPC는 기본적으로 단건 요청/응답(Unary RPC)을 사용하며, 필요할 경우 server / client / bidirectional streaming RPC를 선택적으로 정의할 수 있음
- 스트리밍 RPC에서는 하나의 RPC 호출이 열린 상태에서 메시지를 여러번 주고받을 수 있음
=> 실시간 처리나 대량 데이터 전송에서 REST 대비 구현이 단순하고 자연스러움- gRPC 스트리밍은 소켓처럼 연결을 유지하지만, 메시지 기반으로 구조화된 HTTP/2 스트리밍임
참고) gRPC 호출방식 (통신 패턴)
공통
- 모든 RPC 호출은 Channel(연결)을 재사용함
- RPC가 종료되어도 연결은 유지됨
Unary RPC (기본)
- 요청 1번 > 응답 1번
- 하나의 rpc 호출은 요청/응답 1회로 즉시 종료, 연결(channel)은 유지됨
- rest api 와 구조적으로 동일
Streaming RPC
- 하나의 RPC 호출이 열린 상태로 유지됨
- RPC 안에서 메시지를 연속적으로 전송할 수 있음
- Server Streaming RPC : 요청 1번 > 응답 여러 번 (서버가 여러 메시지를 순차적으로 내려줌)
- Client Streaming RPC : 요청 여러 번 > 응답 1번
- Bidirectional Streaming RPC : 요청/응답을 동시에 여러 번 주고받음 (메시지 흐름은 서로 독립적으로 진행됨)
정리해보면 gRPC의 장점은 단순히 성능보다는 스펙·호출·통신을 구조적으로 통제할 수 있다는 점?
내부 서비스 수 적거나 단순 CRUD 위주라거나 많이 호출 안하면 구조 대비 이점이 크지않을수도 있어보임
아직은 경험이 많지않아 단점까지 단정하긴 어렵지만, gRPC가 왜 내부 서비스 통신에서 자주 쓰이는지는 알 것 같다