gRPC

gRPC란?

Google에서 개발한 고성능 open source RPC framework 이다.

원격 프로시저 호출(RPC, Remote Procedure Call)이란 Network 너머의 함수를 마치 로컬 함수처럼 호출하는 방식이다. gRPC는 이것을 현대적으로 구현한 것.

주로 MicroService Architecture(MSA)에서 서비스 간 통신에 널리 사용됨.

주요 특징

Protocol Buffers (protobuf)

  • 데이터를 직렬화하는 인터페이스 정의 언어(IDL)
  • JSON보다 훨씬 작고 빠른 바이너리 형식
  • .proto 파일로 서비스와 메시지를 정의

HTTP/2 기반

  • Multiplexing으로 단일 연결에서 여러 요청 처리
  • Header 압축으로 overhead 감소
  • 양방향 Streaming 지원

REST vs gRPC

REST와 gRPC를 비교하면 아래와 같음.

gRPC가 적합한 경우

  • Microservice 간 내부 통신
  • 낮은 지연시간이 중요한 경우
  • 실시간 streaming이 필요한 경우
  • Polyglot(다중 언어) 환경

gRPC를 사용하는 이유

성능

JSON은 텍스트라 parsing 비용이 크고 용량도 크다. Protobuf는 binary이므로 직렬화가 훨씬 빠르고 payload 크기도 작음. 여기에 HTTP/2의 multplexing과 header 압축이 더해져, 특히 traffic이 많은 microservice 간 통신에서 체감 차이가 큼.

타입 안정성

REST/JSON은 field 이름 오타나 type 불일치를 runtime에서야 발견함. gRPC는 .proto 파일이 contract 역할을 하므로, client와 server가 서로 다른 schema를 갖는 상황 자체가 compile 단계에서 차단됨.

Streaming

REST로 실시간 data를 처리하려면 websocket이나 SSE같은 별도 기술을 조합해야 함.

gRPC는 단방향, server streaming, client streaming, 양방향 streaming을 하나의 framework 안에서 일관되게 지원함.

다중 언어 지원(Polyglot)

.proto 파일 하나로 Go, Java, Python, Node.js, C++등 여러 언어의 client/server 코드를 자동 생성할 수 있어, 서비스마다 다른 언어를 쓰는 팀에서 interface 불일치 문제를 크게 줄일 수 있음.

gRPC의 동작 흐름

1. 개발 단계 (Complie time)

.proto 파일에 service와 message 구조를 정의하면, protoc compiler가 client용 stub과 server용 스켈레톤 코드를 자동 생성함. 개발자는 이 코드를 기반으로 구현.

2. 요청 흐름 (client -> server)

  1. client app이 stub의 method를 일반 함수처럼 호출
  2. stub이 요청 객체를 protobuf binary로 직렬화
  3. HTTP/2 stream으로 서버에 전송 (header 압축 + multiplexing)
  4. server가 binary를 수신 후 역직렬화하여 객체로 복원
  5. server의 business logic 실행

3. 응답 흐름 (server -> client)

  1. server가 응답 객체를 Protobuf로 직렬화 후 HTTP/2로 전송
  2. client가 수신 후 역직렬화
  3. stub이 결과를 client app에 반환 – 마치 로컬 함수의 반환값처럼 전달됨.

gRPC 간단 구현

gRPC를 Debian Linux 상에서 간단하게 구현 해 보자.

Topology

Common Configuration

; Install Packages
# apt update
# apt install curl git wget unzip net-tools

; Install Golang
# wget https://go.dev/dl/go1.26.3.linux-amd64.tar.gz
# sudo tar -C /usr/local -xzf go1.26.3.linux-amd64.tar.gz
# cat << EOF >> .bashrc
export PATH=$PATH:/usr/local/go/bin
export GOPATH=$HOME/go
export PATH=$PATH:$GOPATH/bin
EOF

; Verify Installation
# go version
go version go1.26.3 linux/amd64

; Install protoc compiler
apt install protobuf-compiler

; Install protoc plugin for Go
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest

; Make a new project
# mkdir grpc-demo && cd grpc-demo
# mkdir proto server client

# go mod init grpc-demo
# go get google.golang.org/grpc
# go get google.golang.org/protobuf

