본 글에서는 gRPC에 대한 소개와 gRPC를 활용한 마이크로서비스에 대해서 이야기하려고 합니다. 필자는 gRPC가 공식적인 릴리즈(1.0)을 하기 전부터 제품에 적용해보면서, 실제로 높은 성능과 혁신적인 생산성을 직접 경험하였습니다. 특히 마이크로서비스의 구현에 매우 적합한 프레임워크임에도 아직 한국에는 널리 알려지지 않은 것 같아서 포스팅합니다.

What is gRPC?

Why should I use it?

높은 생산성과 효율적인 유지보수

자세한 예제는 뒷 부분에서 다시 다루지만, 위와 같은 서비스 정의만으로 서버와 클라이언트(Stub) 코드가 자동으로 생성됩니다. 위 예제의 경우는 sayHello()라는 서비스의 비지니스 로직과 클라이언트에서 Stub을 사용하여 호출해주는 부분만 구현해주면 됩니다.

다양한 언어와 플랫폼 지원

ProtoBuf의 IDL을 활용한 서비스 정의 한개로 다양한 언어와 플랫폼에서 동작하는 서버와 클라이언트 코드가 생성됩니다. 공식적으로 지원하는 언어 및 플랫폼은 Officially Supported Platforms를 참고하시면 됩니다.

HTTP/2 기반의 양방향 스트리밍

높은 메시지 압축률과 성능

다양한 gRPC 생태계

Who is using it?

When is it used?

gRPC가 MSA에 적합한 이유는 아래와 같습니다.

gRPC를 활용하면 비니지스 로직에 집중하여 빠른 서비스 개발이 가능하고, 간단한 설치와 빠른 배포가 가능합니다. 또한, 다양한 언어 및 플랫폼 지원으로 폴리글랏 언어와 기술스택을 지향하는 MSA의 철학과도 일맥상통합니다.

ProtoBuf가 지원하는 IDL 활용한 서비스 및 메시지 정의는 MSA의 다양한 기술 스택의 공존으로인한 중복 발생의 단점을 보완하고, 수많은 서비스간의 API 호출로 인한 성능 저하를 개선합니다.

MSA를 기반으로 하면서 분산처리를 위해서 필요한 Security, API Gateway, Tracing, Monitoring, Health Checking 등의 기능들을 Pluggable하게 포함하고 있습니다.

ProtoBuf에 의한 높은 메시지 압축률은 시스템 전체의 네트워크 트래픽을 획기적으로 줄여줍니다. 이것은 동일한 자원 제약에서 더 많은 서비스 인스턴스를 띄울 수 있다는 것을 의미합니다.

When is it not used?

gRPC의 튜토리얼이나 가이드는 상당히 쉽고 빠르게 습득이 가능합니다. 하지만 실제로 제품에 적용하고 운영할때는 ProtoBuf, HTTP2 등 이해해야 할 기반 기술들이 많습니다. 따라서 어느 정도의 Learning Curve는 각오해야 합니다.

How can I use it?

RPC System

물론 RPC라는 개념이 존재하기 전부터 소켓 프로그래밍을 통해서 네크워크 상에 존재하는 서비스를 호출할 수는 있었습니다. 그러나 네크워크 상의 원격 통신은 느려지거나 서버가 응답하지 않는 등의 다양한 장애가 발생할 수 있습니다. 소켓 프로그래밍으로 직접 구현한다면 네크워크에서 발생 가능한 다양한 예외상황들을 개발자가 직접 핸들링해야만 합니다.

RPC는 이러한 네크워크 통신과 관련된 작업들을 대신해 줍니다. 개발자는 원격 컴퓨터나 프로세스에 존재하는 함수를 동일 프로세스에 존재하는 함수를 호출하는 것 처럼 호출할 수 있습니다.

Protocol Buffers

// Person.proto
message Person {
string name = 1;
int32 id = 2;
bool has_ponycopter = 3;
}

IDL로 정의된 메시지를 기반으로 데이터 접근을 위한 코드를 생성하기 위해서 protoc 컴파일러를 사용합니다. 간단한 컴파일러 명령어로 원하는 언어의 코드를 생성할 수 있고, Person 클래스(자바의 경우)에 setter/getter 등 필요한 모든 메서드가 만들어 집니다.

