gRPC에 관하여
업데이트:
목차
gRPC란?
원격 함수 호출(Remote Procedure Call, RPC)을 현대적으로 구현한 프레임워크입니다. -> “다른 서버에 있는 함수를, 마치 내 로컬 코드 처럼 호출하게 해주는 통신 방식” 이라고 생각하면 쉬울 것 같습니다. 즉, 네트워크 요청/응답을 직접 처리하지 않아도 서버의 함수를 그냥 코드로 부르듯 호출할 수 있게 만들어 주는 기술입니다.
예시 코드
아래는 간단한 덧셈(Add) 예제입니다.
서버는 JavaScript (Node.js), 클라이언트는 Java로 작성돼 있지만, 둘 다 같은 .proto 파일을 기반으로 완벽하게 통신합니다.
Server (JavaScript / Node.js)
import grpc from "@grpc/grpc-js";
import protoLoader from "@grpc/proto-loader";
const PROTO_PATH = "./add.proto";
const packageDef = protoLoader.loadSync(PROTO_PATH, {});
const grpcObject = grpc.loadPackageDefinition(packageDef);
const adderPackage = grpcObject.sample;
function add(call, callback) {
const result = call.request.a + call.request.b;
console.log(`Received: ${call.request.a} + ${call.request.b} = ${result}`);
callback(null, { result });
}
function main() {
const server = new grpc.Server();
server.addService(adderPackage.Adder.service, { Add: add });
server.bindAsync("0.0.0.0:50051", grpc.ServerCredentials.createInsecure(), () => {
server.start();
console.log("✅ gRPC Server running on port 50051");
});
}
main();
server.addService() 부분에서 실제 서비스(Adder)와 함수(Add)를 연결합니다.
클라이언트가 Add 함수를 호출하면, call.request.a + call.request.b를 계산해 응답을 보냅니다. HTTP나 JSON 처리를 직접 하지 않고, gRPC가 Protobuf 바이너리 포맷으로 데이터를 주고받습니다.
Client (Java)
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import sample.AdderGrpc;
import sample.AddRequest;
import sample.AddResponse;
public class GrpcClient {
public static void main(String[] args) {
// 서버와의 채널 생성
ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost", 50051)
.usePlaintext()
.build();
// gRPC 스텁 생성
AdderGrpc.AdderBlockingStub stub = AdderGrpc.newBlockingStub(channel);
// 요청 생성
AddRequest request = AddRequest.newBuilder()
.setA(5)
.setB(3)
.build();
// 서버 호출
AddResponse response = stub.add(request);
System.out.println("5 + 3 = " + response.getResult());
channel.shutdown();
}
}
ManagedChannel을 통해 Node.js 서버(localhost:50051)에 연결합니다.
AdderGrpc.newBlockingStub(channel)로 서버에 정의된 메서드를 “로컬 함수”처럼 쓸 수 있는 객체를 생성합니다.
stub.add(request)를 호출하면 gRPC가 자동으로 네트워크 요청을 만들어 서버의 Add 함수를 실행하고, 응답(AddResponse)을 받습니다.
gRPC는 그렇다면 왜? 사용이 되고 있을까?
gRPC를 지탱하는 핵심 기술에는 3가지가 있습니다.
- HTTP/2 기반 전송
- Protobuf (Protocol Buffers)
- 스트리밍 (Streaming)
1) HTTP/2 기반 전송
gRPC는 기본적으로 HTTP/2 위에서 동작하도록 설계되어 있습니다. 물론 REST API도 HTTP/2를 사용할 수 있지만, 대부분의 REST 프레임워크는 여전히 HTTP/1.1을 기본으로 하고 있습니다. 반면 gRPC는 HTTP/2의 기능(멀티플렉싱, 헤더 압축, 스트리밍)을 언어 수준에서 적극 활용하도록 만들어졌기 때문에 “요청 여러 개 / 응답 여러 개 / 실시간 양방향 스트리밍” 같은 고성능 통신 패턴에 더 자연스럽게 맞습니다.
-
하나의 TCP 연결에서 여러 요청을 동시에 보낼 수 있다. -> 동시 호출이 많은 마이크로서비스 환경에서 병목(헤드 오브 라인 블로킹) 줄어듦
- 헤더 압축 등으로 불필요한 중복 전송을 줄인다.
- 서버가 먼저 데이터를 푸시하거나(서버 스트리밍 등) 클라이언트/서버가 동시에 주고받는 양방향 스트리밍이 가능하다
2) Protobuf (Protocol Buffers)
gRPC는 메세지를 JSON이 아니라 바이너리 형태인 Protobuf로 직렬화합니다. 그래서 다음과 같은 특징을 가지고 있습니다.
- 직렬화/역직렬화가 매우 빠르다.
- 전송되는 Payload크기가 JSON보다 훨씬 작다. -> 모바일처럼 대역폭이 제한적인 환경, 내부 마이크로서비스 고QPS 환경에서 굉장히 유리하다.
3) 스트리밍 (Streaming)
gRPC는 4가지 호출 패턴을 지원한다.
- Unary 요청(REST 와 같다고 보면됨)
- Server streaming
- 클라이언트가 한 번 요청하면 서버가 여러 개의 응답을 스트리밍으로 계속 보냄.
- 실시간 로그 tail, 주가 스트림
- Client streaming
- 클라이언트가 여러 메시지를 연속으로 보내고 서버는 마지막에 한번에 응답
- 클라이언트가 큰 데이터를 조각내서 업로드
- Bidirectional streaming
- 양쪽이 동시에 계속 주고 받음(양방향 전이중 통신)
- 채팅, 실시간 게임 상태 동기화
REST/HTTP 1.1 세계에서는 이걸 구현하려면 WebSocket, SSE, 폴링 등 별도 채널을 또 만들어야 하는데 gRPC는 애초에 언어 차원에서 이 패턴을 네이티브 지원한다.
요약하면:
- 외부(클라이언트 다양함, 브라우저 포함)에게 열어줄 퍼블릭 API → REST가 편하다.
- 내부(마이크로서비스끼리 엄청 자주 호출, 성능 민감) → gRPC가 훨씬 낫다.
AWS와 여러 아티클에서도 “브라우저 지원 한계 때문에 퍼블릭 웹은 여전히 REST가 더 무난하지만, 서비스 내부/백엔드 간 통신은 gRPC가 고성능이라 각광받는다”고 정리하고 있다
실무에서 REST vs gRPC 선택 기준
| 상황 | 추천 | 이유 |
|---|---|---|
| 외부 Public API | REST | 브라우저 호환, 디버깅 용이 |
| 내부 서비스 간 통신 | gRPC | 성능, 타입 안정성 |
| 고트래픽 환경 (초당 수천~수만 요청) | gRPC | 낮은 레이턴시, 작은 페이로드 |
| 빠른 프로토타이핑 | REST | 별도 proto 정의 불필요 |
| 실시간 양방향 통신 | gRPC | Bidirectional streaming |
gRPC의 한계와 주의사항
- 브라우저 직접 지원 불가: gRPC-Web 프록시 필요
- 학습 곡선: .proto 작성, 코드 생성 과정 필요
- HTTP/2 필수: 일부 레거시 인프라에서 지원 안 될 수도
댓글남기기