gRPC란?

google에서 개발한 RPC(Remote Procedure Call)이다. gRPC는 모든 환경에서 실행할 수 있는 오픈 소스 RPC 프레임워크이다. HTTP/2기반으로 다양한 언어에서 사용이 가능하며 스텁, 프로토콜버퍼 등의 특징이 있다.

 

RPC란?

프로세스 간 통신(IPC)의 형태로 떨어져 있거나 분산되어 있는 컴퓨터간의 통신을 위한 기술이다. 중요한 것은 한 프로그램이 네트워크의 세부 정보를 이해하지 않고도 네트워크 안의 다른 컴퓨터에 있는 프로그램에 서비스를 요청할 수 있는 프로토콜이라고 보면 된다. RPC는 client-server 모델을 사용한다. client에서 서비스를 요청(function call)하면, server에서 서비스를 제공하는 형태이다.

Implementation of RPC mechanism

클라이언트 -> stub -> RPC Runtime -> RPC Runtime -> Server stub -> Server 과정의 매커니즘이다.

 

Stub이란?

RPC의 핵심 개념으로 매개변수(parameter) 객체를 메시지(Message)로 변환(Marchalling)/역변환(Unmarshalling)하는 레이어이며 클라이언트의 스텁과 서버의 스텁으로 나뉘어 클라이언트와 서버 간 통신에 필요한 코드를 생성하는 역할을 한다.

기본적으로 gRPC는 Protobuf를 사용하여 서비스를 정의한다. stub을 사용함으로써, 클라이언트는 서비스의 메소드를 직접 호출하지 않고, 스텁을 통해 메소드로의 요청을 전달하고, 스텁이 반환한 프로토콜 버퍼를 클라이언트 코드로 변환한다. 이와 같은 프로세스는 클라이언트와 서버 모두에서 간단하고 일관된 인터페이스를 제공한다.

또한, stub은 작동하는 데 필요한 모든 코드를 자동으로 생성하므로 쉬운 업데이트와 유지관리가 가능하며 stub을 통해 클라이언트와 서버가 요청과 응답을 하기때문에 언어에 자유도가 있다.
  • 클라이언트 스텁
    • 함수 호출에 사용된 파라미터의 변환 및 함수 실행 후 서버에서 전달된 결과의 변환
  • 서버 스텁
    • 클라이언트가 전달한 매개 변수의 역변환 및 함수 실행 결과 변환

gRPC

위의 그림처럼 스텁을 이용해서 서버와 클라이언트간의 언어가 달라도 요청과 응답을 구현할 수 있다. .proto파일을 protoc를 사용하여 생성된 코드는 아래와 같다. (해당 코드가 stub이라고 일컫는 것 같다. --- 추정)

// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.2.0
// - protoc             v3.21.12
// source: grpc/grpc.proto

package hello

import (
	context "context"
	grpc "google.golang.org/grpc"
	codes "google.golang.org/grpc/codes"
	status "google.golang.org/grpc/status"
)

// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.32.0 or later.
const _ = grpc.SupportPackageIsVersion7

// TestClient is the client API for Test service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type TestClient interface {
	Show(ctx context.Context, in *TestRequest, opts ...grpc.CallOption) (*TestResponse, error)
}

type testClient struct {
	cc grpc.ClientConnInterface
}

func NewTestClient(cc grpc.ClientConnInterface) TestClient {
	return &testClient{cc}
}

func (c *testClient) Show(ctx context.Context, in *TestRequest, opts ...grpc.CallOption) (*TestResponse, error) {
	out := new(TestResponse)
	err := c.cc.Invoke(ctx, "/grpc.Test/Show", in, out, opts...)
	if err != nil {
		return nil, err
	}
	return out, nil
}

// TestServer is the server API for Test service.
// All implementations must embed UnimplementedTestServer
// for forward compatibility
type TestServer interface {
	Show(context.Context, *TestRequest) (*TestResponse, error)
	mustEmbedUnimplementedTestServer()
}

