본문 바로가기
Framework/Spring Boot

gRPC 통신: JSON을 버리고 초고속 바이너리(Binary)를 달다

반응형

우리는 지금까지 프론트엔드와 백엔드가 데이터를 주고받을 때 당연하다는 듯이 { "name": "철수", "age": 25 } 같은 JSON (JavaScript Object Notation) 텍스트 포맷을 사용해 왔습니다. 사람이 읽기 쉽고 브라우저에서 잘 돌아가기 때문입니다. 그런데 마이크로서비스(MSA) 환경에서 뒷단의 [주문 서버]와 [결제 서버]끼리 통신할 때도 과연 사람이 읽기 쉬운 JSON을 써야 할까요? "name": 이라는 껍데기 글자를 파싱(Parsing)하느라 서버의 CPU와 네트워크 대역폭이 엄청나게 낭비됩니다. 구글(Google) 아저씨들은 참다못해 "서버끼리 통신할 때는 사람 눈치 보지 마! 텍스트(JSON) 집어치우고 컴퓨터가 제일 좋아하는 0과 1(이진수, Binary)로 압축해서 쏴버려!"라는 미친 성능의 프레임워크를 세상에 공개합니다. 바로 gRPC(Google Remote Procedure Call)입니다.

이번 [Spring Boot 실무 완벽 가이드] 시리즈에서는 "MSA 서버 간 통신의 абсолютный 최강자!" gRPC 프로토콜 버퍼(Protobuf)와 Spring Boot 연동 실무 아키텍처에 대하여 다뤄보겠습니다.

🗜️ 1. 핵심 무기: Protocol Buffers (프로토버프)

gRPC는 JSON 대신 구글이 만든 Protocol Buffers(Protobuf)라는 직렬화 방식을 사용합니다. .proto라는 파일에 우리가 주고받을 데이터 규격을 적어두면, 자바, 파이썬, Go 등 어떤 언어의 코드로든 자동으로 변환(Compile)해 줍니다.

gRPC 개념도
// src/main/proto/payment.proto
syntax = "proto3";
option java_package = "com.myapp.grpc";

// 1. 요청/응답 데이터 규격 (JSON 껍데기를 버리고 숫자와 타입만 정의)
message PaymentRequest {
    int64 orderId = 1; // 변수명 뒤의 =1, =2 는 값이 아니라 '필드 순서(Tag)' 입니다!
    int32 amount = 2;
}

message PaymentResponse {
    bool success = 1;
    string message = 2;
}

// 2. 서비스(인터페이스) 정의
service PaymentService {
    // 결제 서버야, 내가 Request 줄테니 Response 줘! (REST의 POST 역할)
    rpc ProcessPayment(PaymentRequest) returns (PaymentResponse);
}

.proto 파일을 빌드(Gradle 플러그인)하면, 자바 파일(PaymentServiceGrpc.java 등)이 수천 줄 자동으로 생성됩니다. 이 생성된 클래스를 가져다 쓰기만 하면 됩니다.


⚙️ 2. Spring Boot gRPC 서버 구현하기

결제 서버(gRPC Server) 측에서는 스프링 컨트롤러(@RestController) 대신 gRPC 전용 서비스 어노테이션(@GrpcService)을 달아줍니다. (외부 라이브러리인 grpc-spring-boot-starter 사용)

// 결제 서버 (gRPC Server) - 보통 9090 포트로 띄웁니다.
@GrpcService
@Slf4j
public class PaymentGrpcServiceImpl extends PaymentServiceGrpc.PaymentServiceImplBase {

    @Override
    public void processPayment(PaymentRequest request, StreamObserver<PaymentResponse> responseObserver) {
        // 1. 주문 서버에서 넘어온 바이너리 데이터를 객체로 받아 꺼냅니다.
        long orderId = request.getOrderId();
        int amount = request.getAmount();
        log.info("gRPC 요청 수신! 주문번호: {}, 금액: {}", orderId, amount);

        // 2. 응답(Response) 객체를 빌더 패턴으로 예쁘게 굽습니다.
        PaymentResponse response = PaymentResponse.newBuilder()
                .setSuccess(true)
                .setMessage("결제 성공! (초고속 바이너리 통신)")
                .build();

        // 3. 응답 발사 및 통신 종료 (콜백 방식)
        responseObserver.onNext(response);
        responseObserver.onCompleted();
    }
}

주문 서버(gRPC Client)에서는 @GrpcClient 어노테이션을 통해 마치 로컬 서버에 있는 자바 함수를 호출(RPC, Remote Procedure Call)하듯 아주 자연스럽게 결제 서버의 메서드를 호출할 수 있습니다.


💀 3. gRPC의 치명적 단점: 브라우저와 포스트맨의 눈물

성능은 압도적이지만 실무에서 모든 REST를 gRPC로 갈아엎지 못하는 이유가 있습니다. 프론트엔드(웹 브라우저)에서는 gRPC를 직접 호출할 수 없습니다. 브라우저의 자바스크립트는 바이너리를 던지는 HTTP/2 기반의 순수 gRPC 트래픽을 만들어낼 수 없기 때문입니다 (gRPC-Web 이라는 우회로가 있지만 매우 복잡합니다). 또한 포스트맨(Postman)으로 GET 쳐보면서 디버깅하던 달콤한 시절은 끝납니다. 바이트 배열이라 사람이 눈으로 디버깅할 수가 없기 때문입니다.

실무 결론: 프론트엔드와 백엔드의 통신은 여전히 REST(JSON) 또는 GraphQL을 쓰고, 백엔드 서버끼리 내부망에서 데이터를 미친 듯이 퍼 나를 때(Server-to-Server)만 gRPC를 도입하는 것이 글로벌 아키텍처 스탠다드입니다.


🎯 4. 마무리 및 다음 단계

지금까지 뚱뚱한 JSON의 허물을 벗어던지고 Protobuf 바이너리로 압축하여 HTTP/2의 고속도로 위를 질주하는 MSA 내부 통신의 최강자, gRPC의 작동 원리와 Spring Boot 구현법에 대해 상세히 다루어 보았습니다. 이제 여러분의 마이크로서비스들은 0.001초의 낭비도 없이 완벽하게 데이터를 주고받게 되었습니다.

웹소켓(31단계)으로 채팅도 만들었고, gRPC(44단계)로 서버 간 통신도 극대화했습니다. 그런데 갑자기 사장님이 "우리도 줌(Zoom)이나 구글 미트(Google Meet)처럼 1:1 화상 회의(영상 통화) 기능을 앱에 넣어줘!"라고 지시합니다. 영상과 음성을 웹소켓이나 서버를 거쳐서(Relay) 스트리밍하면 스프링 서버는 데이터 트래픽 요금 폭탄을 맞고 파산할 것입니다. "서버는 딱 처음 둘이 만날 수 있게 전화번호만 교환해 주고 빠지고, 영상 데이터는 철수 스마트폰에서 영희 스마트폰으로 다이렉트(P2P)로 쏴버릴 순 없을까?" 이어지는 45단계 포스팅에서는 "서버 부하 0%의 기적! 실시간 화상 회의의 표준" WebRTC(웹알티씨)의 P2P 시그널링(Signaling) 서버를 Spring Boot WebSocket으로 구축하는 완벽 가이드에 대해 아주 뼈 때리게 파헤쳐 보겠습니다!

반응형