gRPC

Yunes·2023년 9월 25일
0

gRPC

목록 보기
2/2
post-thumbnail

gRPC란

gRPC(Google Remote Procedure Call)

등장배경


  1. Server-Client Model

PC 개념이 없던 시절 프로그램은 하나의 메인 프레임에서 동작하는 구조(Monolothic) 로 설계되었으나 기술발전에 따라 고가의 메인 프레임워크를 저가의 워크스테이션 서버로 대체하고자 하는 요구가 생겼고 메인 프레임워크의 기능을 워크스테이션 서버로 분산시키고 네트워크 연결로 서비스하는 방식을 채택하게 되었다. 이 방식을 Server-Client Model 이라고 한다.

이처럼 서버간, 서버와 개인 PC 간 네트워크 연결/통신이 중요해지면서 네트워크 계층구조가 정의되고 발전하기 시작했다.

  1. IPC

프로세스들은 기본적으로 상호독립적이며 메모리를 공유하지 않고 각자 자신의 일을 하며 서로 간섭하지 않는다. 다만 필요에 따라 프로세스간 정보를 교환해야 하는 경우가 있어 별도 수단을 이용하여 프로세스 통신하는 방법론을 통칭하여 IPC(Inter Process Communication) 이라고 한다.

1) Socket

소켓은 OSI 7 layer 의 Application Layer(L7) 에서 Transport Port(L4) 의 TCP 또는 UDP 를 이용하기 위한 수단 이다. 소켓은 대부분의 언어에서 API 형태로 제공하는 편리함 때문에 지금도 많이 사용되나 일련의 통신 과정을 직접 구현하여 통신 관련 장애를 처리하는 것은 고스란히 개발자의 몫이 된다. 서비스가 고도화될수록 data formatting 을 하는 것도 점점 어려워지게 되었다.

2) RPC (Remote Procedure Call)

소켓의 한계가 느껴질때 RPC 라는 기술이 등장한다. 이름 그대로 네트워크로 연결된 서버 상의 프로시저(함수, 메서드 등)를 원격으로 호출할 수 있는 기능이다. IDL(Interface Definition Language) 기반 으로 다양한 언어를 가진 환경에서도 쉽게 확장이 가능하며 인터페이스 협업에도 용이하다.

🟡 지원 언어 : C++ , Java, Python, Ruby, Node.js, C#, Go, PHP, Objective-C ...

RPC 의 핵심 개념은 ‘Stub(스텁)’ 이라는 것인데, 서버와 클라이언트가 서로 다른 주소 공간을 사용하므로 함수 호출에 사용된 매개 변수를 반드시 변환해주어야 한다. 그렇지 않으면 매개 변수에 대한 포인터가 다른 데이터를 가리키게 된다. 이 변환을 담당하는 것이 스텁이다.

스텁


스텁(Stub)은 매개변수(parameter) 객체를 메세지(Message)로 변환(Marshalling)/역변환(Unmarshalling)하는 레이어이며 클라이언트의 스텁과 서버의 스텁으로 나눠진다.

클라이언트의 스텁

함수 호출에 사용된 파라미터의 변환(Marshalling, 마샬링) 및 함수 실행 후 서버에서 전달된 결과의 변환을 담당한다.

서버의 스텁

클라이언트가 전달한 매개 변수의 역변환(Unmarshalling, 언마샬링) 및 함수 실행 결과 변환을 담당한다.

스텁을 이용해서 서버와 클라이언트간의 언어가 달라도 요청과 응답을 구현할 수 있다.

  • Stub 을 활용한 RPC 통신 과정 → 아직 이해가 잘 되진 않은 부분..
    1. IDL 을 사용하여 호출 규약 정의
      1. 함수명, 인자, 반환값에 대한 데이터형이 정의된 IDL 파일을 rpcgen 으로 컴파일시, stub code 가 자동으로 생성됨
    2. Stub code 에 명시된 함수는 원시코드의 형태로 상세 기능은 server에서 구현된다.
      1. 만들어진 stub 코드는 클라이언트/서버에 함께 빌드한다.
    3. client 에서 stub에 정의된 함수를 사용시,
    4. client stub은 RPC runtime 을 통해 함수를 호출하고
    5. server는 수신된 procedure 호출에 대한 처리 후 결과 값을 반환한다.
    6. 최종적으로 Client는 Server의 결과 값을 반환받고, 함수를 Local에 있는 것처럼 사용할 수 있다.

