gRPC 란?
gRPC는 구글이 개발한 RPC 시스템입니다.
gRPC는 대부분의 언어를 지원하며 PB(Protocol Buffer)를 IDL(Interface Definition Language)로 사용합니다.
gRPC에서 원격에 있는 애플리케이션의 메서드를 로컬 메서드인 것처럼 직접 호출할 수 있으므로 분산 애플리케이션 및 서비스를 더 쉽게 만들 수 있습니다.
gPRC 클라이언트와 서버는 클라우드 환경에서 데스크탑, 모바일까지 다양한 환경에서 실행할 수 있고 다양한 언어를 지원합니다. (Go, Ptyhon, Ruby, Java, C++ 등등)
gRPC 서비스 정의
gRPC는 서비스 정의 개념을 기반으로하며 매개 변수 및 반환 유형을 사용하여 원격으로 호출할 수 있는 메서드를 지정합니다. 기본적으로 gRPC는 프로토콜 버퍼를 사용합니다.
service HelloService {
rpc SayHello (HelloRequest) returns (HelloResponse);
}
message HelloRequest {
string greeting = 1;
}
message HelloResponse {
string reply = 1;
}
gRPC를 사용하면 아래 4가지 종류의 서비스 방법을 정의 할 수 있습니다.
1. 단항 RPC
클라이언트가 서버에 단일 요청을 보내고 일반 함수 호출처럼 단일 응답을 받습니다.
rpc SayHello(HelloRequest) returns (HelloResponse);
2. 서버 스트리밍 RPC
서버가 클라이언트의 요청에 대한 응답으로 메시지 스트림을 반환한다는 점을 제외하면 단항 RPC와 유사합니다.
rpc LotsOfReplies(HelloRequest) returns (stream HelloResponse);
3. 클라이언트 스트리밍 RPC
클라이언트가 단일 메시지 대신 서버에 메시지 스트림을 보내는 것을 제외하고는 단항 RPC와 유사합니다.
rpc LotsOfGreetings(stream HelloRequest) returns (HelloResponse);
4. 양방향 스트리밍 RPC
서버, 클라이언트 양쪽에서 메시지 스트림을 보냅니다.
rpc BidiHello(stream HelloRequest) returns (stream HelloResponse);
gRPC 사용방법
gRPC를 사용하기 위해선 아래 도구들이 필요합니다.
- Go
- protoc
- Go plugin
아래 명령어를 사용하여 plugin을 설치합니다.
$ export GO111MODULE=on # Enable module mode
$ go get google.golang.org/protobuf/cmd/protoc-gen-go \
google.golang.org/grpc/cmd/protoc-gen-go-grpc
protoc 컴파일러가 플러그인을 찾을 수 있도록 PATH를 설정합니다.
$ export PATH="$PATH:$(go env GOPATH)/bin"
go 언어를 사용하여 간단한 코드를 작성해 보겠습니다.
1. workspace를 만듭니다.
$ mkdir examples
$ cd examples
$ mkdir helloworld
$ go mod init examples
go: creating new go.mod: module examples
$ ls
go.mod helloworld
2. .proto 파일에 서비스를 정의합니다.
examples/helloworld
디렉터리에서 helloworld.proto
파일을 만들고 아래와 같이 작성합니다.
syntax = "proto3";
option go_package ="examples/helloworld";
package helloworld;
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
3. protoc 컴파일러를 사용하여 서버 및 클라이언트 코드를 생성합니다.
examples
디렉터리에서 아래 명령어를 사용합니다.
$ protoc --go_out=. --go_opt=paths=source_relative \
--go-grpc_out=. --go-grpc_opt=paths=source_relative \
helloworld/helloworld.proto
컴파일이 완료되면 helloworld.pb.go
와 helloworld_grpc.pb.go
파일이 생성됩니다.
$ tree
.
├── go.mod
├── go.sum
└── helloworld
├── helloworld.pb.go
├── helloworld.proto
└── helloworld_grpc.pb.go
helloworld.pb.go
에는 요청 및 응답을 위한 프로토콜 버퍼 코드가 있습니다.
helloworld_grpc.pb.go
에는 서비스에 정의된 메서드를 사용하기 위한 인터페이스가 있습니다.
4. Go gRPC API를 사용하여 서비스를 위한 간단한 클라이언트와 서버를 작성합니다.
서버 생성
examples
디렉터리에 server
디렉터리를 만들어 그 안에 server를 위한 main.go
를 작성하겠습니다.
package main
import (
"context"
"log"
"net"
"google.golang.org/grpc"
pb "examples/helloworld"
)
const (
port = ":50051"
)
// server는 helloworld.GreeterServer를 구현하는데 사용됩니다.
type server struct {
pb.UnimplementedGreeterServer
}
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.GetName()}, nil
}
func main() {
lis, err := net.Listen("tcp", port)
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
// gRPC 서비스 인스턴스 생성
s := grpc.NewServer()
// gRPC 서버에 서비스 구현을 등록합니다.
pb.RegisterGreeterServer(s, &server{})
// lis에 들어오는 연결을 수락하여 서비스를 위한 고루틴을 생성합니다.
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
클라이언트 생성
examples
디렉토리에 client
디렉터리를 만들어 그 안에 client를 위한 main.go
를 작성하겠습니다.
package main
import (
"context"
"log"
"os"
"time"
"google.golang.org/grpc"
pb "examples/helloworld"
)
const (
address = "localhost:50051"
)
func main() {
// 서비스 메서드를 호출하기 위한 gRPC 채널을 생성합니다.
conn, err := grpc.Dial(address, grpc.WithInsecure(), grpc.WithBlock())
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
// Stub을 생성합니다.
c := pb.NewGreeterClient(conn)
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
// Stub에서 메서드를 호출합니다.
// 서버에 매개변수를 전달하여 서버에 있는 메서드를 호출하고 리턴값을 반환 받습니다.
r, err := c.SayHello(ctx, &pb.HelloRequest{Name: "world"})
if err != nil {
log.Fatalf("could not greet: %v",err)
}
log.Printf("Greeting: %s", r.GetMessage())
}
이제 서버와 클라이언트를 실행해 봅니다.
1. 서버 실행
examples/server
디렉터리에서 다음 명령을 실행합니다.
$ go run main.go
2. 클라이언트 실행
examples/client
디렉터리에서 다음 명령을 실행합니다.
$ go run main.go
다음과 같이 출력됩니다.
서버
2020/11/10 16:53:42 Received: world
클라이언트
2020/11/10 16:53:42 Greeting: Hello world
gRPC를 사용하여 클라이언트는 서버에 있는 메서드를 마치 자신의 메서드처럼 사용할 수 있게 됩니다.