// UnimplementedTestServer must be embedded to have forward compatible implementations.
type UnimplementedTestServer struct {
}

func (UnimplementedTestServer) Show(context.Context, *TestRequest) (*TestResponse, error) {
	return nil, status.Errorf(codes.Unimplemented, "method Show not implemented")
}
func (UnimplementedTestServer) mustEmbedUnimplementedTestServer() {}

// UnsafeTestServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to TestServer will
// result in compilation errors.
type UnsafeTestServer interface {
	mustEmbedUnimplementedTestServer()
}

func RegisterTestServer(s grpc.ServiceRegistrar, srv TestServer) {
	s.RegisterService(&Test_ServiceDesc, srv)
}

func _Test_Show_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
	in := new(TestRequest)
	if err := dec(in); err != nil {
		return nil, err
	}
	if interceptor == nil {
		return srv.(TestServer).Show(ctx, in)
	}
	info := &grpc.UnaryServerInfo{
		Server:     srv,
		FullMethod: "/grpc.Test/Show",
	}
	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
		return srv.(TestServer).Show(ctx, req.(*TestRequest))
	}
	return interceptor(ctx, in, info, handler)
}

// Test_ServiceDesc is the grpc.ServiceDesc for Test service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var Test_ServiceDesc = grpc.ServiceDesc{
	ServiceName: "grpc.Test",
	HandlerType: (*TestServer)(nil),
	Methods: []grpc.MethodDesc{
		{
			MethodName: "Show",
			Handler:    _Test_Show_Handler,
		},
	},
	Streams:  []grpc.StreamDesc{},
	Metadata: "grpc/grpc.proto",
}

위처럼 Client와 Server 별로 interface가 생성되고 개발자는 해당 interface를 통해 gRPC를 메소드처럼 사용하기만 하면 된다.

 

 

프로토콜버퍼(protocol buffer)

gRPC는 IDL(Interface Definition Language : 정보를 저장하는 규칙)로 프로토콜버퍼(protocol buffer)를 사용한다.
프로토콜 버퍼는 구조화된 데이터를 직렬화하기 위한 구글의 언어 중립적, 플랫폼 중립적, 확장 가능한 메커니즘을 말합니다. 보통 XML이나 JSON을 생각할 수 있지만 더 작고 빠르고 간단하다.

아래는 .proto 파일을 protoc를 통해 생성한 IDL된 코드이다. 

	RequestString string `protobuf:"bytes,1,opt,name=requestString,proto3" json:"requestString,omitempty"`

protobuf로 byte로 데이터를 직렬화 하는 모습을 볼 수 있다. 자세한 직렬화하는 과정은 이후에 정리해보도록 하자.

 

IDL (Interface Definition Language)

대표적인 IDL로는 JSON, XML, Proto buffer(proto)가 있다. 

IDL 대표 3개 비교

Proto가 가장 빠르며 비용도 적은 모습을 볼 수 있습니다.

 

 

gRPC의 구조

gRPC는 애플리케이션, 프레임워크, 전송계층으로 나눠져 구성되어있다. 지금 보면 HTTP2 위에 올라가있는 것을 알 수 있다.

gRPC 구조

 

gRPC의 채널

gRPC는 여러 서브채널을 열어서 통신한다. 이 채널을 재사용함으로써 통신비용을 줄일 수 있다. gRPC는 HTTP 2.0을 사용하는데 HTTP 1.1의 단점을 보완한 기술이다.

gRPC Channel

코드로 보자.

	// Server
	lis, err := net.Listen("tcp", ":50051")
	if err != nil {
		log.Fatalf("failed to listen: %v", err)
	}
	s := grpc.NewServer()
	pb.RegisterTestServer(s, &server{})
	log.Printf("server listening at %v", lis.Addr())
	if err := s.Serve(lis); err != nil {
		log.Fatalf("failed to serve: %v", err)
	}


	// Client 
	conn, err := grpc.Dial("localhost:50051", grpc.WithTransportCredentials(insecure.NewCredentials()))
	if err != nil {
		log.Fatalf("did not connect: %v", err)
	}
	defer conn.Close()
	c := pb.NewTestClient(conn)