proto 코드를 아래와 같이 작성 (vim proto/hello.proto)

syntax = "proto3";

package hello;

option go_package = "grpc-demo/hello";

// 요청: 이름을 보냄
message HelloRequest {
  string name = 1;
}

// 응답: 메시지와 서버 IP를 함께 반환
message HelloResponse {
  string message   = 1;
  string server_ip = 2;
}

// 단방향 RPC
service Greeter {
  rpc SayHello (HelloRequest) returns (HelloResponse);
}

Compile 및 파일 이동

# protoc \
  --go_out=. \
  --go_opt=paths=source_relative \
  --go-grpc_out=. \
  --go-grpc_opt=paths=source_relative \
  proto/hello.proto \
  --go_opt=Mproto/hello.proto=grpc-demo/hello \
  --go-grpc_opt=Mproto/hello.proto=grpc-demo/hello

# mkdir hello
# mv proto/*.go hello/

grpc-srv Configuration

서버 코드를 아래와 같이 작성 (vim grpc-demo/server/main.go)

package main

import (
    "context"
    "fmt"
    "log"
    "net"
    "os"

    pb "grpc-demo/hello"
    "google.golang.org/grpc"
    "google.golang.org/grpc/reflection"
)

type server struct {
    pb.UnimplementedGreeterServer
}

// SayHello: 클라이언트 요청을 받아 응답 반환
func (s *server) SayHello(
    ctx context.Context,
    req *pb.HelloRequest,
) (*pb.HelloResponse, error) {

    // 서버 자신의 IP를 응답에 포함
    hostname, _ := os.Hostname()
    addrs, _ := net.LookupHost(hostname)
    serverIP := "192.168.10.10"
    if len(addrs) > 0 {
        serverIP = addrs[0]
    }

    log.Printf("[요청 수신] name=%s, 클라이언트=%s",
        req.GetName(),
        ctx.Value("peer"),
    )

    return &pb.HelloResponse{
        Message:  fmt.Sprintf("안녕하세요, %s! — grpc-srv에서 응답", req.GetName()),
        ServerIp: serverIP,
    }, nil
}

func main() {
    port := ":50051"
    lis, err := net.Listen("tcp", port)
    if err != nil {
        log.Fatalf("리슨 실패: %v", err)
    }

    s := grpc.NewServer()
    pb.RegisterGreeterServer(s, &server{})

    // grpcurl 같은 도구로 서비스 목록 조회 가능하게 함
    reflection.Register(s)

    log.Printf("gRPC 서버 시작 — 포트%s", port)
    if err := s.Serve(lis); err != nil {
        log.Fatalf("서버 종료: %v", err)
    }
}

서버 빌드 및 실행

; Build
# cd grpc-demo
# go mod tidy
# go build -o bin/server ./server/

; Run (Background)
# nohup ./bin/server &

grpc-cnt Configuration

클라이언트 코드를 아래와 같이 작성 (vim grpc-demo/client/main.go)

package main

import (
    "context"
    "flag"
    "log"
    "time"

    pb "grpc-demo/hello"
    "google.golang.org/grpc"
    "google.golang.org/grpc/credentials/insecure"
)

func main() {
    // 실행 시 인자로 서버 주소 지정 가능
    serverAddr := flag.String("server", "192.168.10.10:50051", "gRPC 서버 주소")
    name       := flag.String("name", "VM2-Client", "전송할 이름")
    flag.Parse()

    // 서버 연결 (테스트 환경이므로 TLS 없이)
    conn, err := grpc.Dial(
        *serverAddr,
        grpc.WithTransportCredentials(insecure.NewCredentials()),
        grpc.WithBlock(),                        // 연결 성공까지 블록
        grpc.WithTimeout(5 * time.Second),       // 5초 타임아웃
    )
    if err != nil {
        log.Fatalf("서버 연결 실패: %v", err)
    }
    defer conn.Close()

    log.Printf("서버 연결 성공: %s", *serverAddr)

    client := pb.NewGreeterClient(conn)

    // RPC 호출
    ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
    defer cancel()

    res, err := client.SayHello(ctx, &pb.HelloRequest{Name: *name})
    if err != nil {
        log.Fatalf("RPC 호출 실패: %v", err)
    }

    log.Printf("응답 메시지 : %s", res.GetMessage())
    log.Printf("서버 IP     : %s", res.GetServerIp())
}

