0%

gRPC 소개 및 go 예제

gRPC 란

general-purpose Remote Procedure Calls

google 에서 만든 오픈소스로, 원격지의 프로시저를 호출하는 프레임워크 입니다.

gRPC 는 다음과 같은 특징을 갖습니다.

  • 원격지 프로시저를 수행하는 규칙 및 파라미터 전달을 위한 인터페이스로 protocol buffer 라는 오픈소스를 활용하고 있습니다.
  • Blocking & Non-Blocking 을 지원합니다.
  • HTTP/2 프로토콜을 사용합니다.
  • 인증, 로드벨런싱, 트레이싱, 헬스체크 등을 제공합니다.
  • 10개 언어에서 지원되는 라이브러리가 있습니다.

gRPC 사용 및 흐름

  1. 실행하고자 하는 프로시저와, 전달하고자 하는 파라미터 사양을 .proto 파일로 작성합니다.
  2. protoc 를 통해 사용하고자 하는 언어에 맞게 stub 파일을 생성합니다.
    생성된 파일은 각 클라이언트가 참조할 수 있는 언어(.java .c .go 등..) 로써 bean 과 같이 데이터를 엑세스 하거나 핸들링하는 함수가 포함되어 있습니다.
  3. gRPC 에서 각 언어별로 제공하는 SDK 를 제공합니다. 이를 활용해 서버, 클라이언트를 프로그래밍 합니다.
    stub 을 활용해 실행될 프로시저를 구현하거나 전달할 파라미터를 생산할수 있습니다.

.proto 와 stub 파일

protocol buffer 를 사용하는 이점중 하나는 .proto 파일로 구조화된 데이터를 작성하기만 한다면 gRPC가 지원하는 어떤 언어에서든 규약에 상관없이 통신이 가능하다는 것입니다.
작성된 .proto 로부터 언어에 맞는 stub 를 생산하여 참조하게 되면 이후 별도의 사양서를 볼 필요없이 참조한 stub 만으로도 개발이 가능합니다.

gRPC plugin

gRPC 용으로 좀더 활용 가치가 있는 stub 을 만들기위해서는 protoc 에 grpc plugin 바이너리를 전달해야 합니다. 각 언어마다 plugin 이 존재하므로 해당 플러그인을 직접 구해서 stub 을 만들면 됩니다.
go 의 경우

1
protoc -I . config.proto --go_out=plugins=grpc:.

go server client 예제

Server

mod init

1
go mod init config_server

protocol buffer 정의

config/config.proto

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
syntax = "proto3";

package config;

// 설정 조회 서비스
service ConfigStore {
rpc Get (ConfigRequest) returns (ConfigResponse);
}

// 요청
message ConfigRequest {
string profile = 1;
}

// 응답
message ConfigResponse {
string json_config = 1;
}

go grpc 의존성 설치

1
2
go get -u google.golang.org/grpc
go get -u github.com/golang/protobuf/protoc-gen-go

protoc 로 컴파일

protoc -I config config.proto --go_out=plugins=grpc:config 를 통해 config.pb.go 파일을 생성합니다. 추후 로직에서 이 파일을 사용하게 됩니다.

:warning: 경험상 패키지명과 파일명이 일치하지 않으면 추후 오류가 발생되는데 이 부분은 좀더 확인이 필요합니다.

main 함수 작성

main.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package main

import (
pb "config_server/config"
"context"
"google.golang.org/grpc"
"log"
"net"
)

type server struct {
pb.UnimplementedConfigStoreServer
}

func (s *server) Get(ctx context.Context, in *pb.ConfigRequest) (*pb.ConfigResponse, error) {
log.Printf("Received profile: %v", in.GetProfile())
return &pb.ConfigResponse{JsonConfig: `"{"main":"http://google.com"}"`}, nil
}

func main() {
lis, err := net.Listen("tcp", ":8088")
if err != nil {
log.Fatalf("failed to listen: %v", err)
}

s := grpc.NewServer()
pb.RegisterConfigStoreServer(s, &server{})
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}

Client

mod init

1
go mod init config_client

protocol buffer 작성

위에서 컴파일된 config.pb.go 파일을 복사하여 사용해도 됩니다.

config/config.pb.go

main 함수 작성

main.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package main

import (
pb "config_client/config"
"context"
"google.golang.org/grpc"
"log"
"time"
)

func main() {
conn, err := grpc.Dial("localhost:8088", grpc.WithInsecure(), grpc.WithBlock())
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
c := pb.NewConfigStoreClient(conn)

ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()

r, err := c.Get(ctx, &pb.ConfigRequest{Profile: "dev"})
if err != nil {
log.Fatalf("could not request: %v", err)
}

log.Printf("Config: %v", r)
}

실행

1
2
3
4
5
6
7
8
9
10
11
12
13
# 서버 실행
go run main.go

# 클라이언트 실행 및 결과
go run main.go dev
2020/03/18 18:18:29 Config: json_config:"\"{\"main\":\"http://google.com\"}\""

go run main.go prod
2020/03/18 18:25:10 Config: json_config:"\"{\"main\":\"http://aws.com\"}\""

# 서버 결과
2020/03/18 18:18:29 Received profile: dev
2020/03/18 18:25:10 Received profile: prod