쿼카러버의 기술 블로그

[gRPC] grpc 간단 개념 및 서버 / 클라이언트 실행 예제 (정말 쉬운 튜토리얼) 본문

웹 개발

[gRPC] grpc 간단 개념 및 서버 / 클라이언트 실행 예제 (정말 쉬운 튜토리얼)

quokkalover 2022. 2. 26. 17:43

본 글은 튜토리얼 성 글으로, 정말 간단하게 gRPC가 무엇인지 맛보고, 바로 구현에 들어간다. 

 

참고로 웹상에 있는 튜토리얼은 막상 해보면 안되는 경우가 많은데, 필자는 직접 다 실행해보고, 세팅까지 본 글에 적어두었으니, 본 글에 적힌대로 따라오기만 하면 gRPC 서버와 클라이언트를 쉽게 구현하고 실행해볼 수 있다. (생색)

 

자세한 내용은 추후 포스팅에서 다룰 예정이다. (노션엔 정리해뒀는 쓰다보니 너무 길고 복잡..)

 

Prerequisites

본 글의 내용을 직접 실습해보려면 아래 내용들이 충족돼야한다.

 

(1) protocol buffer 설치

go get -u github.com/golang/protobuf/protoc-gen-go

#맥북이면 brew install protobuf

(2) go env환경변수 설정

#vim ~/.bash_profile
export GO_PATH=~/go
export PATH=$PATH:/$GO_PATH/bin
#source ~/.bash_profile

 

gRPC와 REST의 차이점 및 특징

REST와 gRPC는 비슷한 것 같지만 사실 구조적으로 매우 다르다. gRPC가 어떻게 동작하는지에 대해 이해하기 위해서는 우선 REST와 어떤 차이가 있는지 살펴볼 필요가 있다.

 

  1. gRPC는 HTTP2를 사용한다. (REST는 HTTP1.1)
  2. gRPC는 protocol buffer data format을 사용한다. REST는 주로 JSON을 사용한다.
  3. gRPC를 활용하면 server-side streaming, client-side streaming, bidirectional-streaming과 같은 HTTP/2가 가진 feature를 활용할 수 있다.
  4. 내부적으로는 Netty(소켓통신)을 사용하고 있다.
  5. 이미 배포한 서비스를 중단할 필요 없이 데이터 구조를 바꿀 수 있다.

 

gRPC의 아쉬운 점들

아래와 같은 한계 때문에, gRPC개발은 살짝 까다롭다.

  • 관련 툴들의 부족 : API개발에는 주로 다양한 툴을 사용한다. 필자만 하더라도 postman, stoplight studio와 같은 툴들을 주로 사용하곤 하는데, 아직 Postman같은 API툴에서 gRPC서비스를 지원하지 않는다.
  • 로드밸런서가 HTTP/2를 지원해줘야만 정상적인 로드밸런싱이 가능 : envoy와 같은 reverse proxy와의 호환성

 

 

gRPC 서버, 클라이언트 구현

자 gRPC에 대한 개념을 간단하게 살펴봤으니 바로 Go언어로 gRPC 서버와 클라이언트를 구현해보자. 원래 직접 해보면서 해야 이해가 잘된다.

서버 구현

클라이언트와 통신할 수 있는 간단한 서버를 구현해보자.

기본적인 서버 구현

package main

import (
    "log"
    "net"

    "google.golang.org/grpc"
)

func main() {

// TCP connection을 listen할 port를 열어둔다.
    lis, err := net.Listen("tcp", ":9000")
    if err != nil {
        log.Fatalf("failed to listen: %v", err)
    }

// 위에서 열어둔 port를 gRPC 서버와 연결한다.
    grpcServer := grpc.NewServer()

    if err := grpcServer.Serve(lis); err != nil {
        log.Fatalf("failed to serve: %s", err)
    }
}

RPC는 다른 서버에 서비스를 요청하는 프로토콜이기 때문에, 한번 간단한 서비스 (function call)을 한번 만들어보자.

본 예시에서는 간단하게 채팅서비스를 만들어볼꺼고 먼저 통신 방식을 정해야 하기 때문에 예시로 chat.proto 파일을 를 정의하자

syntax = "proto3";
package chat;
option go_package = "./chat";

message Message {
  string body = 1;
}

service ChatService {
  rpc SayHello(Message) returns (Message) {}
}

이렇게 파일 이름에 .proto suffix가 붙는 파일들은 서버와 클라이언트가 서로 공유하는 파일로, gRPC를 통해 서로 어떻게 통신할 것인지에 대해 정의한다.

위 예시의 경우에는 ChatService라는 서비스를 expose하고, 이 서비스는 SayHello라는 함수 호출을 지원한다. 이 SayHello라는 함수는 Client가 어떤 프로그래밍 언어를 사용하는 것과는 관계없이, 그냥 이 호출 규약만 지키면 다 호출할 수 있게 해주는 약속이다.

