yoongrammer

gRPC 란? 본문

분산컴퓨팅

gRPC 란?

yoongrammer 2020. 11. 11. 15:08
728x90

목차

    gRPC 란?


    gRPC는 구글이 개발한 RPC 시스템입니다.

    gRPC는 대부분의 언어를 지원하며 PB(Protocol Buffer)를 IDL(Interface Definition Language)로 사용합니다.

     

    gRPC에서 원격에 있는 애플리케이션의 메서드를 로컬 메서드인 것처럼 직접 호출할 수 있으므로 분산 애플리케이션 및 서비스를 더 쉽게 만들 수 있습니다.

    참고: https://grpc.io/docs/what-is-grpc/introduction/

    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를 사용하기 위해선 아래 도구들이 필요합니다.

    아래 명령어를 사용하여 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.gohelloworld_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를 사용하여 클라이언트는 서버에 있는 메서드를 마치 자신의 메서드처럼 사용할 수 있게 됩니다.

    728x90

    '분산컴퓨팅' 카테고리의 다른 글

    Raft Consensus Algorithm 알아보기  (0) 2021.01.20
    JSON 알아보기  (0) 2020.12.28
    RPC (Remote Procedure Calls) 란?  (0) 2020.11.10
    Protocol Buffer 란?  (0) 2020.11.09
    Comments