클라이언트 빌드

; Build
# cd grpc-demo
# go mod tidy
# go build -o bin/client ./client/

Test gRPC Demo

Server Port Listen

root@grpc-srv:~/grpc-demo# ss -lntp | grep 50051
LISTEN 0      4096               *:50051            *:*    users:(("server",pid=4877,fd=4))

Server Port Scan on Client

root@grpc-cnt:~/grpc-demo# nc -zvn 192.168.10.10 50051
(UNKNOWN) [192.168.10.10] 50051 (?) open

Client API Test (Single)

root@grpc-cnt:~/grpc-demo# ./bin/client -server 192.168.10.10:50051 -name "Debian-Client"
2026/05/27 15:19:48 서버 연결 성공: 192.168.10.10:50051
2026/05/27 15:19:48 응답 메시지 : 안녕하세요, Debian-Client! — grpc-srv에서 응답
2026/05/27 15:19:48 서버 IP     : 192.168.10.10

Client API Test (Multiple)

root@grpc-cnt:~/grpc-demo# for i in $(seq 1 10); do   ./bin/client -server 192.168.10.10:50051 -name "test-$i"; done
2026/05/27 15:22:08 서버 연결 성공: 192.168.10.10:50051
2026/05/27 15:22:08 응답 메시지 : 안녕하세요, test-1! — grpc-srv에서 응답
2026/05/27 15:22:08 서버 IP     : 192.168.10.10
2026/05/27 15:22:08 서버 연결 성공: 192.168.10.10:50051
2026/05/27 15:22:08 응답 메시지 : 안녕하세요, test-2! — grpc-srv에서 응답
2026/05/27 15:22:08 서버 IP     : 192.168.10.10
2026/05/27 15:22:08 서버 연결 성공: 192.168.10.10:50051
2026/05/27 15:22:08 응답 메시지 : 안녕하세요, test-3! — grpc-srv에서 응답
2026/05/27 15:22:08 서버 IP     : 192.168.10.10
2026/05/27 15:22:08 서버 연결 성공: 192.168.10.10:50051
2026/05/27 15:22:08 응답 메시지 : 안녕하세요, test-4! — grpc-srv에서 응답
2026/05/27 15:22:08 서버 IP     : 192.168.10.10
2026/05/27 15:22:08 서버 연결 성공: 192.168.10.10:50051
2026/05/27 15:22:08 응답 메시지 : 안녕하세요, test-5! — grpc-srv에서 응답
2026/05/27 15:22:08 서버 IP     : 192.168.10.10
2026/05/27 15:22:08 서버 연결 성공: 192.168.10.10:50051
2026/05/27 15:22:08 응답 메시지 : 안녕하세요, test-6! — grpc-srv에서 응답
2026/05/27 15:22:08 서버 IP     : 192.168.10.10
2026/05/27 15:22:08 서버 연결 성공: 192.168.10.10:50051
2026/05/27 15:22:08 응답 메시지 : 안녕하세요, test-7! — grpc-srv에서 응답
2026/05/27 15:22:08 서버 IP     : 192.168.10.10
2026/05/27 15:22:08 서버 연결 성공: 192.168.10.10:50051
2026/05/27 15:22:08 응답 메시지 : 안녕하세요, test-8! — grpc-srv에서 응답
2026/05/27 15:22:08 서버 IP     : 192.168.10.10
2026/05/27 15:22:08 서버 연결 성공: 192.168.10.10:50051
2026/05/27 15:22:08 응답 메시지 : 안녕하세요, test-9! — grpc-srv에서 응답
2026/05/27 15:22:08 서버 IP     : 192.168.10.10
2026/05/27 15:22:08 서버 연결 성공: 192.168.10.10:50051
2026/05/27 15:22:08 응답 메시지 : 안녕하세요, test-10! — grpc-srv에서 응답
2026/05/27 15:22:08 서버 IP     : 192.168.10.10

추가로 공부할 것

  • MSA (MicroService Architecture)
  • IDL (Interface Description Language)
  • Multiplexing
  • Polyglot
  • contract
  • HTTP version (HTTP/3 – 나무위키)