위처럼 Server에서 특정 port로 Server를 해주고, Client에서 연결해주면 Channel이 생성된다. 해당 Channel을 통하여 통신을 주고받는다.

 

 

gRPC와 RESTAPI 비교

gRPC는 다음과 같이 프로토콜버퍼를 중심으로 통신하지만 REST API의 경우 HTT로 통신한다. gRPC-WEB은 gRPC로만 통신하는 웹서비스를 뜻한다.

 

워크플로우

gRPC를 이용한 workflow는 아래와 같다. protobufs를 정의한 다음 protoc 컴파일러를 통해 여러가지 언어를 기반으로 서버와 클라이언트가 형성된다.

gRPC workflow

 

장점

gRPC는 대부분의 아키텍쳐에 사용할 수 있지만 MSA에 가장 적합한 기술이다. 많은 서비스 간의 API 호출로 인한 성능 저하를 개선하며 보안, API 게이트웨이, 로깅등을 개발하기 쉽다.

위의 그림처럼 MSA 환경에서 gRPC는 지연시간 감소에서 강력한 강점을 가진다고 한다. (?? 아직 모르겠음)

 

  • 높은 생산성, 프로토콜 버퍼의 IDL만 정의하면 서비스와 메세지에 대한 소스코드가 자동으로 생성되고 데이터를 주고 받을 수 있다.
  • gRPC는 HTTP/2 기반으로 통신하며 이 때문에 양방향 스트리밍이 가능하다.
  • gRPC는 HTTP/2 레이어 위에서 프로토콜 버퍼를 사용해 "직렬화된 바이트 스트림"으로 통신하여 JSON 기반의 통신보다 더 가볍고 통신 속도가 빠르다.
  • 런타임 환경과 개발환경을 구축하기가 굉장히 쉽다.
  • 다양한 언어를 기반으로 만들 수 있다.

단점

  • 브라우저에서 gRPC 서비스 직접 호출이 불가능하다. 
    • 하지만, 프록시 서버를 통해서 쉽게 해결할 수 있음

 

 

 

 

 

 

참고자료

'Go' 카테고리의 다른 글

Import/Export  (0) 2023.12.24
package와 main  (0) 2023.12.24
Go에서 의존성 주입 (wire, fx 사용방법)  (0) 2023.05.14
go로 gRPC Unary 찍먹  (0) 2023.05.01
Golang 설정 및 실행  (0) 2023.04.30

Tool

  • vscode

 

Golang install

homebrew를 이용해서 설치 가능 (최신 버전으로 설치)
!이전 버전으로 설치하고 싶은 경우 go@1.16과 같은 방법으로 설치가 가능
brew install go

 

go version 확인

go version

 

go env

잘 못된 방식으로 설치를 하다가 GOPATH 경로가 이상하게 설정되어 아래와 같이 수정해줌
/usr/local/go path는 golang이 설치된 경로를 확인한 다음 수정
export GOPATH="/usr/local/go"

 

go 파일 실행

vscode로 .go 파일을 만들고 아래와 같이 코드 작성
package main

import "fmt"

func main() {
    fmt.Println("hello world!")
}

cmd 에서 아래 코드 실행

go run 파일명.go

출력

hello world!

 

 

'Go' 카테고리의 다른 글

Import/Export  (0) 2023.12.24
package와 main  (0) 2023.12.24
Go에서 의존성 주입 (wire, fx 사용방법)  (0) 2023.05.14
go로 gRPC Unary 찍먹  (0) 2023.05.01
gRPC란?  (0) 2023.05.01

+ Recent posts