백엔드 엔지니어 이재혁
gRPC란? 본문
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라는 객체를 통해 네트워크 통신을 감싸준다. 마치 일반 함수 호출처럼 사용하지만, 내부적으로는 다음과 같은 과정이 일어난다.
- Stub의 메서드 호출
→ 개발자는 client.getUser(request)처럼 로컬 함수 호출 방식으로 사용 - Protocol Buffers 직렬화
→ 입력 데이터를 바이너리로 변환 - HTTP/2로 서버에 전송
- 서버에서 함수 실행 후 응답 반환
- 클라이언트에서 응답 역직렬화
→ 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 정의를 일치시키기 위한 전략들
- 공통 레파지토리: 서버와 클라이언트에 git submodule로 가져오는 방법
- proto 파일 빌드 및 배포(npm, maven, pip 등): 변경 사항 발생시 패키지 버전 올리고 각 서비스에서 업데이트
- 원격 참조: proto 파일을 원격 URL에서 직접 참조하는 기능을 지원하는 프레임워크 사용 (dotnet-grpc 등)
gRPC를 사용하면 좋을 때
gRPC는 마이크로서비스 아키텍처에서 내부 통신에 많이 사용되고, 고성능/저지연이 중요한 서비스에 쓰기 좋다.
반면, REST API는 외부 공개 API 같이 좀 더 범용적인 지원이 중요할 때 많이 사용한다.
'CS' 카테고리의 다른 글
| Thread와 스케쥴링 (0) | 2025.05.19 |
|---|