gRPC는 구글에서 개발한 오픈소스 기술로 다양한 플랫폼에서 서로 다른 언어로 작성된 애플리케이션 간 통신을 위한 효율적이고 간편한 방법을 제공합니다.
gRPC는 이전의 RPC 시스템과 달리 대용량 데이터 전송 및 멀티플랫폼 지원 등의 문제를 해결할 수 있었습니다.
현재까지도 지속적으로 업데이트와 개선이 이루어지며, 클라우드 네이티브 아키텍처와의 호환성도 강화되고 있는데요.
이번 글에서는 gRPC란 무엇이고, gRPC가 RESTful API 대비 구체적으로 무엇이 더 좋아졌는지 알아보는 시간을 가져보겠습니다.
gRPC 등장 배경
*RPC
gRPC를 설명 드리기 전에, gRPC가 어떠한 이유로 인해 등장하게 되었는지 통신 방식의 변화 과정을 간략하게 설명 드리겠습니다.
RPC(Remote Procedure Call, 원격 프로시저 호출)란 별도의 코드 없이 다른 주소 공간에서 함수나 프로시저를 실행할 수 있게 하는 프로세스 간의 통신 기술입니다.
즉, RPC를 통해 사용자는 사용하고자 하는 함수가 다른 원격지의 애플리케이션에 있어도 사용할 수 있는 것입니다.
후에 설명드릴 gRPC도 결국 RPC의 개념을 따라가기 때문에 RPC에 대해 보다 자세히 알아보도록 하겠습니다.
RPC는 IDL를 사용하여 인터페이스를 명시하는데요.
*IDL
IDL(Interface Definition Language, 인터페이스 정의 언어)은 소프트웨어 컴포넌트의 인터페이스를 묘사하기 위한 명세 언어입니다.
어느 한 언어에 국한되지 않는 언어 중립적인 방법으로 인터페이스를 묘사함으로써, 같은 언어를 사용하지 않는 소프트웨어 컴포넌트 사이의 통신을 가능하게 합니다.
이러한 IDL을 통해 RPC는 서로 다른 시스템에서 통신을 가능하게 합니다.
RPC의 동작 흐름은 다음과 같습니다.
출처 : RPC란? . (2021). https://velog.io/@jakeseo_me/RPC%EB%9E%80 .
- IDL을 통해 호출에 대한 인터페이스를 정의합니다.
- 정의된 IDL을 기반으로 rpcgen이라는 rpc 프로토콜 컴파일러를 통해 코드(stub)가 생성됩니다. stub을 통해 Client는 procedure 호출을 위한 참조자가 생겼고, Server는 procedure 이해를 위한 참조가 생기게 됩니다.
- Client는 자신의 프로그램에서 함수를 호출하는 것처럼 stub에 정의된 함수를 사용합니다.
- Client에서 Stub에 정의된 함수를 사용할 때 Client stub은 RPC 런타임을 통해 함수를 호출합니다.
- Server는 수신된 procedure 호출에 대한 처리 후 결과 값을 반환합니다.
- 최종적으로 Client 프로그램은 서버의 결과 값을 반환 받습니다.
이러한 RPC는 하부 네트워크 프로토콜에 신경 쓰지 않아도 되기 때문에 고유한 프로그램 개발에만 집중이 가능하고 프로세스 간 통신 기능을 비교적 쉽게 구현 가능한 장점이 있습니다.
특히, IDL(Interface Definication Language) 기반으로 다양한 언어에서도 쉽게 확장이 가능한데요.
하지만, RPC는 코드를 이해하기 어려울 뿐더러 에러 발생 시 디버깅이 어려운 단점이 존재합니다.
이로 인해 사람들은 데이터 통신을 Web을 활용해보려 시도했고 그로 인해 등장한 것이 REST API 입니다.
REST
REST(REpresentational State Transfer)는 HTTP1.1 기반 URI(Uniform Resource Identifier)를 통해 자원(Resource)을 명시하고,
HTTP Method(POST, GET, PUT, DELETE)를 통해 해당 자원에 대한 CRUD를 적용하는 것을 의미합니다.
출처 : REST API . (2021). https://velog.io/@drv98/REST-API.
REST는 HTTP 프로토콜의 인프라를 그대로 사용하기 때문에 별도의 인프라를 구축할 필요도 없고, REST API 메시지가 의도하는 바를 명확하게 나타내기 때문에 쉽게 파악할 수 있습니다.
이는 특정 메소드의 세부적인 표현 문구를 JSON, XML 등 다양한 언어를 활용하여 작성할 수 있다는 장점 뿐만 아니라 간결한 헤더 표현을 통한 가독성 향상이라는 두 마리 토끼를 잡는 효과를 가져다 주게 되는 것입니다.
하지만, REST는 표준 자체가 존재하지 않아 정의가 필요하고 사용할 수 있는 메소드가 4가지밖에 없다는 단점이 존재합니다.
이러한 REST가 보편적으로 사용되던 중에, 2016년 8월 google이 개발한 오픈소스 RPC 프레임워크인 gRPC가 등장하게 됩니다.
gRPC
google은 1주일 동안 띄우는 일주일 동안 띄우는 컨테이너가 약 20억개, 1초 동안 던지는 원격 호출의 수는 100억 번이라고 합니다.(2017년 기준)
구글은 이러한 대규모 서비스를 운영하기 위해 gRPC를 개발했습니다.
gRPC는 말 그대로 google에서 개발한 RPC 프레임워크입니다.
기본적인 개념은 RPC와 동일하지만 특징으로는 HTTP/1.1이 아닌 HTTP/2 기반으로 양방향 통신을 지원하고 HTTP/2를 사용합니다.
또한, gRPC는 IDL로 PB(Protocol Buffer)를 사용합니다.
gRPC 의 장점
gRPC는 앞서 설명 드렸다시피 HTTP/1.1이 아닌 HTTP/2 방식을 사용한다고 말씀 드렸는데요.
HTTP/2가 HTTP/1.1에 비해 어떤 면에서 좋은지 알아보도록 하겠습니다.
HTTP/1.1 vs HTTP/2
출처 : HTTP & HTTPS . (2022). https://velog.io/@ooooorobo/HTTP-HTTPS .
HTTP(HyperText Transfer Protocol)는 1999년 출시 이후 지금까지 가장 많이 사용되고 있는 프로토콜인데요.
HTTP1.1은 기본적으로 한 connection에 하나의 요청과 응답을 처리하기 때문에 동시 전송, 다수의 리소스를 처리하기에 속도와 성능 이슈를 가지고 있었습니다.
또한 무거운 header가 요청 마다 중복 전달되어 비효율적이었습니다.
이러한 문제점들을 해결하기 위해서 UI 개발자, front-end 개발자들이 고군분투 하고 있던 중에 HTTP2가 나오게 되었습니다.
HTTP/2는 성능 뿐 아니라 속도 면에서도 우수한데요. 한 connection에 여러 개의 메시지를 주고 받을 수 있고,
무거웠던 header를 압축하여 중복을 제거하고 전달하기 때문에 HTTP/1.1에 비해서 훨씬 효율적이었습니다.
또한 반드시 클라이언트의 요청이 들어와야 응답을 할 수 있는 HTTP/1.1과 달리, 요청 없이도 서버가 리소스를 전달할 수 있어 클라이언트의 요청을 최소화 할 수 있게 되었습니다.
Protobuf
프토토콜 버퍼란
또한, gRPC는 IDL로 PB(Protocol Buffer)를 사용합니다.
프로토콜 버퍼(Protocol Buffer)는 구조화된 데이터를 직렬화하는 방식으로,
여기서 직렬화(Serialization)란 Java 시스템 내에서 사용하는 객체 또는 데이터를 Java 시스템 외에서도 사용할 수 있도록 Byte 형태로 데이터를 변환하는 기술입니다.
즉, 구조화된 데이터를 파일로 만든다고 보시면 됩니다.
Proto File이란 무엇인가
Protobol Buffer는 RPC의 IDL과 동일하게 언어 중립적인 형태로 데이터 타입을 정의하기 위해서 Proto File에 정의합니다.
정의한 Proto File을 사용하려면 각 언어에 맞게 데이터 클래스로 생성해야 하기 때문에 protoc 컴파일러가 필요합니다.(RPC에서는 rpcgen)
protoc 컴파일러로 Proto File을 컴파일하면 각 언어에 맞게 데이터 클래스 파일을 생성해주기 때문입니다.
즉, Proto File은 Protocol Buffer의 기본 정보를 명세하는 파일입니다.
- proto file 정의
- protoc 컴파일러를 이용하여 컴파일
- 원하는 소스 파일 생성(.java, .py, .c++, …)
//Openmaru.proto
syntax="proto3"
service Greeter {
rpc OpenmaruHello (HelloRequest) returns (HelloReply);
rpc Openmaru (stream HelloRequest) returns (stream person);
}
message person {
string name = 1;
int32 age = 2;
string phone = 3;
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
Protocol Buffer 방식을 왜 쓰는가
보통 서버-클라이언트 간에 데이터 교환을 위해 JSON 형태를 많이 사용하는데 프로토콜 버퍼를 쓰는 이유가 무엇인지 비교하며 설명 드리겠습니다.
예를 들어 ‘Person’ 이라는 객체를 JSON 포맷으로 표현한다면 아래와 같습니다.
{
"name":"juan",
"age":26
}
이 객체를 직렬화 한다면
{“name”:”juan”,”age”:26} 으로 데이터의 크기는 총 24Byte입니다.
하지만 Protocol Buffer 형태로 직렬화 한다면
{
string name= 1;
int32 age= 2;
}
과 같이 name, age의 속성 값을 Proto file에서 작성한 Field Number로 대체 가능하다는 점이 가장 큰 이유입니다.
결국 프로토콜 버퍼를 사용하면 그림과 같이 8 Byte만 사용하게 되는 것입니다
그림에 나오는 최초의 1 Byte에서 5bit는 Field Number, 3bit는 Field Type을 나타냅니다.
Field Type은 다음과 같이 확인하실 수 있습니다.
필드 타입
Type |
Meaning |
Used For |
0 |
Variant |
int32, int64, uint32, uint64, sint32, sint64, bool, enum |
1 |
64-bit |
fixed64, sfixed64, double |
2 |
Length-delimited |
string, bytes, embedded messages, packed repeated fields |
3 |
Start group |
groups (deprecated) |
4 |
End group |
groups (deprecated) |
5 |
32-bit |
fixed32, sfixed32, float |
이로써 JSON에 비해 데이터의 크기가 월등히 작아져 대용량 데이터를 처리하는 데 매우 빠른 성능을 자랑합니다.
RESTful API vs gRPC
지금부터 본격적으로 RESTful API와 gRPC의 성능을 비교해보도록 하겠습니다.
간단한 SpringBoot 기반의 CRUD 애플리케이션을 만들었으며, 모두 같은 메시지를 보냈습니다.
실습 환경은 다음과 같습니다.
- gRPC 1.42.1
- Spring Boot 2.7.6
POSTMAN 10.5.8- Eclipse Java Development Tools 3.18.1200.v20220607-0700
- jdk 1.8
- Gradle 7.6
- Apache JMeter 5.5
Proto 파일 및 메시지는 다음과 같습니다.
helloworld.proto
syntax = "proto3";
option java_multiple_files = true;
option java_package = "net.devh.boot.grpc.examples.lib";
option java_outer_classname = "HelloWorldProto";
// The greeting service definition.
service Simple {
// 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 {
string message = 1;
}
message EmployeeRequest {
int32 emp_id=1;
}
message EmployeeResponse {
int32 emp_id=1;
string name=2;
string email=3;
}
message APIResponse {
string responseMessage=1;
int32 messageCode=2;
}
message getEmployeeParams {};
service EmployeeService {
rpc getEmployee(EmployeeRequest) returns (APIResponse) {};
rpc addEmployee(EmployeeResponse) returns (APIResponse) {};
rpc deleteEmployee(EmployeeRequest) returns (APIResponse) {};
rpc getEmployees(getEmployeeParams) returns (APIResponse) {};
}
JSON Message
{
"emp_id":1,
"name":"juan",
"email":"juan.kim@openmaru.io"
}
RESTful API 애플리케이션에 메시지를 보냈습니다.
패킷을 분석한 결과는 다음과 같습니다.
분석 결과 총 보내지는 데이터의 양은 70 Byte 입니다.
gRPC도 마찬가지로 메시지를 보냈습니다.
패킷을 분석한 결과는 다음과 같습니다.
총 보내지는 데이터의 양은 30 Byte로 RESTful 에 비해 40 Byte가 줄어든 것을 확인할 수 있습니다.
또한, gRPC는 id, name, email과 같은 속성을 태그로 관리하기 때문에 속성 값들이 많아지면 많아질수록 데이터의 양을 엄청나게 줄일 수 있을 것입니다.
다음으로는 jMeter를 이용하여 성능 테스트를 해 보도록 하겠습니다.
RESTful API
gRPC
비교 그래프
이와 같은 과정을 1,10,100,500,1000 번 반복한 결과 값을 차트로 그리면 다음과 같습니다.
- 초당 처리량
- 초당 수신 데이터양
- 데이터 크기(데이터 압축으로 인한 크기 감소)
Byte 크기가 줄어듦에 따라 처리 속도, 초당 수신 데이터양 등이 RESTful 대비 각 각 25%, 19%, 19% 좋아지는 것을 확인할 수 있었습니다.
JSON 혹은 Proto 파일 Service에 보내지는 데이터의 양이 많아지면 많아질수록 이 차이는 더더욱 명확해질 것입니다.
다음으로는 gRPC 애플리케이션을 OpenShift에 배포하고 OPENMARU APM으로 모니터링하여 정보를 추적하도록 하겠습니다.
OpenShift 환경에 gRPC 애플리케이션 배포 방법
gRPC 애플리케이션을 배포하기 위해서 저는 다음과 같은 작업을 할 예정입니다.
사전 준비 사항
-
- git repository 구성 및 권한 설정
- 애플리케이션 배포를 위한 프로젝트 생성 및 프로젝트 권한을 부여 받은 유저 확보
- git repository에 gRPC 애플리케이션 push
이제 이 gRPC Server, Client 애플리케이션을 OpenShift에 배포하도록 하겠습니다.
OpenShift에 배포하기 위해서 저는 OpenShift의 S2I(Source To Image) 기능을 활용할 것이고 다음과 같은 작업을 할 예정입니다.
- Namespace 생성
- build config 생성
- build 수행
- deployment config 생성 및 deploy
- service 및 route 생성
- NodePort 생성
- 서비스 확인
1.Namespace 생성
[root@bastion ~]# oc new-project test
Now using project "test" on server "https://api.ocp.openmaru.com:6443".
You can add applications to this project with the 'new-app' command. For example, try:
oc new-app rails-postgresql-example
to build a new example application in Ruby. Or use kubectl to deploy a simple Kubernetes application:
kubectl create deployment hello-node --image=k8s.gcr.io/e2e-test-images/agnhost:2.33 -- /agnhost serve-hostname
2.build config 생성
[root@bastion openjdk18-openshift]# oc new-build image-registry.openshift-image-registry.svc:5000/openshift/openjdk18-openshift-openmaruapm-juan:5.1.0.8.0~https://moomallangi:"kiki280909"@github.com/MooMallangI/gRPC_Server.git --name=server -n test
--> Found container image 7298f7a (10 minutes old) from image-registry.openshift-image-registry.svc:5000 for "image-registry.openshift-image-registry.svc:5000/openshift/openjdk18-openshift-openmaruapm-juan:5.1.0.8.0"
Java Applications
-----------------
Platform for building and running plain Java applications (fat-jar and flat classpath)
Tags: builder, java
* An image stream tag will be created as "openjdk18-openshift-openmaruapm-juan:5.1.0.8.0" that will track the source image
* A source build using source code from https://moomallangi:kiki280909@github.com/MooMallangI/gRPC_Server.git will be created
* The resulting image will be pushed to image stream tag "server:latest"
* Every time "openjdk18-openshift-openmaruapm-juan:5.1.0.8.0" changes a new build will be triggered
--> Creating resources with label build=server ...
imagestream.image.openshift.io "openjdk18-openshift-openmaruapm-juan" created
imagestream.image.openshift.io "server" created
buildconfig.build.openshift.io "server" created
--> Success
3. build 수행
[root@bastion openjdk18-openshift]# oc start-build server --wait -n test
build.build.openshift.io/server-2 started
[root@bastion openjdk18-openshift]#
4. deployment config 생성 및 deploy
[root@bastion openjdk18-openshift]# oc new-app server --as-deployment-config=true
--> Found image 1fe808c (About a minute old) in image stream "test/server" under tag "latest" for "server"
Java Applications
-----------------
Platform for building and running plain Java applications (fat-jar and flat classpath)
Tags: builder, java
* This image will be deployed in deployment config "server"
* Ports 8080/tcp, 8443/tcp, 8778/tcp will be load balanced by service "server"
* Other containers can access this service through the hostname "server"
--> Creating resources ...
deploymentconfig.apps.openshift.io "server" created
service "server" created
--> Success
Application is not exposed. You can expose services to the outside world by executing one or more of the commands below:
'oc expose service/server'
Run 'oc status' to view your app.
[root@bastion openjdk18-openshift]#
5. service 및 route 생성
[root@bastion openjdk18-openshift]# oc expose svc/server --name=test
route.route.openshift.io/test exposed
[root@bastion openjdk18-openshift]#
6. NodePort 생성
gRPC Client 애플리케이션도 위와 동일한 방법으로 배포했습니다.
마지막으로, 정상적으로 서비스가 동작중인지 확인하도록 하겠습니다.
7. 서비스 확인
OPENMARU APM에서 gRPC 정보 추적
마지막으로, gRPC 애플리케이션을 OPENMARU APM으로 모니터링 해 보는 시간을 가지면서 마치도록 하겠습니다.
Deployment Config에 저희 OPENMARU APM 에서 모니터링을 하기 위한 환경 변수들과 gRPC 애플리케이션을 모니터링 하기 위한 환경 변수를 추가하여 Pod를 재배포했습니다.
Deployment Config에 추가한 환경 변수들은 다음과 같습니다.
OPENMARU APM을 통해 확인한 결과, 클라이언트와 서버의 호출 정보 및 호출 관계가 잘 나오는 것을 확인하실 수 있습니다.
Relationship을 클릭하면, 호출 관계를 목록에 트리 형태(ㄴ 표시)로 표현하고 있는 것을 보실 수 있습니다.
또한, 토폴로지 맵에 호출관계가 표현되는 것도 확인하실 수 있습니다.
마치며
지금까지 gRPC에 대한 개념과 원리에 대해서 설명드리고
애플리케이션 배포 방식과 오픈마루 APM에서 gRPC의 정보 추적에 대한 내용도 알려드렸습니다.
gRPC 는 다양한 언어(예: C++, Java, Python, 등)와 플랫폼에서 사용할 수 있어 마이크로서비스 아키텍처, 클라우드 네이티브 애플리케이션 및 분산 시스템에서 널리 사용되고 있습니다.
그렇기에 IT 분야의 개발자나 엔지니어라면 이에 대한 이해는 필수적일 것입니다.
그럼 이상으로 긴 글 마치겠습니다.
감사합니다 😊
김주안 (juan.kim@openmaru.io)
Support Team
Pro
마이크로서비스 아키텍처 (MSA : MICROSERVICES ARCHITECTURE) 발표 자료 다운로드
/in Kubernetes, 발표자료/by 실장 님OpenShift 자료 다운로드
/in Container, Kubernetes, OpenShift, 발표자료/by 실장 님애플리케이션 마이그레이션 툴킷 ( MTA : Migration Toolkit for Applications ) 발표자료 다운로드
/in Container, Kubernetes, OpenShift, Red Hat, Tech Talk, 발표자료, 분류되지 않음/by 실장 님