nyximos.log

gRPC 본문

ETC

gRPC

nyximos 2024. 9. 10. 22:12

gRPC란?

Google에서 개발한, 원격 프로시저 호출 Remote Procedure Call, RPC 프레임워크

  • 서로 다른 컴퓨터나 서버간 원격으로 함수를 호출할 수 있게 해주는 도구이다.

프로토콜 버퍼 Protobuf 직렬화 포맷을 사용해서 데이터와 메서드 호출을 정의

  • Google에서 만든 데이터 직렬화 포맷
  • JSON이나 XML보다 더 빠르고 작은 데이터 형식을 사용해 효율적으로 전송한다.
  • 서버와 클라이언트가 더 효율적으로 통신할 수 있다.

1. 개발자가 Protobuf로 데이터 구조(예: 사람의 이름, 나이 등)를 정의

2. 정의에 따라 Protobuf는 데이터를 작은 크기의 이진 데이터변환

3. 변환된 데이터 전송

4. 받는 쪽에서 이 데이터를 다시 원래의 형태로 복원

 

 

클라이언트가 A서버에 name을 보내면

A서버가 B서버로 gRPC 요청을 보내는 임의의 서비스를 실행해보자.

  • 클라이언트A 서버에 HTTP 요청을 보냄
  • A 서버B 서버로 gRPC 요청을 전송
  • B 서버는 요청을 처리하고 응답을 gRPC로 반환
  • A 서버는 결과를 받아 클라이언트에게 반환

 

build.gradle

  • grpc-netty-shaded : gRPC 서버와 클라이언트 구현을 위한 기본 네트워킹 라이브러리
  • grpc-protobuf : Protobuf와 gRPC 통합
  • grpc-stub : gRPC 클라이언트를 위한 스텁 코드 생성
  • protobuf-java : Protobuf 형식의 데이터를 자바 코드로 변환하는 라이브러리
  • grpc-server-spring-boot-starter : Spring Boot에서 gRPC 서버를 쉽게 설정할 수 있도록 지원하는 라이브러리
plugins {
    id 'java'
    id 'org.springframework.boot' version '3.3.3'
    id 'io.spring.dependency-management' version '1.1.6'
    id 'com.google.protobuf' version '0.9.3'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'

java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(21)
    }
}

repositories {
    mavenCentral()
}

def grpcVersion = '1.66.0'
def protobufVersion = '4.28.0'
def grpcSpringBootStarterVersion = '2.15.0.RELEASE'

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation "io.grpc:grpc-netty-shaded:${grpcVersion}"
    implementation "io.grpc:grpc-protobuf:${grpcVersion}"
    implementation "io.grpc:grpc-stub:${grpcVersion}"
    implementation "com.google.protobuf:protobuf-java:${protobufVersion}"
    implementation "net.devh:grpc-server-spring-boot-starter:${grpcSpringBootStarterVersion}"
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

protobuf {
    protoc {
        artifact = "com.google.protobuf:protoc:${protobufVersion}"
    }
    plugins {
        grpc {
            artifact = "io.grpc:protoc-gen-grpc-java:${grpcVersion}"
        }
    }
    generateProtoTasks {
        all()*.plugins {
            grpc {}
        }
    }
}

tasks.named('test') {
    useJUnitPlatform()
}

 

B서버에 grpc-server-spring-boot-starter를 추가해준다.

B 서버에서 gRPC 서버를 실행하고, A 서버로부터 요청을 받기 위한 설정.

implementation "net.devh:grpc-server-spring-boot-starter:${grpcSpringBootStarterVersion}"

 

A서버에 grpc-client-spring-boot-starter를 추가해준다.

A 서버에서 B 서버로 gRPC 요청을 보내기 위한 클라이언트 설정.

implementation "net.devh:grpc-client-spring-boot-starter:${grpcSpringBootStarterVersion}"

 

 

proto 파일 (hello.proto)

A서버 B서버 모두 있어야한다.

proto 파일은 gRPC 클라이언트(A 서버)와 gRPC 서버(B 서버)가 동일한 데이터 구조와 메서드를 공유하도록 해주기 때문에,

양쪽에서 동일한 proto 파일을 기반으로 gRPC 코드가 생성돼야한다.

 

 

  • A 서버B 서버 모두에서 이 proto 파일을 기반으로 각각의 gRPC 코드를 생성한다.
  • protoc를 사용하여 Java 코드를 생성하는 과정이 필요한데 build.gradle의 Protobuf 설정을 통해 자동으로 이루어진다.

 

syntax = "proto3";

package com.example.hello;

service HelloService {
  rpc SayHello (HelloRequest) returns (HelloResponse);
}

message HelloRequest {
  string name = 1;
}

message HelloResponse {
  string message = 1;
}

 

1. syntax = "proto3";

  • proto3는 Protocol Buffers(Protobuf)의 최신 문법 버전

