gRPC 정리하기

연어·2024년 12월 8일
0

dev

목록 보기
6/6
post-thumbnail

gRPC

구글에서 개발한 RPC 프레임워크로, 네트워크를 통해서 다른 시스템의 함수를 호출할 수 있게 해 준다. 클라이언트 애플리케이션은 다른 머신의 서버 애플리케이션의 함수를 로컬 함수처럼 호출해 사용할 수 있다.
클라이언트와 서버는 다양한 환경으로 구성되어 실행하고 통신할 수 있다. Java 로 gRPC 서버를 만들고 Go, Python 등으로 gRPC 클라이언트를 만들 수 있는 등, gRPC 에서 지원하는 모든 언어로 다양하게 환경을 구성할 수 있다.

핵심 개념

RPC (Remote Procedure Call)

로컬에서 함수를 호출하듯이, 다른 서버의 함수를 호출할 수 있게 하는 기술.
예를 들어, A서버에서 B서버의 calculate(a, b)를 호출하면, 네트워크를 통해 통신해서 결과를 받을 수 있다.

Protocol Buffers (Protobuf)

gRPC는 데이터를 직렬화 및 역직렬화 하기 위해 Protocol Buffers 라는 포맷을 사용한다. Protocol Buffers 는 JSON이나 XML보다 더 빠르고 작은 크기로 데이터를 처리한다.
.proto파일로 메세지와 서비스를 정의하면 정의한 내용을 바탕으로 프로토콜 버퍼 컴파일러(protoc)를 통해 각 언어에 맞게 사용할 수 있는 코드가 자동으로 생성된다.

Proto3 버전은 더 많은 언어 지원과 새로운 기능이 추가되었다.
일반적으로 현재 기본 프로토콜 버퍼 버전인 proto2를 사용할 수 있지만, gRPC 사용 시 proto3를 사용하는 것을 추천한다고 안내하고 있다. proto3는 gRPC에서 지원하는 모든 언어를 사용할 수 있고, proto2proto3간의 통신에 대한 호환성 문제를 피할 수 있다.

HTTP/2 기반

gRPC는 HTTP/2 프로토콜을 사용해 기존 HTTP/1.1보다 빠른 통신과 효율적인 데이터 전송을 제공한다.
HTTP/1.1은 하나의 요청-응답만 처리할 수 있다. 요청마다 별도의 TCP 커넥션을 열어야 하고, 추가 요청은 이전 요청이 끝나야 처리할 수 있다.
HTTP/2는 다중화(멀티플렉싱)를 지원해 하나의 TCP 연결로 여러 요청과 응답을 동시에 처리할 수 있다. 또 헤더 압축, 서버 푸시, 스트림 지원 등의 기능을 통해 대역폭을 효율적으로 사용하고 지연 시간을 줄인다.

  • 멀티플렉싱
    하나의 TCP 연결로 여러 요청과 응답을 주고 받을 수 있는 기술.
    만약 a, b, c 세 개의 요청을 보냈다면 a 가 끝날 때까지 b, c가 기다리지 않고 병렬 처리된다.
    지연 시간 감소와 네트워크 효율성을 증가시킬 수 있다.

  • 헤더 압축
    HTTP/1.1 은 요청마다 헤더를 전송해 중복 데이터가 많다. HTTP/2HPACK 방식으로 헤더를 압축해 전송 데이터를 줄인다.
    데이터 전송량이 감소하며 속도가 향상된다.

  • 스트림 지원
    스트림이라는 독립적인 데이터 채널을 통해서 요청과 응답을 처리한다.
    각 스트림은 순서와 우선순위를 가지고 있어 네트워크 자원을 효과적으로 사용한다.
    요청이 많더라도 네트워크 충돌 없이 유연한 처리가 가능하다.

  • 서버 푸시
    클라이언트가 요청하지 않아도 서버가 데이터를 클라이언트에게 전송할 수 있는 방식. 클라이언트가 추가 요청을 보내기 전에 필요한 리소스를 받을 수 있어 지연 시간이 감소하고 불필요한 요청을 줄여 네트워크의 자원을 절약한다.

    ex) 클라이언트가 웹 페이지의 html 을 요청하면 서버가 html 뿐만 아니라 해당 페이지에 필요한 css, javascript 파일도 미리 전송할 수 있다. 클라이언트는 필요한 파일을 기다릴 필요가 없어 페이지 로드 시간이 단축된다.

