관리 메뉴

백엔드 엔지니어 이재혁

gRPC란? 본문

CS

gRPC란?

alex00728 2025. 7. 11. 17:52

gRPC는 Google이 개발한 RPC(Remote Procedure Call) 프레임워크다.

RPC는 원격에서 프로시저를 호출하는 기술인데, 원격지에 있는 함수를 마치 로컬에서 호출하는 것처럼 사용할 수 있게 해준다.

 

gRPC의 주요 특징

  • HTTP/2 기반 통신: 양방향 스트리밍, 멀티플렉싱, 헤더 압축 등 최신 웹 기술 지원
  • Protocol Buffers(proto): JSON보다 빠르고 가벼운 바이너리 직렬화 포맷
  • Stub(스텁) 자동 생성: proto 파일만 작성하면 각 언어별로 클라이언트/서버 코드가 자동 생성됨
  • 다양한 언어 지원: Node.js, Java, Go, Python 등 여러 언어에서 사용 가능
  • 보안 및 인증: SSL/TLS 등 기본 지원

 

gRPC가 "로컬 함수처럼" 동작하는 원리

gRPC는 Stub라는 객체를 통해 네트워크 통신을 감싸준다. 마치 일반 함수 호출처럼 사용하지만, 내부적으로는 다음과 같은 과정이 일어난다.

  1. Stub의 메서드 호출
    → 개발자는 client.getUser(request)처럼 로컬 함수 호출 방식으로 사용
  2. Protocol Buffers 직렬화
    → 입력 데이터를 바이너리로 변환
  3. HTTP/2로 서버에 전송
  4. 서버에서 함수 실행 후 응답 반환
  5. 클라이언트에서 응답 역직렬화
    → Stub가 응답을 받아 다시 객체로 변환

이 모든 과정이 Stub 내부에서 처리되기 때문에, 네트워크나 직렬화/역직렬화 코드를 직접 작성할 필요가 없다.

 

node.js로 gRPC 간단히 사용해보기

1. node.js용 grpc 패키지 설치

`npm install @grpc/grpc-js @grpc/proto-loader`

 

2. proto 파일을 작성해 프로토콜 정의

`proto/service.proto`

syntax = "proto3";

// 패키지 정의 - Java 패키지 / C# 네임스페이스와 유사
package users;

// 서비스 정의 - 함수를 정의하는 것과 유사
service UserService {
  rpc GetUser (UserRequest) returns (UserResponse);
}

// 메시지 정의 - DTO 정의와 유사
message UserRequest {
  string userId = 1;
}

message UserResponse {
  // 데이터타입 이름 = 태그 번호(순서)
  string userId = 1;
  string name = 3;
  int32 age = 2; // int가 아니라 int32/int64를 사용한다.
  // grpc proto 데이터 타입은 일반적인 프로그래밍 언어의 데이터 타입과 차이가 있다. 
  
  // 이번 예제에는 age가 태그 번호 2, name이 태그 번호 3이라고 정의되어 있다. 
  // 필드가 정의된 순서와 무관하게 태그 번호로 정의한 숫자가 반영된다.
  // 역직렬화 출력 결과는 최종 결과에서 확인해보자.
}

 

3. gRPC 서버 작성

`server.js`

const grpc = require('@grpc/grpc-js');
const protoLoader = require('@grpc/proto-loader');

const packageDefinition = protoLoader.loadSync('proto/service.proto');
const usersProto = grpc.loadPackageDefinition(packageDefinition).users;

const server = new grpc.Server();

const userData = {
  "1": { userId: "1", name: "Alice", age: 25 },
  "2": { userId: "2", name: "Bob", age: 30 }
};

server.addService(usersProto.UserService.service, {
  GetUser: (call, callback) => {
    const user = userData[call.request.userId];
    if (user) {
      callback(null, user);
    } else {
      callback({ code: grpc.status.NOT_FOUND, details: "User not found" });
    }
  }
});

