1장 gRPC 소개
개요
현대 소프트웨어 시스템
여러 네트워크 위치에서 실행, 다양한 통신 프로토콜 사용한 메세지 전송 -> 분산 애플리케이션의 구성을 가진다.
MSA(Microservice Architecture)
독립적이고 자율적(개별로 개발, 배포, 확장)이며, 비즈니스 중심의 느슨하게 결합된 서비스 모음으로 소프트웨어 애플리케이션 구축
클라우드 네이티브 아키텍처(Cloud Native Architecture)
조직이 퍼블릭, 프라이빗, 그리고 하이브리드 클라우드와 같은 현대적이고 동적인 환경에서 확장 가능한 어플리케이션을 개발하고, 실행할 수 있게 함.
ex) 컨테이너, 서비스 매쉬, 마이크로 서비스, immutable 인프라, declarative API
이 기술은 회복성, 관리 편의성, 가시성을 갖춘 느슨하게 결합된 시스템을 가능하게 함.
견고한 자동화 기능을 함께 사용하면, 엔지니어는 영향이 큰 변경을 최소한의 노력으로 자주, 예측 가능하게 수행할 수 있음
MSA 기반으로 구축되는 시스템은 프로세스간(또는 서비스 간, app 간) 통신 기술을 사용해 네트워크로 연결해야함
기존 방식의 app 과 달리 MSA의 세분화되는 특성으로 인해 네트워크 통신 연결이 급증하게 됨
-> 사용된 아키텍처 스타일(기존 또는 MSA 아키텍처)와 상관없이 프로세스 간 통신 기술은 최신 분산 소프트웨어의 가장 중요한 부분!!
프로세스간 통신
- 동기식 요청-응답(synchronous request-response): 클라이언트 프로세스가 네트워크에 있는 서버에게 요청 메세지를 보내고, 응답 기다림
- 비동기적 방식의 이벤트 기반(asynchronous event-driven): 이벤트 브로커라는 중개자 통해 메세지를 비동기적으로 전달하는 방식
MSA나 CNA 용 동기적 요청-응답 스타일의 통신 구축할 때 -> 주로 RESTful 서비스로 구성
app이나 서비스를 http 기반의 네트워크 호출을 통해 액세스되고 상태를 변경할 수 있는 리소스 모임으로 모델링
But, 많은 경우, 프로세스 간 통신 구축할 때 부피가 크고 비효율적, 에러가 쉽게 발생
좀 더 효율적, 확장성 높은, 느슨하게 결합되는 프로세스 간 통신 기술 요구 -> gRPC 등장!
gRPC는 주로 통신에 동기식 요청-응답 스타일 사용하지만, 초기 통신이 설정되면, 완전 비동기식이거나 스트리밍 모드에서 작동할 수 있음
gRPC
로컬 함수를 호출하는 것 만큼 쉽게, 분산된 이기종 어플리케이션을 연결, 호출, 운영, 디버깅할 수 있는 프로세스 간 통신 기술
1. 서비스 정의
입력 파라미터, 반환 타입과 함께 해당 서비스의 메서드를 서비스 인터페이스 정의로 만듦
-
gRPC 어플리케이션 개발 시, 가장 먼저 해야하는 일은 서비스 인터페이스 정의
서비스 정의에 사용되는 언어: IDL Interface Definition Language
서비스 정의를 이용해 server skeleton 이라는 서버측 코드 생성 가능 (저수준의 통신 추상화를 통해 서버측 로직을 단순화할 수 있음)
클라이언트 측도 client stub 이라는 코드 생성 가능(다양한 프로그래밍 언어에 대한 낮은 수준의 통신을 추상화함으로써 클라이언트 측 통신을 단순화 시킴)
서비스 인터페이스 정의에 지정된 메서드는 로컬 함수를 호출하는 것처럼 쉽게 클라이언트 측에서 원격으로 호출 가능
gRPC 프레임워크는 엄격한 서비스 규격 확인, 데이터 직렬화, 네트워크 통신, 인증, 액세스 제어, 관찰 가능성(observability) 등과 관련된 모든 복잡한 부분 처리
서비스 정의는 프로토(.proto 확장자인 일반 텍스트 파일) 파일에 작성, 서버 측과 클라이언트 측 코드를 생성하는데 사용한다.
이들 간의 네트워크 통신은 HTTP/2 의 프로토콜 버퍼를 통해 이뤄짐
gRPC는 프로토콜 버퍼를 IDL로 사용해 서비스 인터페이스를 정의한다.
프로토콜 버퍼는 언어에 구애받지 않고 플랫폼 중립적이며 확장 가능한 메커니즘으로 구조화된 데이터를 직렬화하고자 사용됨.
(추후 4장에서 프로토콜 버퍼의 기본 사항을 자세히 설명 예정, 지금은 데이터 직렬화 메커니즘으로 생각하자)
프로토콜 버퍼 메세지로 RPC method 파라미터 및 반환 타입, gRPC 서비스를 정의
이 서비스 정의는 프로토콜 버퍼 형식의 확장이므로 프로토 파일에서 코드를 생성하고자 gRPC 플러그인 사용
정리하자면!!
서비스란, 원격으로 호출될 수 있는 메서드(ex. addProduct, getProduct)의 모음
각 method에는 서비스의 일부로 정의 or 다른 프로토콜 버퍼 정의로 가져올 수 있는 입력 파라미터와 반환 타입 가지고 있음
입출력 파라미터는 사용자 정의 타입(Product, ProdcutID 타입) or 프로토콜 버퍼의 well-known type(https://oreil.ly/0Uc3A)
이런 타입들은 메세지로 구조화됨, 각 메세지는 필드라는 일련의 이름-값을 포함하는 작은 논리적 레코드, 메세지 바이너리 형식에서 필드를 식별하기 위한 고유 필드 번호(ex. string id = 1)를 갖는다.
2-1. gRPC 서버
서비스 정의가 완료되면, 이를 사용해 프로토콜 버퍼 컴파일러인 protoc로 서버측 또는 클라이언트 측 코드, 정의된 메세지 타입의 데이터 지정(populating), 직렬화(serializing), 데이터 취득(retrieving)의 일반 프로토콜 버퍼 코드를 생성할 수 있음
상위 서비스 클래스를 오버라이드함으로써 서버 스켈레톤(skeleton)의 서비스 로직 구현해야함.
서비스 구현되면 gRPC 서버를 실행해 클라이언트에서의 요청을 수신하고,(ex. TCP 포트 오픈)
해당 요청을 서비스 구현으로 지정한다음 , 서비스 결과를 다시 클라이언트로 반환
2-2. gRPC 클라이언트
서버 측과 마찬가지로 서비스 정의를 사용해 클라이언트 스텁(stub) 생성
스텁은 서버와 동일한 메서드 제공, 클라이언트 코드에서 메서드들의 호출을 네트워크 상 원격 함수 호출로 변환
gRPC 서비스 정의는 언어에 구애 받지 않음 -> 3rd party 통해 다양한 언어의 클라이언트와 서버 생성 가능
ex) 서버는 Go로 구현, 클라이언트 스텁은 Java로 생성
클라이언트 구현 단계 간단 설명
1. 원격 서버와의 연결 설정: 원격 서버 주소를 사용해 채널 생성
2. 해당 연결로 클라이언트 스텁을 지정: 채널을 사용해 블로킹 방식 스텁 생성
3. 이 스텁을 통해 원격 메서드 호출: 블로킹 스텁 통한 원격 메서드 호출
3. 클라이언트-서버 메세지 흐름
gRPC 클라이언트가 gRPC 서비스 호출할 때,
클라이언트의 gRPC 라이브러리: 서비스 호출 시 프로토콜 버퍼를 사용, RPC 호출 프로토콜 형식으로 마샬링, HTTP/2 통해 전송
서버측: 요청을 언마샬링, 각 프로시저 호출은 프로토콜 버퍼에 의해 실행됨
전송 프로토콜로 양방향 메세징을 지원하는 고성능 바이너리 프로토콜인 HTTP/2
gRPC 프로토콜을 살펴보기 전에 다양한 프로세스 간 통신 기술이 어떻게 발전해 왔는지 개략적으로 이해해보자.
프로세스 간 통신의 역사
기존 RPC
RPC는 클라이언트-서비스 애플리케이션을 구축하는데 널리 사용되는 프로세스 간 통신 기술
RPC 통해 클라이언트는 로컬 메서드를 호출하는 것처럼 원격으로 메서드의 기능을 호출할 수 있음
이전 RPC 구현 방식
- CORBA(Common Object Request Architecture): 90년대 초에 등장한 컴포넌트 규약, 프로그래밍 언어나 사용 환경 제약 없이 소프트웨어를 통합하고자 만들어짐. 주로 C/C++에서 사용, 구조가 복잡하고 적용이 어려워 2000년대 초 이후에는 거의 사용하지 않음
- RMI(Remote Method Invocation): 자바 어플리케이션의 가장 기본적인 통신 기술, 아직도 많이 사용하지만 자바에서만 사용 가능
이전 RPC 구현 방식의 단점
상호 운용성(interoperability)을 저해하는 TCP와 같은 통신 프로토콜로 구축
과장된 규격에 기반을 두기 때문에 매우 복잡함
Q. TCP가 왜 상호 운용성을 저해?
상호 운용성: 하나의 시스템이 동일 혹은 이기종의 다른 시스템과 아무런 제약 없이 서로 호환되어 사용할 수 있는 성질
한 시스템은 데이터 교환에 TCP 사용하고 다른 시스템은 다른 프로토콜을 사용하는 경우, 변환기나 게이트웨이 없이는 통신 불가능해서..?
SOAP
CORBA와 같은 기존 RPC 구현의 한계로, MS나 IBM같은 대규모 기업들이 설계
SOAP은 서비스 지향 아키텍처(SOA; Service-Oriented Architecture)에서 서비스(SOA에서는 웹서비스라 불리는) 간 XML 기반의 구조화된 데이터 교환용 표준 통신 기술
HTTP와 같은 프로토콜을 통해 통신
서비스 인터페이스, 해당 서비스의 operation과 호출에 사용되는 XML 메세지 포맷을 정의
단점
SOAP을 중심으로 구축된 규격의 복잡성, 메세지 포맷의 복잡성 -> 분산 애플리케이션 구축의 민첩성 저해
현대 분산 애플리케이션 개발 맥락에서는 SOAP 웹 서비스는 legacy 기술로 간주
기존 분산 애플리케이션 대부분이 REST 아키텍처 스타일을 대신 사용
REST
Roy Fielding의 박사 학위 논문에서 시작된 아키텍처 스타일
REST는 분산된 애플리케이션을 리소스 모음으로 모델링하는 자원 지향 아키텍처(Resource-Oriented Architecture)
리소스에 액세스하는 클라이언트가 해당 리소스의 상태(CRUD)를 변경할 수 있음
실질적인 구현은 HTTP
HTTP에서는 Restful 웹 어플리케이션을 고유한 식별자(URL)로 액세스할 수 있는 리소스 모음으로 모델링함
상태 변경 작업은 HTTP 메서드(GET, POST, PUT, DELETE, PATCH 등)의 형태로 리소스 위에 적용됨
자원 상태: JSON, XML, HTML, YAML 등의 텍스트 형식
HTTP와 JSON을 사용한 REST 아키텍처 스타일의 애플리케이션 구축
-> 범용적인 마이크로 서비스 구축 방법
단점
마이크로서비스가 많아지고 이들 간의 네트워크 상호작용의 확산 -> Restful 서비스는 최신 요구 사항 충족 X
Restful 서비스를 최신 마이크로서비스 기반 애플리케이션의 메세징 프로토콜로 사용할 수 없는 이유
1. 비효율적 텍스트 기반 메세지 프로토콜
Restful 서비스는 본질적으로 HTTP 1.x와 같은 텍스트 기반 전송 프로토콜로 구축됨, JSON처럼 사람이 읽을 수 있는 텍스트 포맷 활용
서비스 간 통신의 경우, 사람이 읽을 수 있는 텍스트 기반 포맷은 비효율적
클라이언트 애플리케이션(출발지)은 서버로 전송한 바이너리 콘텐츠 만듦
-> 해당 바이너리 구조체를 텍스트로 변환(HTTP 1.x는 텍스트 기반 메세지를 전송해야 하기 때문)
-> 네트워크 통해 텍스트로 전송(HTTP 통함)
-> 서비스(도착지) 측에서 다시 바이너리 구조로 변환
이 방식 보다는, 서비스와 소비자의 비즈니스 로직으로 바로 매핑될 수 있는 바이너리 형식으로 쉽게 전송하는게 나음
(JSON은 사람이 읽을 수 있어 쉽게 사용할 수 있다는 편리성이 있지만, 이는 도구의 문제로 편리성은 해결 가능함)
2. 엄격한 타입 점검 부족
polyglot 기술로 구축돼 네트워크를 통해 제공되는 서비스가 점점 증가함에 따라
명확하고(well-defined), 엄격하게 점검(strongly typed)되는 서비스 정의가 중요해짐
기존 RESTful 서비스에서 사용되는 OpenAPI/Swagger 같은 대부분 서비스 정의 기술은 근간의 아키텍처 스타일이나 메세징 프로토콜에 처음부터 고려되지 않아 polyglot 기술과 잘 통합되지 않음
-> 분산 애플리케이션 구축에 많은 비호환성, 런타임 에러, 상호호환성 이슈 등의 문제 야기
ex) RESTful 서비스 개발할 때, 서비스 정의나 애플리케이션 사이에 공유되는 정보의 타입 정의가 따로 요구되지 않음
-> 오히려 서비스되는 텍스트 포맷 확인 or OpenAPI와 같은 서드파티 API 정의 기술을 활용
결국 최신의 엄격한 타입 서비스 정의 기술과 폴리 글랏용 서버 및 클라이언트 측 중요 코드를 생성하는 프레임워크의 필요성이 증대됨
* Polyglot 프로그래밍: 요구사항이나 시스템 성격에 맞는 다양한 언어를 사용해 개발하는 것
3. REST 아키텍처 스타일 강제의 어려움
아키텍처 스타일로서 REST는 실제 서비스 구축에 도움이 되는 좋은 사례가 많음
But, HTTP와 같은 구현 프로토콜 일부로 통합되지 않아 구현 단계에서 REST 규칙을 적용하는게 쉽지 않음
대부분 RESTful 서비스는 실질적으로 REST 스타일의 기본 규칙을 준수하지 않는 경우가 많아 단순 HTTP 서비스로 제공되는 경우 많음
-> RESTful 서비스의 일관성과 규칙을 유지하는데 개발팀의 시간이 허비됨
gRPC의 시작
최신 클라우드 네이티브 애플리케이션의 프로세스 간 통신 기술 적용의 제약사항으로 인해 새로운 메세지 프로토콜의 탐구가 시작됨
구글은 2015년도 오픈소스 RPC 프레임워크로 gRPC를 출시
표준화되고 범용적인 크로스 플랫폼 RPC 인프라 담당
초당 수천억건의 인터넷상 요청 처리하는 성능, 확장성, 기능 제공
넷플릭스, 스퀘어, 리프트, 도커, 시스코, CoreOS 등 주요 기업의 대규모 도입으로 지난 몇 년 크게 성장
클라우드 네이티브 컴퓨팅을 보편적이고 지속 가능하게 만드는데 기여하는 가장 인기있는 오픈소스 소프트웨어 재단 중 하나인 CNCF(Cloud Native Computing Foundation)에 합류
왜 gRPC 인가?
기존 프로세스 간 통신 기술의 대부분 단점을 극복할 수 있는 인터넷 규모의 통신 기술로 설계됨
-> 대부분의 최신 애플리케이션과 서버는 프로세스 간 통신 프로토콜을 gRPC로 대체하고 있음
gRPC의 장점
1. 프로세스 간 통신 효율성
JSON, XML 같은 텍스트 형식 사용하는 대신 프로토콜 버퍼 기반 바이너리 프로토콜 사용해 통신
HTTP/2 위에 프로토콜 버퍼로 구현
-> 프로세스 간 통신 속도 매우 빨라짐
2. 간단 명확한 서비스 인터페이스와 스키마
애플리케이션 개발용 명세 우선(contract-first) 접근 방식 권장
서비스 인터페이스 정의 후 나중에 구현 세부 사항 작업
gRPC는 RESTful의 OpenAPI/Swagger나 SOAP의 WSDL과 달리 간단하지만 일관되고 안정적인 확장 가능한 애플리케이션 개발 경험 제공
*contract-first: 서비스 제공을 위한 개발 방식 중 하나, 명세를 먼저 정의하고 개발하는 것 일컬음.
반대로 코드를 먼저 작성하고 나중에 코드 기반의 명세를 생성하는 건 코드 우선(code-first)
*WSDL: 웹 서비스 정의 언어(Web Service Definition Language)로, SOAP 표준에서 사용하는 서비스 정의 언어
3. 엄격한 타입 점검 형식
프로토콜 버퍼 사용하기 때문에 gRPC 서비스 계약은 애플리케이션 간 통신에 사용할 데이터 타입 명확하게 정의
정적 타이핑(static typing)은 여러 팀과 기술에 걸친 클라우드 네이티브 애플리케이션 구축할 때 발생할 수 있는 대부분의 런타임과 상호운용 에러 극복하는데 도움 -> 안정적인 서비스가 가능
4. 폴리글랏
여러 프로그래밍 언어와 작동하도록 설계됨
프로토콜 버퍼 기반 -> 특정 언어에 구애 받지 않음
기존 gRPC 서비스나 클라이언트와 상호 연결하고자 여러 프로그래밍 언어를 선택할 수 있음!!
5. 이중 스트리밍(duplex streaming)
클라이언트나 서버측 스트리밍 기본적으로 지원
서비스 정의 자체에 포함 -> 스트리밍 서비스, 스트리밍 클라이언트를 훨씬 쉽게 개발할 수 있음
기존의 request-response 스타일의 메세징 방식이나 클라이언트 및 서버측 스트리밍을 구축하는 기능
-> 기존 RESTful 메세징 스타일에 비해 주요 장점
*(full) duplex streaming: 단일 통신 채널 통해 데이터를 양방향으로 동시에 송수신, 요청-응답 주기 기다리지 않고 동시에 서로에게 데이터 보낼 수 있음
실시간 및 지속적인 양방향 데이터 교환이 가능
이더넷, TCP/IP 연결과 같은 최신 네트워크 통신에 사용됨
활용 사례) VoIP(Voice over IP); Skype, Zoom, 실시간 채팅 애플리케이션, 온라인 게임
* half duplex streaming: 데이터를 양방향으로 전송할 수는 있지만 동시에 전송할 수는 없음. 통신 채널은 데이터 전송과 수신 번갈아 함.
한쪽이 말을 하면 상대방이 듣고, 그 반대도 마찬가지
6. 유용한 내장 기능 지원
인증, 암호화, 복원력(deadline, timeout), 메타데이터 교환, 압축, 로드밸런싱,서비스 검색 등과 같은 필수적인 기능 기본 지원(5장 참고)
7. 클라우드 네이티브 생태계와 통합
CNCF의 일부이기 때문에 대부분의 최신 프레임워크와 기술은 gRPC 기본 지원
8. 성숙하고 널리 채택됨
구글의 강력한 테스트를 통해 완성됨
주요IT 기술 회사에 채택됨
gRPC의 단점
1. 외부 서비스 부적합
인터넷을 통해 애플리케이션이나 서비스를 외부 클라이언트에 제공하려는 경우, 대부분의 외부 사용자는 gRPC가 새롭기 때문에 적합하지 않을 수 있음
contract-driven 이면서, 강력한 타입 속성 가짐 -> 외부 당사자에게 노출되는 서비스의 유연성을 방해할 수 있음, GraphQL같은 프로토콜과 달리 사용자는 훨씬 적은 제어권을 가짐
gRPC gateway는 이 문제 해결책으로 설계됨 -> 8장!! 궁금하다!!
2. 서비스 정의의 급격한 변경에 따른 개발 프로세스 복잡성
스키마 수정은 흔함 -> gRPC 서비스 정의가 급격히 변경되면, 클라이언트, 서버 코드 모두 다시 생성해야함!
기존의 CI(Continuous Integration) 프로세스에 통합돼야하는데, 전체 개발 수명 주기를 복잡하게 할 수 있음
But, 대부분의 gRPC 서비스 정의 변경은 서비스 계약을 위반하지 않게 수용될 수 있고,(int -> float 정도 일까?)
주요 변경 사항이 없는 한 다른 버전의 proto(프로토콜 버퍼의 프로토 파일)를 사용해 클라이언트 및 서버와 문제 없이 상호 운용 가능
즉, 대부분의 경우 코드 재생성은 필요하지 않음
3. 상대적으로 작은 생태계
기존 REST나 HTTP 프로토콜에 비해 상대적으로 작음
브라우저와 모바일 앱에서 gRPC 지원도 여전히 초기 단계
-> gRPC는 모든 프로세스 간 통신 요구 사항에 사용해야하는 기술은 아님.
늘 그래왔든, 비즈니스 usecase나 요구사항을 평가하고 적절한 메세징 프로토콜 선택해야함
다른 프로토콜과 비교: GraphQL과 Thrift
REST의 주요 한계점으로 인해 탄생한 gRPC, 비슷한 요구를 충족시키고자 등장한 다양한 프로세스 통신 기술 있음
Apache Thrift
gRPC와 유사한 RPC 프레임 워크(페이스북에서 개발돼 나중에 아파치 재단으로 기증됨)
자체 인터페이스 정의 언어 사용
광범위한 프로그래밍 언어 지원
정의 파일에서 데이터 타입과 서비스 인터페이스 정의 가능
thrift 컴파일러는 해당 서비스 정의를 사용해 클라이언트, 서버 측 코드 생성
쓰리프트 전송 계층에서 네트워크 I/O 추상화 제공
시스템의 나머지 부분에서 쓰리프트에 대한 의존성 분리(decouple) 가능
-> TCP, HTTP 와 같은 다양한 전송 방식처럼 실행 가능
gRPC와의 차별점
전송
스트리밍
채택과 커뮤니티
성능
GraphQL
gRPC 실사례
요약
Q. 프로토콜(HTTP) vs 통신 기술(Rest, RPC 등)
프로토콜에는 TCP/IP가 있고, SMTP, FTP/SFTP 등이 있음