이렇게 장점이 많은 RPC가 왜 쉽게 찾아볼 수 없었을까?

RPC는 매우 획기적인 방법론이었고 로컬에서 제공하는 빠른 속도, 가용성 등을 분산 프로그래밍에서도 제공하고 있다고 홍보를 했으나 정작 구현의 어려움, 지원 기능의 한계 등으로 제대로 활용되지 못했다. 그러던 중, 데이터 통신을 익숙한 Web을 활용해보려는 시도로 이어졌고 이 자리를 REST 가 차지하게 되었다.

gRPC (google Remote Procedure Call)


gRPC는 구글에서 개발한 RPC(RPC, Remote Procedure Call) 시스템이며 모든 환경에서 실행할 수 있는 오픈 소스 고성능 RPC 프레임워크이며 로컬 함수를 호출하는 것만큼 쉽게 분산된 이기종 애플리케이션을 연결, 호출, 운영, 디버깅할 수 있는 프로세스 간 통신 기술이다.

gRPC를 이용하면 원격에 있는 애플리케이션의 메서드를 로컬 메서드인 것처럼 직접 호출할 수 있기 때문에 분산 애플리케이션과 서비스를 보다 쉽게 만들 수 있다.

REST 와 비교했을때 두드러진 차이점은 HTTP/2 를 사용한다는 것과 프로토콜 버퍼로 데이터를 전달한다는 점이다. 그렇기에 Proto File 만 배포시, 환경과 프로그램 언어에 구애받지 않고 서로 간의 데이터 통신이 가능하다.

  1. HTTP/2

HTTP/1.1 은 클라이언트의 요청이 올때만 서버가 응답을 하는 구조로 매 요청마다 connection 을 생성해야 하며 cookie 등 많은 메타 정보들을 저장하는 무거운 header 가 요청마다 중복 전달되어 비효율적이고 느린 속도를 보여주었다.

HTTP/2 는 한 connection 으로 동시에 여러 개 메시지를 주고받으며, header 를 압축하여 중복 제거 후 전달하기에 훨씬 효율적이다. 또한 필요시 클라이언트의 요청 없이도 서버가 리소스를 전달할 수 있기에 클라이언트 요청을 최소화 할 수 있다.

  1. ProtoBuf (Protocol Buffer, 프로토콜 버퍼)

Protocol Buffer 는 구글 사에서 개발한 구조화된 데이터를 직렬화하는 기법이다.

직렬화란, 데이터 표현을 바이트 단위로 변환하는 작업을 의미한다.

아래 예제처럼 같은 정보를 저장해도 text 기반인 json 의 경우 82 byte 가 소요되는데 반해, 직렬화 된 protocol buffer는 필드 번호, 필드 유형 등을 1byte 로 받아서 식별하고, 주어진 length 만큼만 읽도록 하여 33byte 만 필요하게 된다.


→ XML, JSON 과 같이 텍스트가 아닌 binary 로 전송되기에 빠르다.

프로토콜 버퍼로 작업할 때는 proto file에서 직렬화하려는 데이터 구조를 정의한다.

프로토콜 버퍼는 하나의 프로그래밍 언어가 아니라 여러 프로그래밍 언어를 지원하기 때문에, 특정 언어에 종속성이 없는 형태로 데이터 타입을 정의하게 되는데, 이 파일을 proto file이라고 한다.

  1. Proto File

1) Message and Field

Proto File 에서는 주고 받는 데이터들을 message 라는 것으로 정의한다. 이 메세지는 여러 가지 타입의 필드로 구성된다.

syntax = "proto3";

message PersonInfo {
  string name = 1;
  int32 id = 2;
  bool has_ponycopter = 3;
}

🟡 Naming

message 이름은 CamelCase 형태, field 이름은 under_bar 형태로 사용할 것을 권장하고 있다. 단, field 이름은 숫자로 시작할 수 없다.

🟡 Field Tag (= Field number)

필드 번호가 1~15일 때는 1byte, 16~ 2047은 2byte 를 Tag로 가져가게 된다.

🟡 proto2 VS proto3

위에 Sample.proto 에서는 syntax = “proto3” 를 지정해 줌으로써 proto version 3 의 규약을 따르겠다고 선언했다. 디폴트는 proto2 이다.

Proto2 지원 언어 : C++, Java, Python, Go