// 단순한 테스트 구성을 위해 createInsecure() 함수 사용
// 실무에서 사용할 때는 TSL 인증서를 적용하는 방식 권장. createSsl() 함수
server.bindAsync('127.0.0.1:50051', grpc.ServerCredentials.createInsecure(), () => {
  console.log('gRPC server running on port 50051');
});

 

4. gRPC 서버를 이용하는 클라이언트 작성

`client.js`

const grpc = require('@grpc/grpc-js');
const protoLoader = require('@grpc/proto-loader');

const packageDefinition = protoLoader.loadSync('proto/service.proto');
const usersProto = grpc.loadPackageDefinition(packageDefinition).users;

// 단순한 테스트 구성을 위해 createInsecure() 함수 사용
const client = new usersProto.UserService('127.0.0.1:50051', grpc.credentials.createInsecure());

client.GetUser({ userId: "1" }, (error, response) => {
  if (error) {
    console.error(error);
  } else {
    console.log('User:', response);
  }
});

 

실행 확인

1. 서버 실행: `node server.js`

2. 클라이언트 실행: `node client.js`

3. 결과: `User: { userId: '1', age: 25, name: 'Alice' }`

 

gRPC와 REST API의 차이

  gRPC REST API
프로토콜 HTTP/2 HTTP/1.1 등
데이터 포멧 Protocol Buffers (바이너리) JSON (텍스트 기반)
코드 구조 함수 호출 방식, Stub 자동 생성 엔드포인트(URL)와 HTTP 메서드
성능 빠르고 효율적, 스트리밍 지원 상대적으로 느림
사용 용도 마이크로서비스 내부 통신, 실시간 서비스 외부 API, 웹/모바일 통신

 

gRPC와 REST API의 직렬화/역직렬화 비교

  • REST API: 직렬화 시 문자열(JSON) 생성

예시: `JSON.stringify({ userId: "1" });`

→ 객체가 문자열(JSON)로 변환되어 서버에 요청할 때 body에 포함된다.

 

사람이 읽을 수 있는 텍스트이므로, 브라우저 개발자 도구에서 쉽게 확인 가능하다.

대신 문자열을 전송하는 방식이기에 데이터 크기가 비교적 크다.

 

  • gRPC: 직렬화시 바이너리 데이터 생성

예시: `client.GetUser({ userId: "1" }, func)`

→ 내부적으로 Protocol Buffers로 바이너리 데이터로 직렬화된다.

 

바이너리 데이터는 사람이 직접 읽기 어렵다.

바이너리 데이터를 전송하기 때문에 데이터 크기가 작고, 전송 및 파싱 속도가 빠르다.

 

대신 gRPC 통신에서는 proto 정의를 일치시키는 것이 매우 중요하다.

gRPC는 데이터를 바이너리로 직렬화하는데, 이 바이너리 데이터는 proto 정의에 따라 필드의 순서, 타입, 태그 번호 등으로 엄격하게 인코딩된다. proto 정의가 다르면, 같은 바이너리도 서버와 클라이언트가 전혀 다르게 해석할 수 있다.

 

proto 정의 일치시키기

이번 예제에서는 간단히 같은 파일을 공유하는 방식을 사용했지만,

서버와 클라이언트가 개별 서버에 올라가는 경우에는 활용하기 어려운 방식이다.

 

proto 정의를 일치시키기 위한 전략들

  1. 공통 레파지토리: 서버와 클라이언트에 git submodule로 가져오는 방법
  2. proto 파일 빌드 및 배포(npm, maven, pip 등): 변경 사항 발생시 패키지 버전 올리고 각 서비스에서 업데이트
  3. 원격 참조: proto 파일을 원격 URL에서 직접 참조하는 기능을 지원하는 프레임워크 사용 (dotnet-grpc 등)

 

gRPC를 사용하면 좋을 때

gRPC마이크로서비스 아키텍처에서 내부 통신에 많이 사용되고, 고성능/저지연이 중요한 서비스에 쓰기 좋다.

반면, REST API는 외부 공개 API 같이 좀 더 범용적인 지원이 중요할 때 많이 사용한다.

 

'CS' 카테고리의 다른 글

Thread와 스케쥴링  (0) 2025.05.19