위 예제는 간단한 gRPC helloworld 예제의 풀 버전입니다. proto 파일을 작성하기 위해서 여러가지 ProtoBuf 옵션 및 IDL에 대한 공부가 필요한 것을 알 수 있습니다. 자세한 내용은 Protocol Buffers 공식 사이트에서 확인할 수 있습니다. gRPC와 함께 사용하기 위한 문법은 proto3 가이드를 참고해야 합니다.

HTTP/2

HTTP/1은 기본적으로 클라이언트가 서버에 요청을 보내고, 서버가 요청에 대한 응답을 보내는 구조입니다. 따라서 요청 단위로 클라이언트와 서버를 왕복해야 합니다. 또한 쿠키를 포함한 헤더 크기는 불필요하게 큽니다. 이런 특징때문에 HTTP/1은 느립니다. 성능을 개선하기 위해서 구글은 SPDY를 개발하고, 이를 기반으로 HTTP/2 표준이 만들어집니다.

성능이 개선된 HTTP/2의 주요 특징은 아래와 같습니다.

Header Compression

Header Table과 Huffman Encoding 기법을 사용하여 HTTP/2 헤더정보를 압축하였습니다.

Multiplexed Streams

HTTP/1에서 요청마다 새로운 커넥션을 자주 만드는 것과는 달리 HTTP/2는 한개의 커넥션으로 동시에 여러개의 메시지를 주고 받을 수 있습니다.

Server Push

HTTP/2에서는 클라이언트의 요청없이도 서버가 리소스를 보낼 수 있습니다. 클라이언트 요청이 최소화되기 때문에 성능이 향상될 수 있습니다.

Stream Priority

요청에 우선순위를 지정하여 중요한 리소스를 먼저 전달받을 수 있습니다.

gRPC는 이런 HTTP/2의 특징을 기반으로 하기때문에 양방향 스트리밍이 가능하고, 기본적인 통신 속도가 빠릅니다. on-connection 상태에서 비동기 통신의 구현이 용이합니다.

gRPC Example

1) .proto 파일에 서비스를 정의합니다.

gRPC는 일반적인 RPC뿐만 아니라 server-to-client streaming RPC, client-to-server streaming RPC, bidirectional streaming RPC를 만들어서 사용할 수 있습니다.

2) 서버와 클라이언트 코드를 생성합니다.

코드를 생성하기 위해서는 ProtoBuf의 컴파일러인 protoc를 사용합니다. gradle이나 maven을 사용하고 있다면 protoc 빌드 플러그인을 사용하여 코드를 생성할 수 있습니다. 관련 내용은 README에 자세하게 설명되어 있습니다.

ProtoBuf 메시지 정의에 따라서 Feature.java, Point.java, Rectangle.java 등의 메시지 파일이 생성되고 각 필드에 대한 getter, setter, 직렬화 코드 등이 생성됩니다.

gRPC 서비스 정의에 따라서 RouteGuideGrpc.java 파일이 생성되고, RouteGuideGrpc.RouteGuideImplBase를 상속한 서비스 코드에 비지니스 코드를 작성하면 됩니다.

RouteGuide 서비스의 메서드를 호출하기 위한 클라이언트(stub) 코드들이 생성됩니다. 클라이언트에서 이 Stub을 사용하여 서비스의 함수를 호출하면 됩니다.

3) 서비스를 구현합니다.

4) 서버를 실행 코드를 작성합니다.

서비스의 비니지스 로직은 위에서 작성했지만, 서비스가 실행될 서버 코드가 필요합니다. 서버 코드에서는 포트를 설정하고, 작성된 서비스를 실행하기 위한 부분입니다. gRPC에서는 ServerBuilder를 사용하여 쉽게 서버 코드를 작성할 수 있습니다.

5) 클라이언트를 구현합니다.

gRPC에서는 클라이언트(Stub) 코드도 생성됩니다. 클라이언트에서 생성된 코드를 사용하여 gRPC Stub을 생성하고 서비스의 각 메서드를 호출하는 코드를 구현합니다.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store