이제 위처럼 proto 파일을 정의했으니 이 proto파일을 따라서 Go로 호출할 수있게 해주는 Go specific한 gRPC 코드를 만들어보자.

mkdir chat
protoc -I . --go_out=plugins=grpc:. protos/chat.proto

이렇게 실행하면 chat/chat.pb.go라는 파일이 생성될 것이다. 이제 우리는 이 chat.pb.go라는 파일에서 미리 정의해준 함수들을 활용해 gRPC통신을 할 수 있게 된다.

이제 통신을 위해 다음을 준비해야 한다.

1) 서버에 ChatService를 등록(Register)

2) chat/chat.go에 어떤 작업을 수행할 것인지에 대한 함수의 내용 정의.

  • 여태까지는 함수 호출을 어떻게 할건지 정한거고, 이제는 함수 호출시 어떤 동작을 하게 할건지 정해야 한다.

1) 서버에 ChatService를 등록

먼저 위에서 구현한 서버에 ChatService를 등록(Register)하자

package main

import (
    "fmt"
    "log"
    "net"

    "github.com/tutorialedge/go-grpc-beginners-tutorial/chat"
    "google.golang.org/grpc"
)

func main() {

    fmt.Println("Go gRPC Beginners Tutorial!")

    lis, err := net.Listen("tcp", fmt.Sprintf(":%d", 9000))
    if err != nil {
        log.Fatalf("failed to listen: %v", err)
    }

    s := chat.Server{}

    grpcServer := grpc.NewServer()

    chat.RegisterChatServiceServer(grpcServer, &s)

    if err := grpcServer.Serve(lis); err != nil {
        log.Fatalf("failed to serve: %s", err)
    }
}

2) chat.go에 SayHello가 호출되면 어떤 기능을 수행할 것인지 넣어주자

package chat

import (
    "log"

    "golang.org/x/net/context"
)

type Server struct {
}

func (s *Server) SayHello(ctx context.Context, in *Message) (*Message, error) {
    log.Printf("Receive message body from client: %s", in.Body)
    return &Message{Body: "Hello From the Richet's gRPC Server!"}, nil
}

위 기능 외에도 다른 함수들을 만들고 싶다면 새로운 메소드를 Server struct에 만들어주고, 위에서 만든 chat.proto파일에도 해당 함수를 적어주면 된다.

이제 서버 코드를 구현했어니 한번 실행해보고 클라이언트를 만들어보자~

아래로 코드를 실행하면 localhost:9000에서 gRPC통신을 대기하고 있다.

go run server.go

클라이언트 구현

package main

import (
    "log"

    "golang.org/x/net/context"
    "google.golang.org/grpc"

    "github.com/getveryrichet/gRPC-Tutorial/chat"
)

func main() {

    var conn *grpc.ClientConn
    // connection to server
    conn, err := grpc.Dial(":9000", grpc.WithInsecure())
    if err != nil {
        log.Fatalf("did not connect: %s", err)
    }
    defer conn.Close()

    // new client
    c := chat.NewChatServiceClient(conn)

    // function call
    response, err := c.SayHello(context.Background(), &chat.Message{Body: "Hello From Client!"})
    if err != nil {
        log.Fatalf("Error when calling SayHello: %s", err)
    }
    log.Printf("Response from Richet's gRPC server: %s", response.Body)

}

서버는 동작하고 있으니까, 이제 .proto파일을 통해 생성된 Client생성과 관련된 코드를 가져다가, 클라이언트를 통해 서버에 호출해보자

$ go run client.go

 

여기서 gRPC를 어떻게 활용할 수 있는지에 대한 기본 실행예제 글을 마치도록하겠다.

 

이후에는 protocol buffer가 무엇인지, gRPC의 통신 방식(unary, stream, bidirectional)에 대해서도 추후 포스팅에 다룰 예정이니 참고 바란다.

 

 

추가로 필자는 이제 여기에 더해서 다른 기능 메소드들을 추가로 구현해볼건데, 기본적인 코드 구조는 똑같으니 git repo를 참고하길 바란다. (star좀 눌러주고, fork도 해주면 감사..)

 

https://github.com/getveryrichet/gRPC-Tutorial

 

GitHub - getveryrichet/gRPC-Tutorial: Simple gRPC Server / Client implemented

Simple gRPC Server / Client implemented. Contribute to getveryrichet/gRPC-Tutorial development by creating an account on GitHub.

github.com

 

 

참고자료

https://tutorialedge.net/golang/go-grpc-beginners-tutorial/

https://chacha95.github.io/2020-06-15-gRPC1/

Comments