Why gRPC?
gRPC는 구글이 자신들의 데이터 센터에서의 범용 RPC와 다양한 End-Point 들간의 효율적인 연동을 위해서 만들어진 프로토콜이다. RPC라는 이름에 걸 맞게 로컬에서 서버(Remote)의 서비스(Procedure)를 API처럼 호출(Call)할 수 있도록 해주는 것이다. gRPC는 간략하게는 다음과 같은 장점을 가진다.
1) gRPC는 Protocol Buffer를 사용하여 서비스와 메시지를 정의할 수 있으며 이 정의에 따라서 각각의 프로그래밍 언어들을 위한 Stub 코드를 생성할 수 있다. 서비스와 메시지를 정의한 .proto 파일만 있으면 해당 서비스를 제공하는 서버와 연동할 수 있는 클라이언트를 각각의 언어에 맞게 생성하여 프로그래밍 할 수 있다.
지원 되는 언어는 android/java, Kotlin/JVM, Objective-C 와 같은 모바일과 C++, Go, Java, Node, Python, Ruby 등 다양한 프로그래밍 언어를 지원한다. Web은 gRPC HTTP/2를 지원하지 않고 gRPC-Web으로 다른 형태로 지원된다. (grpc.io/docs/languages/web/quickstart/ 참고)
따라서 gRPC의 경우 프로그래밍 관점에서 생산성이 높고 유지보수가 쉽다고 할 수 있다.
2) gRPC 는 HTTP/2 를 기반으로 통신하며 HTTP/2는 HTTP/1에 비해서 여러 장점이 있는데, 이는 gRPC가 RESTful 한 API나 WebSocket에 비해서 가지는 장점이라고 봐도 무관할것 같다. 대표적으로 양방향 스트리밍이 된다는 것이고 헤더 압축률에 의해서 네트워크 트래픽이 줄어들고 HTTP/1 대비 로딩 속도가 빠르다. gRPC는 Protocol Buffer에 의한 메시지 압축도 네트워크 트래픽을 줄이는데 더 도움이 된다. 즉 RPC의 성능이 더 좋아진다고 볼 수 있다.
(swiftymind.tistory.com/114, americanopeople.tistory.com/115 에 개괄적인 내용과 요소 등이 잘 설명되어 있는것 같다.)
Protocol Buffer에 대하여
Protocol Buffer는 구조화된 데이터를 직렬화하기 위하여 구글이 공개한 플랫폼 독립적인 확장 메커니즘이다. (developers.google.com/protocol-buffers참고) 현재 proto3 가 최신 버전이며 메시지 Type을 정의하고 이 메시지 Type을 기반으로 메시지를 정의할 수 있다. Protocol Buffer 정의는 .proto 파일에 기술한다. Service와 Message로 구성하여 제공되는 서비스와 이 서비스에 필요한 메시지들을 정의한다. namespace 관리를 위해 패키지를 선언할 수 있다. 다음은 protobuf 선언 예이다. (github.com/grpc/grpc-go/blob/master/examples/helloworld/helloworld/helloworld.proto 의예)
syntax = "proto3"; package helloworld; // The greeting service definition. service Greeter { // 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; } |
HelloRequest와 HelloReply 메시지가 정의되어 있으며 Greeter 서비스의 SayHello 라는 API가 정의되어 있다.
Go에서 gRPC 이용하기(helloworld 예제)
1. Protocol Buffer Compiler 설치 및 Stub 생성
Protocol Buffer 정의로부터 stub을 생성하려면 protoc 라는 컴파일러를 설치해야 한다. ubuntu의 경우 패키지 매니저로 설치 가능하지만 낮은 버전이 설치되기 때문에 다음과 같이 컴파일 하는 것을 추천한다.
$ sudo apt-get install autoconf automake libtool curl make g++ unzip -y
$ git clone https://github.com/google/protobuf.git
$ cd protobuf
$ git submodule update --init --recursive
$ ./autogen.sh
$ ./configure
$ make
$ make check
$ sudo make install
$ sudo ldconfig
Go 언어용 Plugin은 다음과 같이 설치한다. (grpc.io/docs/languages/go/quickstart/ 참고)
$ export GO111MODULE=on
$ go get github.com/golang/protobuf/protoc-gen-go
$ go get google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.0
protoc 가 go plugin을 찾게 하기 위해 PATH를 등록해 준다.
$ export PATH=$PATH:$GOPATH/bin
위에서 설명한 proto 정의를 helloworld 폴더에 helloworld.proto로 생성한다.
syntax = "proto3"; option go_package="helloWorld/helloworld"; package helloworld; // The greeting service definition. service Greeter { // 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; } |
여기에서 go_package를 option으로 지정해 준다. 그리고 다음과 같이 go stub 을 생성한다.
$protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative helloworld/helloworld.proto
만약 --go_opt 옵션이 지원되지 않는 경우 protoc를 업데이트 한다. 이 명령을 실행하고 나면 helloworld 폴더에 helloworld.pb.go, helloworld_grpc_pb.go 가 생성되있는 것을 알 수 있다.
2. Go Server 생성
다음은 상기의 stub을 이용한 서버이다.
package main import ( "context" "log" "net" pb "helloWorld/helloworld" "google.golang.org/grpc" ) const ( port = ":50051" ) // server is used to implement helloworld.GreeterServer. type server struct { pb.UnimplementedGreeterServer } // SayHello implements helloworld.GreeterServer func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) { log.Printf("Received: %v", in.GetName()) return &pb.HelloReply{Message:"Hello "+in.GetNme()},nil } func main(){ lis, err:=net.Listen("tcp",port) if err !=nil { log.Fatalf("failed to listen: %v",err) } s:=grpc.NewServer() pb.RegisterGreeterServer(s,&server{}) if err :=s.Serve(lis);err!=nil { log.Fatalf("failed to server:%v",err) } } |
$go build server.go 하게 되면 server binary가 생성된다.
3. Go Client 생성
다음은 상기 서버의 SayHello 서비스를 이용하는 클라이언트이다.
package main import ( "context" "log" "os" "time" "google.golang.org/grpc" pb "helloWorld/helloworld" ) const ( address ="localhost:50051" defaultName = "world" ) func main() { // Set up a connection to the server. conn,err:=grpc.Dial(address,grpc.WithInsecure(),grpc.WithBlock()) if err!=nil { log.Fatalf("did not connect : %v",err) } defer conn.Close() c:=pb.NewGreeterClient(conn) name:=defaultName if len(os.Args) > 1 { name=os.Args[1] } ctx,cancel:=context.WithTimeout(context.Background(),time.Second) defer cancel() r,err:=c.SayHello(ctx,&pb.HelloRequest{Name:name}) if err!=nil { log.Fatalf("could not greet :%v",err) } log.Printf("Greeting:%s",r.GetMessage()) } |
$go build client.go 하게 되면 client binary가 생성된다.