Proto3 지원 언어 : C++, Java, Python, Go, Ruby, Objective-C, C#, JavaScript, PHP, Dart ...

🟡 Proto File Field Rule

required : 필수로 가져야 할 필드 (only use proto2)

optional : 해당 필드를 가지지 않거나 하나만 가짐 (only use proto2)

repeated : 임의 반복 가능한 필드 (번호 및 값의 순서는 보존)

[packed=true] 옵션 : key-value 쌍 형태에서 value 만 반복

message SearchRequest{
	string query = 1;
	repeated int32 result = 2 [packed=true];
}
  1. 서비스 모델

Service

RPC를 통해 서버가 클라이언트에게 제공할 함수의 형태를 정의한다.

서비스 명과 RPC 메소드 명 모두 CamelCase 형태를 권장한다. 옵션을 주지 않으면 단이 요청/응답 으로 동작하지만, stream 옵션을 주면 RPC를 구현할 수 있다.

service SearchService {
	rpc Search (SearchRequst) returns (SearchResponse);
	rpc Upload ()
}
service SearchService {
	rpc Search (stream SearchRequst) returns (stream SearchResponse);
}
  1. Message
  • 요청과 응답에 대한 구조 정의
// 서비스 정의는 프로토콜 버퍼의 정의로 시작. 보통 proto3
syntax = "proto3";

option csharp_namespace = "GrpcGreeter"; //C#

// option go_package = "maum-backend-protobuf/gen/go/protos/notification/v1"; //Golang

// 패키지 이름은 프로토콜 메시지 타입 사이의 이름 충돌을 방지하고자 사용, 코드 생성에도 사용
package greet;

// gRPC 서비스의 서비스 인터페이스를 정의
// The greeting service definition.
service Greeter {
  // Sends a greeting. 인사를 건네는 원격 메서드
  rpc SayHello (HelloRequest) returns (HelloReply);
}

// The request message containing the user's name. 인사 요청 정보의 메시지 형식, 타입을 정의
message HelloRequest {
  string name = 1;
}

// The response message containing the greetings.
message HelloReply {
 repeated string message = 1;
}

이렇게 작성된 proto file을 protoc 컴파일러로 컴파일 하면 데이터에 접근할 수 있는 각 언어에 맞는 형태의 데이터 클래스를 생성해준다. 만들어진 클래스는 각 필드를 위한 접근자 뿐 아니라 전체 구조를 바이트로 직렬화하거나 바이트로부터 전체 구조를 파싱하는 메서드들을 제공한다.

syntax = "proto3";

option go_package = "backend-protobuf/gen/go/protos/notification/v1"; //Golang

// message type 이름을 중첩없이 구분할 때 사용
package profile;

service Profile{
	// 메서드명(메서드 request)returns(메서드 response)
  rpc ShowProfile(ShowProfileRequest) returns (ShowProfileResponse);
}
	// 메서드들의 request, response 등에서 사용된 파라미터들의 타입, 변수명 등을 메세지로 정의
message ShowProfileRequest{
  string user_id = 1;
}

message ShowProfileResponse{
  string location = 1;
  string language = 2;
  repeated string interest = 4;
}

Maps

key - value 쌍. 이 맵의 필드들은 repeated 될 수 없다. key type 은 string 과 scalar type 의 자료형만 지정 가능하며 value type 은 map 외의 모든 자료형 지정이 가능하다.

message MapMsg {
	map<string, InnerMapMsg> NameMap = 1;
}

message InnerMapMsg 

]

데이터 형식

와이어 타입종류필드 타입
0가변 길이 정수(Varient)int32, int64, uint32, uint64, sint32, sint64, bool, enum
164비트fixed64, sfixed64, double
2길이 구분string, bytes, embedded messages, packed repeated fields
3,4시작, 종류 그룹사용중단
532비트fixed32, sfixed32, float

워크플로우


gRPC를 이용한 워크플로우는 앞의 그림과 같다. 처음에 protobufs를 정의한 다음 protoc 컴파일러를 통해 여러가지 언어를 기반으로 서버와 클라이언트가 형성된다.

장점


gRPC는 대부분의 아키텍쳐에 사용할 수 있지만 MSA에 가장 적합한 기술이다. 많은 서비스 간의 API 호출로 인한 성능 저하를 개선하며 보안, API 게이트웨이, 로깅 등을 개발하기 쉽다.

