# gRPC 개요 및 proto 파일 정의

2021. 5. 30. 12:30 개발 이야기/오픈소스

 

안녕하세요. 해커의 개발일기 입니다.

 

오늘은 MSA 구조에서 많이 쓰기고 있는 GRPC에 대해서 알아보도록 하겠습니다.

 

저도 회사에서 많이 사용하고 있기 때문에 처음에는 다른 사용법에 조금 헤멧지만 지금은 gRPC가 정말 편리하다는 것성능적으로 우월하는 점을 잘 알고 있기 때문에 초보적인 사용법과 더불어 심화된 내용 및 proto 파일 정의 내용도 다룰 예정입니다.

gRPC와 proto 파일에 대해서 알아보겠습니다. gRPC는 구글에서 개발안 오픈소스 원격 프로시저 호출(RPC) 시스템이고 전송을 위해서 HTTP/2를 사용하고 인터페이스 정의 언어로는 포로토콜 버퍼(.proto 파일)을 사용하고 있습니다. 기본적으로 gRPC는 HTTP/2 베이스기 때문에 외부에서 노출되어야 하고 로드밸런서 적용이 필요한 경우. 로드밸런서가 HTTP/2를 지원해줘야만 정상적인 로드밸런싱이 가능하다는 점을 알고 계셔야 합니다. 

gRPC에 대해서 간략이 알아보도록 하겠습니다. 명칭으로는 g가 google이라는 소문이 있기는 한데 정확한지는 모르겠습니다. 그래서 googleRPC 라는 추측이 있으며 소문에 걸맞게 구글에서 만든 오픈소스 입니다. 다양한 언어를 지원하고 여러 통신 방식을 지원하는 특징이 있습니다. 또한 내부적으로는 Netty(소켓통신)을 사용하고 있습니다.

  • 다양한 언어 지원
  • 동기식 요청-응답 / 완전 미동기 / 스트리밍 등 다양한 통신 방식 지원
  • HTTP/2, Protocol Buffer 사용

Protocol Buffer 또한 구글에서 개발하고 오픈소스로 공개한 직렬화 데이터 구조인데요. 인터페이스 정의용 언어(IDL Interface Definetion Language)를 사용해 데이터를 저장하기 위해 proto 라는 형태로 정의하고 protoc(proto compile)을 이용해 컴파일을 하면 언어에 맞는 코드를 생성해줍니다. 

 

gRPC에는 통신 방식에 따라 unary, stream이라는 용어가 존재합니다. unary는 http와 유사하게 stateless한 통신방식으로 request와 reponse를 1회 받을 때만 TCP 커넥션을 맺고 처리하는 방식이며, stream은 이름에서 유추할 수 있지만 지속적으로 데이터를 보내고 받을 수 있는 소켓 통신과 같이 사용할 수 있는 통신 방식입니다(gRPC의 unary가 HTTP 통신과 유사하지만 성능은 훨씬 좋습니다)

정의된 proto 파일을 보고 어떻게 통신 타입을 정의할 수 있는지 보겠습니다.

syntax = "proto3";

import "google/protobuf/wrappers.proto";

package ecommerce;

// 패키지 명시
service OrderManagement {
  rpc addOrder(Order) returns (google.protobuf.StringValue);
  rpc getOrder(google.protobuf.StringValue) returns (Order);
  rpc searchOrders(google.protobuf.StringValue) returns (stream Order);
  rpc updateOrders(stream  Order) returns (google.protobuf.StringValue);
  rpc processOrders(stream google.protobuf.StringValue) returns (stream CombinedShipment);
}

message Order {
  string id = 1;
  repeated string items = 2;
  string description = 3;
  float price = 4;
  string destination = 5;
}

message CombinedShipment {
  string id = 1;
  string status = 2;
  repeated Order ordersList = 3;
}

message Order는 데이터를 주고 받을 데이터 포멧입니다. repeatedArraystring items을 여러개 받겠다는 의미입니다. service 부분이 실제 데이터 통신을 어떻게 주고 받을 것인지 정의하는 부분으로 쉽게 설명하면 웹 서비스의 api 정의서라고 이해하시면 됩니다. rpc addOrder를 보면 Order라는 메시지 타입을 unary(단항)으로 보내겠다는 의미로 세션을 유지하지 않습니다. rpc updateOrders를 보면 stream으로 Order 메시지를 지속적으로 보내고 서버는 unary 로 응답을 주도록 정의된 모습입니다. 마지막으로 양방향 stream을 사용하려면 processOrders에 정의된 것과 같이 input과 output에 모두 stream을 넣으주면 양쪽에서 세션을 유지하고 있는 소켓통신과 같은 모습이 됩니다. 이렇게 필요에 맞게 설계 후 protoc 컴파일을 해주면 됩니다.

protoc 컴파일을 해보도록 하겠습니다. protoc을 설치하고 환경변수 설정하는 방법은 생략하도록 하겠습니다.

# protoc 설치 후 go 언어로 빌드하기위해 protoc-gen 설치
go install google.golang.org/protobuf/cmd/protoc-gen-gogo

# proto compile 명령어
$ protoc -I . protofile.proto --gogo_out=.

 

빌드를 하게되면 protofile.pb.go 파일이 생성되고 proto file에서 정의한 메시지 형식과 통신 방식을 go언어로 생성해줍니다(많은 부분 코드로 만들어주기 때문에 생산성이 아주 좋은것 같습니다) 그리고 중요한 점은 이렇게 protoc 컴파일로 생성된 파일은 절대 .. 수정하시지 않길 바랍니다. getter, setter 등이 필요하시다면 extension 기능을 통해 추가 가능합니다

 

이렇게 생성된 pb.go 파일 안에 있는 메서드를 이용해 손쉽게 코드에 가져다가 사용할 수 있습니다. grpc 커넥션을 만들고 proto 컴파일로 생성된 NewOrderManagementClient 생성자를 통해 클라이언트를 만들면 아래 코드와 그림과 같이 쉽게 사용할 수 있습니다. 하지만 protoc 컴파일로 생성된 파일을 수정해서 사용하시다보면 어디에선가 메시지 타입이 맞지 않는다는 에러가 발생하기 시작합니다.  만약 다시 protoc 컴파일로 파일을 만들지 않고 원인을 찾으시려고 하시다가는 .. 혼돈의 카오스로 ..... 

	conn, err := grpc.Dial(address1, grpc.WithInsecure())

	client := pb.NewOrderManagementClient(conn)
	ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
	defer cancel()
	i := 0
	for {
		i++
		n := strconv.Itoa(i)
		time.Sleep(10000)
		// Add Order
		order1 := pb.Order{Id: n, Items: []string{"iPhone XS", "Mac Book Pro"}, Destination: "San Jose, CA", Price: 2300.00}
		res, _ := client.AddOrder(ctx, &order1)
		if res != nil {
			log.Print("AddOrder Response -> ", res.Value)
		}

 

이상, 기초적인 grpc 사용방법과 protoc 사용방법에 대한 내용이었습니다. 써보신 분들은 아시겠지만 성능면에서 정말 우월하기 때문에 MSA 구조를 사용하신다면 한번 고려해보시길 추천해드립니다.

다음 글에서 심화 과정을 얘기해보겠습니다.