이번 포스팅은 gRPC입니다.
요즘 업무에 자주 쓰고 있어서 가볍게 정리해두려 합니다.
[핵심 용어]
/*
- RPC
- IDL(Interface Definition Language)
- gRPC
- protobuf(프로토버프/프로토콜버퍼)
- microservice(마이크로서비스)
- monolithic(모놀리식)
*/
RPC는 Remote Procedure Call
뜻 그대로 프로세스에서 다른 프로세스의 메소드(프로시져)를 원격으로 호출하는 기법입니다
gRPC는 google에서 만든 RPC입니다
먼저 함수명, 파라미터, 반환 값 세 가지를 IDL(Interface Definition Language)로 정의해요
[IDL 설명]
/*
IDL은 뜻 그대로 인터페이스를 정의하는 언어예요
서로 다른 언어로 만들어진 X App(golang)과 Y App(JAVA)이
서로 통신할 수 있게 합의하는 언어입니다
예를 들면
A와 B가 HTML로 JSON 형식의 데이터를 주고받아요
A가 보낸 내용을 B가 파싱해서 이해하려면 데이터의 형식을 알아야겠죠?
(이름 이라든가, 타입이라든가, 값이라든가)
형식은 보통 규격 문서로 정의를 합니다
HTML을 gRPC 입장으로 보면
HTML => gRPC
JSON => protobuf
형식(규격 문서) => IDL
RPC에서 주로 쓰이는 용어에요
*/
클라이언트와 서버는 작성한 IDL을 공유해서 갖습니다
1. 서버 : 클라이언트가 접속할 수 있는 주소에 IDL 대로 함수를 열어두고 기다립니다
2. 클라이언트 : 서버에 접속합니다
3. 클라이언트 : 서버에 IDL대로 매개변수를 제공해 함수를 호출합니다
4. 서버 : 클라이언트가 호출한 함수를 수행하고 결과를 반환합니다
5. 클라이언트 : 결과를 받습니다
RPC의 뜻은 알았으니 우선 gRPC의 'g'가 뭔지 봅시다
gRPC 탄생 비화를 짧게 설명 드릴 필요가 있어요
google은 이미 이전부터 Microservice 아키텍쳐(작은 서비스 단위로 나누어 서비스 간의 상호작용으로 작동하는 소프트웨어 구조)를 연구하고 적용해왔습니다.
[Microservice 설명]
/*
프로그램은 여러 가지 기능이 합쳐져서 하나의 큰 프로그램을 구성하죠
보통 하나의 프로그램은 여러가지 모듈로 나눠져 있어요
카페 계산대로 예를 들면
- 메뉴 모듈 : 메뉴 추가/제거/선택, 가격 계산
- 결제 모듈 : 현금/카드 결제 수행
- 영수증 모듈 : 영수증 출력
- 정산 모듈 : 매출 정산
등의 모듈이 있겠죠!(대충)
이런 모듈이 합쳐져 하나의 프로그램을 이루는 아키텍쳐를 모놀리식(Monolithic)이라 해요
(모노는 '하나'를 뜻하는 그리스 접두어임)
모놀리식은 모듈이 나눠져 있지만 모듈 중 무언가를 변경하면
전체를 다시 빌드하고 배포해야 합니다
장점 : 개발, 빌드, 배포 프로세스가 간단! (우리가 대충 개발할 때를 떠올려봐요!)
단점 : 기능은 멈춤 없이 도는 게 {왕 중요한데} 뭔가 작은 거라도 바꾸면 내렸다 올려야 하니 죽여야 해요(그 외에도 여러 가지)
Microservice는 하나의 큰 Monolithic의 모듈들을 서로 다른 App으로 나눠버린다고 생각하면 돼요
Microservice에서는 모듈을 서비스라고 표현합니다
서비스들은 모듈 방식보다 느슨한 결합(Loose coupling)으로 되어있어요 (중요함)
다른 앱들로 나누면 서로 기능 쓸 때마다 통신해야 되잖아!? 뭔 짓이여..!
그래서 서비스와 서비스 간 통신을 위한 프로토콜은 가볍게 구성합니다!
물론 그래도 통신 비용은 있겠지만
그런 비용을 무시할 정도로 엄청난 장점이 있는 구조예요(트렌드다 이 말이야)
Microservice를 짧게 언급하지만 나중에 따로 정리할 거예요
추가되었으니 자세한 내용은 아래를 참고하세요
2021.07.15 - [Cloud/Cloud Native] - Microservice
재밌는 변화를 가져온 친구입니다
*/
google은 약 10년간 데이터 센터에서 실행되는 많은 Microservice들 간의 연결과 통신을 위해
single general-purpose 한 Stubby라는 RPC 인프라를 만들어 사용합니다
(단어를 보니 하나의 형태로 다양한 종류의 모든 서비스의 연동을 했나 봐요!)
이후 2015년 3월쯤에 오픈소스화하게 되었고,
이를 시대의 요구에 따라 IoT, Cloud 등에 활용할 수 있도록 리워크하여 탄생합니다
기존에 Stubby 녀석은 다양한 기능을 가졌지만 자기들 구조에 맞게 설계된 탓에
general-purpose 하게 설계했다 해도
구글러들이 아닌 개발자들까지 쓰기에는 적합하지 않은 구조였다고 합니다
그 와중에 HTTP2 등의 표준 등장으로 리워크를 해야겠다 결심을 합니다
그 결과로 나온 게 더욱이 general-purpose해진 RPC인 gRPC다!
gRPC는 높은 성능과 Microservice에 적합한 구조로 최근 더 많이 사용되고 있습니다
grpc.io에 들어가면 설명이 꽤 잘 나와있습니다
gRPC가 달달한 이유를 간단히 짚어봅니다
1. 직관적
2. 다양한 언어에서 사용 가능
3. 양방향 스트리밍
4. 높은 메시지 압축률과 성능
5. Healthcheck, Trace, Authentication Load Balancing 등 다양한 기능 제공
맨날 다 직관적이래, 뭐가 직관적인데??
아래 형태를 보면 알 수 있습니다
service HelloService { // 함수의 정의
rpc SayHello (HelloRequest) returns (HelloResponse);
}
// parameter 구조체 정의
message HelloRequest {
string greeting = 1;
}
// return 구조체 정의
message HelloResponse {
string reply = 1;
}
SayHello란 함수가 있네?
매개변수는 HelloRequest네?
반환 값은 HelloResponse네?
HelloRequest랑 HelloResponse는 string 값 하나를 가진 구조체인가 봐?
프로그래머는 보기만 해도 파악할 수 있을 정도로 직관적입니다
위의 정의를 server와 client 측이 공유하고
server 측이 함수를 열고 기다리면
client가 접속하여 parameter를 주면서
해당 함수를 실행하고
결과를 반환받아가면 동작 끝입니다
와 깔끔해-!
음? 근데 기존 HTTP 통신도 사전 정의한 형태의 JSON을 담아
request를 보내고 서버가 동작해서 response를 반환하는데?
물론 그래요 거의 똑같죠
하지만
방금 본 직관적인 함수, parameter, return의 선언,
훌륭한 성능,
다양한 연동을 위한 기능들까지!
작고 빠릅니다
그래프만 보고 간단히 넘어갑시다(성능에 대해선 나중에 다시 다뤄볼게요)
일반적으로 사용하는 REST/JSON와 비슷하거나 더 좋은 성능을 보여줍니다
직관적일 뿐만 아니라 빠릅니다
아까 잠깐 본 아래의 IDL(Interface Definition Language)인
protobuf(프로토버프, 프로토콜 버퍼) 덕분입니다
service HelloService { // 함수의 정의
rpc SayHello (HelloRequest) returns (HelloResponse);
}
// parameter 구조체 정의
message HelloRequest {
string greeting = 1;
}
// return 구조체 정의
message HelloResponse {
string reply = 1;
}
.proto라는 확장자를 가진 파일로 위와 같이 정의합니다.
이게 좋은 이유를 볼까요!
공식 페이지에도 잘 나와있습니다
길지 않으니 읽어보시는 걸 추천드려요
Protobuf는 XML과 비교하여 설명을 많이 합니다
XML보다 뭐가 좋은가?
- protobuf가 더 간단함
- 3~10배 정도 작음
- 20~100배 정도 빠름
- 데이터 엑세스 클래스라는 개념으로 쉽게 사용 가능
- enumeration, nested message, 안정적인 message update, Any 타입, Map 타입 등 많은 기능이 있음
간단한 예를 들어 증명해줍니다
먼저 XML입니다
XML 직렬화 예)
이는 직렬화된 후 69바이트이며, 5000-10000ns 소요가 된다고 하네요
C++ XML 사용 예)
cout << "Name: " << person.getElementsByTagName("name")->item(0)->innerText() << endl; cout << "E-mail: " << person.getElementsByTagName("email")->item(0)->innerText() << endl;
다음은 protobuf 입니다
protobuf 직렬화 예)
파싱 후 28바이트이며, 100-200ns가 소요된다고 합니다
C++ protobuf 사용 예)
cout << "Name: " << person.name() << endl; cout << "E-mail: " << person.email() << endl;
우와 달아 보이네요!
protobuf가 무조건 좋은 건 아닙니다
실제론 위와 같이 humanreadable 한 형태가 아닌 바이너리 형태 이므로
텍스트 기반 마크업 모델(HTML)에선 불리하죠
자세한 serialization 관련 내용은 기회가 되면 다음에 포스팅 해보겠습니다
그래요... protobuf 맛만 봤지만, 이 덕분에 gRPC가 작고 빠른 건 알겠어요
다른 건요?
한번 써봅시다
이런저런 함수로 통신하면 되겠고..
또 서비스에 필요한 기능이...
우선 Authenticaiton. 음? 있네
그리고 또... Heartbeat check. 이것도 있네?
Load balancing도 구현해야지. ...있네?
Monitoring, Tracing, ....
물론 이런 필요한 기능을 가져다 쓰려면
다 공부해야 합니다
Production 환경에 적용하려면 알아야 하는 내용이 꽤나 많아져요
(단점이라면 단점일까요... 개발이 그런 거 아니겠습니까)
하지만 튜토리얼도 잘 돼있고 어렵지 않으며
익히고 나면 개발 효율이 좋아집니다
(성능은 덤이죠)
또한, Protobuf에 의한 메시지 압축률은 트래픽을 줄이는데 효과적입니다
물론 RPC 통신의 비용이 있기 때문에 너무나도 간단한 REST API로 구성된 서비스에는 적합하지 않아요~
그리고, HTTP2 기반이므로 특징을 그대로 가져갑니다
gRPC를 활용하면 함수 형태의 로직에 집중할 수 있어 빠른 서비스 설계 및 개발이 가능하고
다양한 언어로 지원하고 있기 때문에 MSA(Microservice Architecture)에 적합하고 Cloud 쪽에서도 굉장히 널리 활용되고 있습니다
이 정도로 마무리하고.
다음엔 golang을 통해 실제로 사용해보도록 하겠습니다
- 맛보기인데 설명할 내용이 너무 많아요
'Programming > Framework & Tool' 카테고리의 다른 글
Concurrency with Actor Model(행위자 모델) (0) | 2021.10.29 |
---|