HTTP 1.1 버전

HTTP1.1은 1999년 출시 이후 지금까지 웹에서 가장 많이 사용되고있는 프로토콜입니다. HTTP1.1은 기본적으로 연결당 하나의 Request과 Response를 처리하기 때문에 동시전송 문제와 다수의 리소스를 처리하기에 속도 및 성능 이슈를 가지고 있습니다.

대표적인 HTTP 1.1의 단점은 다음과 같습니다.

HOLB(Head Of Line Blocking) - 특정 응답 지연

HTTP/1.1은 connection당 하나의 Request를 처리하기에 속도 문제가 있었습니다. 이를 개선할 기법으로 pipelining이 제안됬는데, 이 방식은 하나의 connection을 통해 다수개의 파일을 Request/Response 받을 수 있는 기법을 말하는데, 이 기법을 통해 성능 향상 할 수 있으나 문제점이 있습니다.

pipelining 예시

만약 첫번째 이미지에 대한 Response가 지연되면, 아래 그림과 같이 두, 세번째 이미지는 첫번째 이미지의 응답처리가 완료되기 전까지 대기하게 됩니다. 이와 같은 현상을 HTTP의 Head Of Line Blocking(HOLB) 이라 부르며 pipelining 기법의 문제점 중 하나입니다.

pipelining의 문제 예시

RTT(Round Trip Time) 증가

HTTP1.1은 하나의 connection에 하나의 request를 처리합니다. 이로인해 하나의 connection마다 tcp 연결을 하며, 신뢰성 연결을 하는 tcp connection은 시작시 3-way handshake, 종료시 4-way handshake가 반복적으로 발생, 이로인한 오버헤드가 발생합니다.

heavy header

HTTP1.1의 header에는 많은 metadata가 저장되어 있습니다. 사용자가 방문한 웹페이지는 다수의 HTTP Request가 발생하게 되는데 이 경우 매 Request마다 중복된 header값을 전송하며, 이 중 cookie가 큰 문제입니다.

HTTP 2.0

HTTP2.0은 HTTP1.1의 프로토콜을 계승해 동일한 API면서 성능 향상에 초점을 맞췄습니다.

Multiplexed Streams

한 connection으로 동시에 여러개 메시지를 주고 받을 수 있으며, Response는 순서에 상관없이 stream으로 주고 받습니다.

Stream Prioritization

리소스간 우선순위를 설정해 클라이언트가 먼저 필요한 리소스부터 보내줍니다.

Server Push

서버는 클라이언트의 요청에대해 요청하지 않은 리소스를 마음대로 보내줄 수 있습니다.

Header Compression

Header table과 Huffman Encoding 기법(HPACK 압축방식)을 이용해 압축을 했습니다.

HPACK 기법

HTTP 버전별 차이

단점


gRPC 에서는 아직 브라우저 관련 API가 제공되지 않기 때문에 브라우저에서 직접 gRPC 서비스를 호출하는 것이 불가능하다. 또한 기존 데이터 통신과 다르게 텍스트 기반 (XML, JSON)이 아니라 Encoding 된 Binary Sream 이기 때문에 사람이 읽기 어렵다.

gRPC 특징


  1. 높은 생산성과 다양한 언어 및 플랫폼 지원
    gRPC는 서비스와 메시지를 정의하기 위해 Protocol Buffers를 사용한다. 프로토콜 버퍼의 IDL만 정의하면 서비스와 메세지에 대한 소스코드가 자동으로 생성되고 데이터를 주고 받을 수 있다.
  2. HTTP/2 기반의 양방향 스트리밍gRPC는 HTTP/2 기반으로 통신한다. HTTP/2는 하나의 TCP 연결이 여러 개의 양방향 스트리밍을 지원한다.
  3. 성능 이점gRPC는 HTTP/2 레이어 위에서 Protocol Buffers를 사용해 직렬화된 바이트 스트림으로 통신하여 JSON 기반의 통신보다 더 가볍고 통신 속도가 빠르다. 때문에 laytency 감소와 더 많은 트래픽을 처리할 수 있는 성능의 이점이 있다.

참고자료

NBP 기술 & 경험 시대의 흐름, gRPC 깊게 파고들기 #1
NBP 기술 & 경험 시대의 흐름, gRPC 깊게 파고들기 #2

profile
미래의 나를 만들어나가는 한 개발자의 블로그입니다.

0개의 댓글