gRPC
- 다른 개발자 분들께 공유할 용도로 개략적으로 쓰인 글입니다. 번역이 잘못되었을 수도 있습니다 ㅠ
gRPC란 무엇인가요?
- gRPC는 어떤 환경에서도 동작하는 모던한 오픈소스 원격 프로시저 요청 (Remote Procedure Call, RPC) 프레임워크입니다.
- gRPC는 구글에서 10년 이상동안 수 많은 MSA와 데이터센터 사이를 연결하기 위해 사용하던 Stubby라고 부르던 범용 RPC 인프라를 크로스플랫폼, 오픈소스화해서 만들어졌습니다.
- Stubby를 공개하기엔 구글 사내 서비스와 너무 타이트하게 연결이 있어서 spdy, http/2, QUIC 등을 지원하는 기능을 추가하고 Stubby기능을 좀 더 표준화하도록 수정해서 오픈되었습니다.
- gRPC stub
- client side에서 요청을 grpc 형태로 만들어주는 역할을 하는 컴포넌트의 이름입니다.
gRPC가 반드시 제공해야할 기능
- Services not Objects, Messages not References
- 요청을 큰 덩어리로 만들고 object를 여러 service에 분산되도록 만들지 않는 것(Don’t distribute your objects, Martin Fowler), 그리고 너무 높은 네트워크비용을 방지하는것(Fallacies of distributed computing, wikipedia).
- Streaming
- Blocking & Non-Blocking
기타 등등 … gRPC Principles 참조
gRPC의 장점
- Low Latency, Highly scalable, distributed systems.
- 클라우드 서버와 커뮤니케이션하는 모바일 클라이언트를 지원하는데에 초점이 맞춰져있습니다.
- http/2를 이용한 reverse proxy 가능
- 서버간에 http 1.1 keep-alive로 통신하고 있을 경우, 특정 요청이 holding되면 다음 요청도 전부 holding되는 문제가 생길 수 있습니다
- http/2 reverse proxy를 통해 multiplex하게 서버와 요청을 주고 받을 수 있게 됩니다.
- vendasta에서 gRPC 사용을 통해 얻은 세 가지 이점
- gRPC를 이용해 5개 이상의 언어로 이루어진 SDK-서버 간의 통신을 통합할 수 있었습니다.
- SDK 개발시에 더 이상 개발자가 api문서를 작성하지 않아도되고, api 형태가 어떤식으로 되어있는지 물어볼 필요가 없어졌습니다
- vendasta에서는 하나의 서버에 통신하기 위해 여러 언어로 SDK를 만들어서 해당 이점이 극대화될 수 있었습니다.
- gRPC 사용을 통해, 어떤 요청이 끝날 때 까지 기다릴 필요가 없이, 첫 요청이 들어오면 순서와 관계없이 서버에서 응답을 내보내주면 되기 때문에 첫 화면 구성이 더 빨라졌습니다.
- JSON을 프로토콜 버퍼로 변환하고난 뒤에, 서버/클라이언트에서 Serialization / Deserialization하는 것에 대한 어려움을 해소할 수 있게 되었습니다.
- gRPC에서는 하나의 요청 / 응답 형태에서 타입이 정해진 message를 사용합니다.
- gRPC를 이용해 5개 이상의 언어로 이루어진 SDK-서버 간의 통신을 통합할 수 있었습니다.
- Protocol Buffer
- XML, json등으로 들어온 요청 / 응답을 Protocol Buffer를 이용해 직렬화하여 더 작은 크기의 요청 / 응답으로 만들 수 있습니다.
- 기본적으로는 base128을 이용한 byte array 직렬화를 사용하지만, 사용방식에 따라 json, text 직렬화로 사용 가능합니다.
- 자바에서의 protoBuf의 예시입니다.
- 전화번호부를 만들기 위한 메시지 형태를 구성합니다.
syntax = "proto2";
package tutorial;
option java_package = "com.example.tutorial";
option java_outer_classname = "AddressBookProtos";
// message를 갖고 있는 outer class이름을 지정합니다.
// model은 message로부터 생성합니다. 이를 위해 protoc 라는 compiler를 사용해야합니다.
message Person {
required string name = 1;
required int32 id = 2;
optional string email = 3;
enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK = 2;
}
message PhoneNumber {
required string number = 1; // required를 쓸 때는 주의해야합니다. 누락될 경우 RuntimeException이 나기 때문에, 가능하면 optional과 repeated로 메시지를 구성하는 편이 좋습니다.
optional PhoneType type = 2 [default = HOME];
}
repeated PhoneNumber phones = 4;
}
message AddressBook {
repeated Person people = 1;
}
- protoc의 사용법 참고
- protoc로 만들어진 클래스를 이용해, 자바에서는 아래와 같이 메시지를 생성할 수 있습니다
Person john =
Person.newBuilder()
.setId(1234)
.setName("John Doe")
.setEmail("jdoe@example.com")
.addPhones(
Person.PhoneNumber.newBuilder()
.setNumber("555-4321")
.setType(Person.PhoneType.HOME))
.build();
- builder가 쓰기에 별로라면, 직접 커스텀코드를 작성해서 만들 수 있다고 되어있긴 합니다.
gRPC life cycle
- RPC 요청 1
- 처음 gRPC요청을 하기 위해 metadata를 서버에 전송하고, 서버에서는 deadline을 설정해서 client에 돌려줍니다.
- deadline은 RPC 요청의 성공 여부와 관계 없이 해당 RPC 요청을 언제 timeout시켜서 종료할지에 대한 정보를 갖고 있습니다.
- 서버는 바로 서버의 metadata를 response하거나, client의 다음 요청을 기다립니다
- 서버의 metadata는 반드시 response보다 먼저 보내야 합니다.
- 서버가 클라이언트로부터 요청을 받았다면, 어떤 것이든지 응답을 보내줘야 합니다.
- status가 ok라면 client가 응답을 받아서 요청을 완료합니다.
- 처음 gRPC요청을 하기 위해 metadata를 서버에 전송하고, 서버에서는 deadline을 설정해서 client에 돌려줍니다.
- Server streaming RPC
- 서버에서는 클라이언트에 response를 보내고 뒤따르는 메타데이터가 있다면 보낸 뒤에 complete를 의미하는 패킷을 보내는 것으로 연결을 종료합니다.
- Client streaming RPC
- 클라이언트에서는 request를 하나로 보내는 대신 여러 개를 스트리밍으로 보낼 수 있습니다.
- 서버는 일반적으로 하나의 response를 보내지만, 클라이언트가 모든 request를 보낼 때까지 기다릴 필요는 없습니다.
- Bidirectional streaming RPC
- 양방향 스트리밍 RPC에서는 클라이언트의 요청으로 stream이 연결된 뒤에, 서버에서는 클라이언트의 추가 요청을 기다립니다.
- 어플리케이션에 따라 다르겠지만, 클라이언트와 서버는 순서와 관계없이 읽기/쓰기를 반복할 것입니다.
- 스트림은 완전하게 독립적으로 동작합니다.
- 서버는 클라이언트의 모든 요청을 기다리거나, 핑퐁식으로 메시지를 주고받거나 할 수 있습니다.
- 데드라인과 타임아웃
- gRPC는 클라이언트가
DEADLINE_EXCEEDED
에러가 발생해서 RPC가 종료되기 전에 얼마나 RPC를 기다려야 하는지에 대해 정할 수 있습니다. - 서버에서는 쿼리를 날려서 특정 RPC가 time out이 되었는지, RPC 커넥션 시간이 얼마나 남았는지를 확인할 수 있습니다.
- 언어별로 데드라인이나 타임아웃의 형태는 다르게 설정됩니다
- 어떤 언어에서는 디폴트 데드라인이 없기도 하고, 어떤 언어는 특정 시각이 지나면 데드라인으로 판단하기도하고, 몇몇 언어는 특정 시간이 지나면 타임아웃으로 판단하기도 합니다.
- gRPC는 클라이언트가
- RPC Termination
- gRPC에서는, RPC의 종료를 클라이언트 / 서버에서 각각 판단합니다.
- 서버에서는 response가 finish 됐을 때, 클라이언트에서는 deadline이 지났을 때 등.
- gRPC에서는, RPC의 종료를 클라이언트 / 서버에서 각각 판단합니다.
- Canceling RPCs
- 서버, 클라이언트 모두 어떤 시점에서든지 RPC 연결을 취소할 수 있습니다.
- RPC를 취소한다는 것이 요청을 “롤백 (undo)” 한다는 의미는 아닙니다.
- Metadata
- metadata는 key-value list로 정의된 RPC 요청에 대한 정보입니다.
- header와 비슷한 형태인듯합니다.
- metadata는 key-value list로 정의된 RPC 요청에 대한 정보입니다.
- Channels
- 채널은 client 에서 stub을 만들 때 생성되는 호스트와 포트로 특정되는 gRPC 서버 연결에 대한 정보를 가집니다.
- 채널의 설정을 변경하거나, state (
connected
,idle
)를 확인할 수 있습니다.
gRPC 적용전 환경세팅
- centOS 6에서 C++ gRPC가 빌드되지 않는 문제가 있었습니다 (2017년 1월 18일)
- https://github.com/grpc/grpc/issues/9365
- gRPC 자바를 사용하면 되긴 합니다(https://grpc.io/docs/quickstart/java.html)
- JDK 7 이상
- 아직 웹에서는 안정적으로 사용할 수 없습니다.
- 다만 웹용 프로젝트가 2018년 10월 23일에 릴리즈되긴 했습니다(https://github.com/grpc/grpc-web, https://grpc.io/blog/grpc-web-ga)
- gRPC-Web은 브라우저-서버간의 gRPC통신을 가능하게 만들어줍니다.
- 다만 웹용 프로젝트가 2018년 10월 23일에 릴리즈되긴 했습니다(https://github.com/grpc/grpc-web, https://grpc.io/blog/grpc-web-ga)
- NGINX의 버전이 최소 1.13.10(18년 3월 20일 릴리즈, 18년 4월 17일 1.14.0에 포함되었습니다)이 되어야 합니다.
- Server Push가 적용되어 있습니다
- gRPC proxy pass가 적용되어있습니다.
- NGINX에 http/2 reverse proxy가 도입되지 않을 예정이기 때문에, http/2 또는 h2c로 nginx 서버간에 통신을 하기위해서는 gRPC를 사용하거나 server push를 사용해야 합니다.
- 현재 NGINX 에서는 Nginx Plus 라는 걸 만들어서 장사를 하려고 하기 떄문에, 당분간 http/2 proxy가 만들어지진 않을 것 같습니다.
- 무려 1개 인스턴스당 최소 연간 2500달러입니다. 엔터프라이즈 급은 연간 5000달러입니다 참고
서비스에 gRPC 적용 방식 예정
- Mobile, PC
- Client(web)-REST => Proxy Server (Nginx -> gRPC Gateway) => Server (Nginx -> gRPC stub -> spring)
Q&A
- protoc로 나온 모델 파일 (java의 경우 class)에서 message 파일 (.proto)을 만들어낼 수 는 없나요?
- 현재는 불가능합니다.
- client - server(load balancer) - server(backend) 사이의 stream은 하나를 공유해서 사용하게 되나요?
- client - server(backend) 사이의 stream이 독립적이기 때문에, server (load balancer) - server(backend) 사이의 stream도 여러 개가 생성되게 됩니다.
- thrift와 비교해서 장점은 어떤 것이 있나요?
- 성능적인 장점은 없습니다. RPC 간의 성능 비교를 한 문서를 링크해드립니다..
- 다만, gRPC의 경우 proto 파일(message)을 통한 클라이언트 별 구현이 용이하기 때문에 thrift에 비해 커뮤니케이션 비용을 줄여줄 수 있다는 장점이 있습니다.
- 당장 사용할 수 있나요?
- api g/w 에서 보안 부분을 처리해줘야합니다 (gRPC 요청에 대한 hmac 인증 적용 등)
- gRPC에서 채널단위 보안 / 요청단위 보안에 관련된 API를 제공하지만, token방식의 보안은 구글 OAuth2를 사용할 때만 지원하는듯 합니다. abstact 클래스를 직접 구현해서 기능을 확장하도록 권장하고 있습니다.
- gRPC authentication