다양한 통신 방식

  • 단일 요청 및 응답
    일반적인 함수 호출처럼 요청에 대한 응답을 받는다.

    rpc SayHello(HelloRequest) returns (HelloResponse);
  • 서버 스트리밍
    클라이언트가 요청하면 서버에서 여러 응답을 스트리밍으로 보낸다.
    클라이언트가 요청을 보내면 서버가 여러 개의 응답을 순차적으로 스트리밍한다. 개별 RPC 호출 내에서 메세지 순서를 보장하며 클라이언트는 더이상 메세지가 없을 때까지 반환된 스트림을 읽는다.
    ex) 영화 스트리밍 서비스에서 클라이언트가 한번 요청하면 서버는 영화 데이터를 지속적으로 보낸다.

    rpc LotsOfReplies(HelloRequest) returns (stream HelloResponse);
  • 클라이언트 스트리밍
    클라이언트가 여러 데이터를 보내고 서버는 단일 응답을 보낸다. 개별 RPC 호출 내에서 메세지 순서를 보장한다.
    ex) 클라이언트가 센서 데이터를 실시간으로 서버에 전송한다.

    rpc LotsOfReplies(stream HelloRequest) returns (HelloResponse);
  • 양방향 스트리밍
    클라이언트와 서버가 데이터를 동시에 주고 받는다. 서버는 응답하기 전에 모든 클라이언트 메세지를 수신할 때까지 기다리거나, 메세지를 읽고 쓰기를 번갈아 가거나, 읽기와 쓰기를 조합할 수 있다. 각 스트림의 메세지 순서는 유지된다.

    rpc BidHello(stream HelloRequest) returns (stream HelloResponse);

스트리밍
데이터를 한번에 전송하는 대신, 일정한 간격으로 나누어 전송하는 방식

Stub

클라이언트와 서버 간 통신을 간단하게 만들어주는 자동 생성되는 코드로,
Stub을 통해 클라이언트는 원격 서버의 함수를 로컬 함수처럼 호출할 수 있고 네트워크 관련한 복잡한 처리를 직접 구현하지 않아도 된다.
Stub은 클라이언트와 서버 간의 중간 인터페이스 역할을 한다.

  • 클라이언트 Stub
    • 서버의 함수 호출을 로컬 함수 호출처럼 사용할 수 있게 해주는 인터페이스
    • 클라이언트는 Stub을 통해서 네트워크 요청 및 응답 처리를 자동으로 수행한다.
    • 원격 호출 과정은 Stub내부에서 처리된다.
  • 서버 Stub
    • 클라이언트 요청을 받아 해당 로직을 실행하고 응답을 보내는 역할을 한다.
    • 클라이언트로부터 받은 데이터를 역직렬화 해 실제 서버 함수로 전달한다. 함수 실행 후에는 결과를 직렬화 해 응답한다.

동작 과정

  1. .proto 파일 작성 - 서비스 정의 및 요청/응답 메세지 구조 작성
syntac = "proto3";

message PersonRequest {
	int32 id = 1;
}

message PersonResponse {
	string name = 1;
    int32 id = 2;
    string email = 3;
}

service PersonService {
	rpc GetPerson (PersonRequest) returns (PersonREsponse);
}
  1. protoc 를 사용해 클라이언트와 서버의 Stub 코드 자동 생성
  • ex) 클라이언트 : PersonServiceGrpc.PersonServiceBlockingStub
  • ex) 서버 : PersonServiceGrpc.PersonServiceImplBase
  1. 클라이언트 Stub호출
    Stub을 통해 서버의 함수를 호출하고, 요청 데이터를 직렬화 해 네트워크로 전송

  2. 서버 Stub 처리
    클라이언트로부터 요청 데이터를 받아 디코딩, 실제 구현된 함수 로직을 수행하고 결과를 반환

Stub 의 종류

종류동작
Blocking Stub동기 방식으로 동작.
Async Stub비동기 방식으로 동작.
서버의 응답을 기다리지 않고 요청을 보낸 후, 콜백 함수를 통해 결과 처리
Future Stub비동기 호출의 결과를 Future객체로 반환.
클라이언트가 나중에 결과 확인 가능

장점과 단점

gRPC는 Protocol BuffersHTTP/2 덕분에 더 빠르고 효율적이다. Java, Python, Go, C++ 등 다양한 언어로 사용 가능하며, .proto 파일로 자동화된 코드로 클라이언트와 서버 코드를 쉽게 생성한다. 또 단일 요청부터 양방향 스트리밍까지 다양한 통신을 지원하고 있어 각각의 서비스 요구사항에 맞게 설계할 수 있어 유연성이 높다.

하지만 gRPC를 활용하기 위한 추가 학습이 필요하다. 단순 CRUD 서비스에서는 과도한 도입일 수 있다. 브라우저는 웹소켓이나 RESTful API 를 통해야 사용이 가능한데, 브라우저 클라이언트와의 직접 통신이 필요한 경우 REST보다 불편할 수 있다. 또 grpccurl, Postman 등의 추가 도구 없이는 디버깅이 어렵다.

RESTful API 와 gRPC의 차이

기능RESTful APIgRPC
통신 프로토콜HTTP/1.1HTTP/2
데이터 형식JSONProtocol Buffers (바이너리 형식)
성능텍스트 기반으로 상대적으로 느림바이너리 기반으로 빠르고 효율적
코드 생성직접 작성 필요.proto 파일로 자동 생성
다중 언어 지원언어간 표준 없음다양한 언어에서 동일한 방식으로 사용 가능
유연성단일 요청-응답스트리밍, 양방향 통신 등의 다양한 방식 지원