2. package com.example.hello;

  • package는 이 프로토콜 파일의 네임스페이스를 정의
  • 여기서 com.example.hello는 이 프로토콜 버퍼의 패키지 이름

3. service HelloService

  • gRPC 서비스를 정의
  • 클라이언트가 호출할 수 있는 원격 메서드들의 모음

4. rpc SayHello (HelloRequest) returns (HelloResponse);

  • rpc는 원격 프로시저 호출 Remote Procedure Call 정의
    • 클라이언트는 이 메서드를 호출하여 서버로 요청을 보낸다.
  • SayHello: 클라이언트가 호출할 수 있는 메서드 이름
  • (HelloRequest): 이 메서드가 클라이언트로부터 받을 입력 메시지 형식
  • returns (HelloResponse): 이 메서드가 서버에서 클라이언트로 응답할 때 사용할 출력 메시지 형식

5. message HelloRequest

  • SayHello 메서드가 입력 받을 데이터 형식 정의
  • string name = 1; : 필드 정의
    • = 1: 필드 번호.
      • 프로토콜 버퍼는 필드에 번호를 할당하고 데이터를 직렬화할 때 사용한다.

6. message HelloResponse

  • SayHello 메서드가 반환할 데이터 형식을 정의합니다.
  • string message = 1; : 필드 정의
    • = 1: 필드 번호

 

B서버 : gRPC 서비스 구현

package com.example.bserver;

import com.example.hello.HelloRequest;
import com.example.hello.HelloResponse;
import com.example.hello.HelloServiceGrpc;
import io.grpc.stub.StreamObserver;
import net.devh.boot.grpc.server.service.GrpcService;

@GrpcService
public class HelloServiceImpl extends HelloServiceGrpc.HelloServiceImplBase {

    @Override
    public void sayHello(HelloRequest request, StreamObserver<HelloResponse> responseObserver) {
        String name = request.getName();
        String message = "Hello, " + name + "!";

        HelloResponse response = HelloResponse.newBuilder()
                .setMessage(message)
                .build();

        responseObserver.onNext(response);
        responseObserver.onCompleted();
    }
}

 

@GrpcService

  • gRPC 서비스일때 달아준다!
  • Spring Boot와 gRPC를 통합하여 자동으로 이 서비스를 gRPC 서버로 등록한다.

HelloServiceImpl extends HelloServiceGrpc.HelloServiceImplBase

  • gRPC로 정의된 서비스인 HelloService의 구현체
  • HelloServiceGrpc.HelloServiceImplBase: Protobuf 파일에서 자동으로 생성된 추상 클래스
  • 오버라이드하여 구현한다.

3. sayHello(HelloRequest request, StreamObserver<HelloResponse> responseObserver)

  • 클라이언트가 gRPC 호출을 통해 서버로 보내는 요청을 처리하는 메서드
  • StreamObserver<HelloResponse> responseObserver
    • 서버가 클라이언트에게 응답할 때 사용하는 비동기 응답 인터페이스
    • 서버는 이 인터페이스를 사용하여 클라이언트로 데이터를 반환합니다.
  • responseObserver.onNext(response);
    • 클라이언트에게 응답 메시지를 전송
  • responseObserver.onCompleted();
    • 응답이 완료되었음을 클라이언트에게 알린다.
    • 모든 응답을 다 보낸 후 호출

 

3. A 서버: gRPC 클라이언트 구현 (HelloClient.java)

package com.example.aserver;

import com.example.hello.HelloRequest;
import com.example.hello.HelloResponse;
import com.example.hello.HelloServiceGrpc;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import org.springframework.stereotype.Service;

@Service
public class HelloClient {

    private final HelloServiceGrpc.HelloServiceBlockingStub helloServiceStub;

    public HelloClient() {
        ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost", 9090)  // B 서버 주소, 포트
                .usePlaintext()  // SSL 비활성화
                .build();
        helloServiceStub = HelloServiceGrpc.newBlockingStub(channel);
    }

    public String sayHello(String name) {
        HelloRequest request = HelloRequest.newBuilder()
                .setName(name)
                .build();

        HelloResponse response = helloServiceStub.sayHello(request);
        return response.getMessage();
    }
}

 

4. A 서버: REST API 구현 (HelloController.java)

A 서버의 컨트롤러에서 HelloClient를 통해 B 서버에 gRPC 요청을 보내도록 수정합니다.

package com.example.aserver;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequiredArgsConstructor
public class HelloController {

    private final HelloClient helloClient;

    @GetMapping("/hello")
    public String sayHello(@RequestParam String name) {
        return helloClient.sayHello(name);  // gRPC 요청 전송
    }
}

 

'ETC' 카테고리의 다른 글

단방향 암호화 알고리즘 - MD5, SHA-256  (1) 2024.07.20