gRPC 공식 FAQ에 의하면, gRPC가 REST와 비교해 아래와 같이 기술하고 있다.

Why is gRPC better/worse than REST?
gRPC largely follows HTTP semantics over HTTP/2 but we explicitly allow for full-duplex streaming. We diverge from typical REST conventions as we use static paths for performance reasons during call dispatch as parsing call parameters from paths, query parameters and payload body adds latency and complexity. We have also formalized a set of errors that we believe are more directly applicable to API use cases than the HTTP status codes.

HTTP/2 기반의 HTTP 시맨틱

gRPC는 HTTP/2 기반으로 동작하며, HTTP 의 기본적인 동작 원칙을 따른다.
하지만 gRPC는 풀-듀플렉스 스트리밍(클라이언트와 서버가 동시에 데이터를 주고받을 수 있는 기술, 양방향 스트리밍) 을 명시적으로 지원한다.

REST의 전형적인 관례

gRPC는 정적 경로(static path)를 사용한다. 이는 Call Dispatch 의 성능을 높이기 위해서다.

Call Dispatch
클라이언트 요청을 서버에서 적절한 서비스나 메서드로 매핑하는 방식

  • REST: 동적 경로를 파싱하고 경로 및 매개변수에 따라 호출할 핸들러를 찾는다.
  • gRPC: 정적 경로를 사용해 추가적인 파싱 과정 없잉 서비스와 메서드를 즉시 매핑한다.

정적 경로는 고정된 URL 형식을 사용한다. 경로의 구조가 일정하고 요청마다 변하지 않는다. gRPC는 서비스 호출 경로를 .proto파일에 정의된 고정 경로를 사용한다. 서비스 이름과 메서드 이름으로 구성되며 요청 데이터는 페이로드로 전달된다.

service Calculator {
	rpc Add (AddRequest) returns (AddResponse);
}

// 경로 : /Calculator/Add

동적 경로는 경로에 변수나 매개변수를 포함할 수 있다.

/users/{id}/orders/{orderId}

REST는 경로, 쿼리 매개변수, 페이로드에서 데이터를 파싱하는데, 이러한 방식은 지연 시간과 복잡성을 증가시킬 수 있어 gRPC는 REST의 관례를 따르지 않는다.

표준화된 에러 처리 방식

gRPC는 API 사용 사례에 더 적합하다고 판단한 표준화된 에러 세트를 정의해 사용한다.
REST에서 일반적으로 사용하는 HTTP 상태 코드보다 더 직관적이고 API에 적합한 에러 코드 체계를 제공한다.

REST 의 HTTP 상태 코드는 gRPC의 에러 코드와 아래와 같이 매핑할 수 있다.

  • 400 Bad Request -> INVALID_ARGUMENT
  • 401 Unauthorized -> UNAUTHENTICATED
  • 403 Forbidden -> PERMISSION_DENIED
  • 404 Not Found -> NOT_FOUND
  • 500 Internal Server Error -> INTERNAL

특히 HTTP 상태 코드 500은 gRPC에서 INTERNAL, UNAVAILABLE, DATA_LOSS 등으로 더 명확히 구분할 수 있다.


gRPC 는 낮은 지연 시간과 높은 확장성으로 분산 시스템 및 MSA 구조에서 효율적으로 활용할 수 있다. 단순한 구조의 시스템에서 적용하기에 과도할 수 있지만, 여러 서비스 간 통신이 빈번하고 대규모 서비스 간의 통신을 효율적으로 다루기 위해서 다양한 설계 방식을 적용하는 데에 최적화 할 수 있는 기술로 활용할 수 있다.

실제 업무에 도입해서 사용했을 때 크게 와닿았던 부분은 엄격한 인터페이스 관리가 가능하다는 점이었다. 서버와 클라이언트가 .proto 파일에 정의된 내용이 동일하지 않으면 (필드 번호가 변경되거나, 데이터 타입이 변경되는 등) Stub 메서드 호출 시점에 에러가 발생한다. 당시에 프로젝트를 진행하면서 배포와 픽스를 반복하며 속도감 있게 진행했는데, 반영된 변경사항을 조금 더 빠르게 인지할 수 있었고 하나의 파일로 정의된 작업 내용을 파악할 수 있어 업무 소통에 유연함을 더할 수 있었다. 처음 .proto파일을 작성하는 것이 처음에는 꽤나 귀찮(...)았지만, 활용에 익숙해지면서 gRPC 동작 방식과 핵심 개념, 이점을 이론적으로 이해하고 기술의 목적에 기반해 설계에 맞게 적용할 수 있는 스킬을 갖추기 위해 정리하였다.

참고

https://grpc.io/docs/

profile
끄적이는 개발자

0개의 댓글