본문 바로가기

Web

[Web] gRPC 사용해보기

https://grpc.io/

개요

gRPC는 위에서의 설명과 같이 모든 환경에서 실행할 수 있는 고성능 RPC(원격 프로시저 호출) 프레임워크입니다. 구글에서 개발해서 g를 접두사로 붙였습니다. 원격 프로시저 호출이라는 의미는 다른 서버의 함수(프로시저)를 내 서버에서 실행할 수 있다는 의미이고 gRPC는 이를 지원하는 프레임워크입니다.

장점

  • HTTP/2와 Protobuf 등을 사용하여 빠르고 효율적인 통신. 특히 대용량 데이터 전송 시 이점이 있음
  • 서버와 클라이언트의 양방향 스트리밍을 지원하여 실시간 데이터 처리에 용이
  • 거의 모든 환경에서 사용 가능

단점

  • 디버깅이 어려움(Protobuf는 바이너리 방식)
  • 기본적인 브라우저 환경에서는 사용이 어려움. 추가적인 라이브러리 필요.

예시(Server)

이 글에서는 자주 사용하는 Web 서버의 상황을 예를 들기 위해 Spring Boot, Kotlin, gradle을 사용하는 환경을 기준으로 잡았습니다.

이후에는 Armeria 프레임워크를 사용하거나 Kotlin만을 사용해서 처리하는 방식도 알아보겠습니다.

참고: https://grpc.io/docs/languages/kotlin/quickstart/

 

Quick start

This guide gets you started with gRPC in Kotlin with a simple working example.

grpc.io

 

 

아래 예시는 Interface, Server, Client로 나누어 작성하였으며 전부 하나의 모듈에 작성하였습니다.

interface : proto를 작성하고 관리하는 모듈

Interface

build.gradle.kts

plugins {
    id("com.google.protobuf") version "0.9.4"
}

// Version 변수는 알맞게 추가합니다.

dependencies {
    implementation("io.grpc:grpc-protobuf:$grpcProtoVersion")
    implementation("io.grpc:grpc-kotlin-stub:$grpcKotlinVersion")
    implementation("com.google.protobuf:protobuf-kotlin:$grpcProtoKotlinVersion")
}

protobuf {
    protoc {
        artifact = "com.google.protobuf:protoc:$grpcProtoKotlinVersion"
    }
    plugins {
        id("grpc") {
            artifact = "io.grpc:protoc-gen-grpc-java:$grpcProtoVersion"
        }
        id("grpckt") {
            artifact = "io.grpc:protoc-gen-grpc-kotlin:$grpcKotlinVersion:jdk8@jar"
        }
    }
    generateProtoTasks {
        all().forEach { generateProtoTask ->
            generateProtoTask.plugins {
                id("grpc")
                id("grpckt")
            }
            generateProtoTask.builtins {
                id("kotlin")
            }
        }
    }
}

 

위의 내용을 build.gradle.kts에 추가합니다. gRPC를 통해 사용할 함수 내용을 작성하기 위한 부분만 포함하고 있습니다.

만약 위에서 "id()"를 찾지 못한다는 에러가 발생한다면 protobuf 부분을 주석처리 후 플러그인 부터 로드하시면 해결됩니다.

 

.proto 파일 작성

syntax = "proto3";

package com.marrrang.grpc.lib;

option java_multiple_files = true;
option java_package = "com.marrrang.grpc.lib";
option java_outer_classname = "MarrrangProto";

service Marrrang {
  rpc SayHello (HelloRequest) returns (HelloReply);
}

message HelloRequest {
  string name = 1;
}

message HelloReply {
  string message = 1;
}

 

간단하게 작성한 .proto 파일입니다. 위치는 src.main.kotlin.proto 하위에 작성하였습니다.

위에서 사용한 option에 대해서 추가적인 정보는 여기에서 찾아보실 수 있습니다.

gradle build

./graldew build

 

성공적으로 build 된다면 build.generated.source.proto.main 폴더 하위에 java, kotlin 폴더에서 저희가 만든 함수 내용을 확인할 수 있습니다.

Server

Interface 내용과 동일한 모듈에서 이후 내용도 진행합니다.

build.gradle.kts

dependencies {
    implementation("net.devh:grpc-spring-boot-starter:$version")
}

sourceSets {
    getByName("main") {
        java {
            srcDirs(
                "build/generated/source/proto/main/java",
                "build/generated/source/proto/main/kotlin"
            )
        }
    }
}

 

sourceSets : 소스 작성 시에 build한 내용을 참조하여 작성 가능하도록 명시해주는 부분

application.yaml

grpc:
  server:
    port: 9090

 

 

grpc 서버에 대한 설정을 추가합니다.

grpcService 작성

import com.marrrang.grpc.lib.HelloReply
import com.marrrang.grpc.lib.HelloRequest
import com.marrrang.grpc.lib.MarrrangGrpc
import io.grpc.stub.StreamObserver
import net.devh.boot.grpc.server.service.GrpcService

@GrpcService
class GrpcServerService: MarrrangGrpc.MarrrangImplBase() {
    override fun sayHello(req: HelloRequest, responseObserver: StreamObserver<HelloReply?>) {
        val reply: HelloReply = HelloReply.newBuilder()
            .setMessage("Marrrang Hello : " + req.name)
            .build()

        responseObserver.onNext(reply)
        responseObserver.onCompleted()
    }
}

 

위에서 com.marrrang.grpc.lib에서 import한 내용이 .proto 파일에서 정의한 내용으로 만들어진 것입니다.

// .proto 파일
service Marrrang {
    ...
}

// build 결과물
MarrrangGrpc {
    ...
}

build 결과물에는 service로 지정한 이름 뒤에 Grpc가 붙어서 클래스가 만들어졌습니다. 이 클래스 하위에 있는 구현체 Base를 상속받아서 서비스 로직을 작성하게 됩니다.

 

위 내용까지 작성 후 실행하고 PostMan을 사용하여 요청을 보내보면 아래와 같은 결과를 받을 수 있습니다.

postman으로 gRPC 요청 보냈을 시

 

 

Client

Client 부분은 조금 더 활용하기 위해 Spring Web을 추가하여 gRPC로의 직접 호출 이외에도 호출 가능한 부분을 만들려고 합니다.

build.gradle.kts

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-web:3.2.4")
}

 

ClientService 작성

import com.marrrang.grpc.lib.HelloRequest
import com.marrrang.grpc.lib.MarrrangGrpc
import net.devh.boot.grpc.client.inject.GrpcClient
import org.springframework.stereotype.Service
import java.util.concurrent.CompletableFuture

@Service
class GrpcClientService {
    @GrpcClient("marrrangGrpcClient")
    private lateinit var asyncStub: MarrrangGrpc.MarrrangFutureStub

    fun sayHello(name: String): CompletableFuture<String> {
        val request = HelloRequest.newBuilder().setName(name).build()

        val asyncResponse = asyncStub.sayHello(request)

        val completableFuture = CompletableFuture<String>()

        asyncResponse.addListener(
            {
                try {
                    val response = asyncResponse.get() // 비동기 결과 가져오기
                    completableFuture.complete(response.message)
                } catch (e: Exception) {
                    completableFuture.completeExceptionally(e) // 예외 처리
                }
            },
            { runnable -> runnable.run() }
        )

        return completableFuture
    }
}

비동기 처리를 기반으로 했습니다.

Controller 작성

import com.marrrang.grpcexample.service.GrpcClientService
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RestController
import java.util.concurrent.CompletableFuture

@RestController
class GrpcExampleController(private val grpcClientService: GrpcClientService) {
    @GetMapping("/sayHelloAsync")
    fun sayHelloAsync(@RequestParam name: String): CompletableFuture<String> {
        val responseFuture = grpcClientService.sayHello(name)

        return responseFuture
    }
}

 

Application.yaml 내용 추가

grpc:
  server:
    port: 9090
  client:
    marrrangGrpcClient:
      address: "static://localhost:9090"
      negotiation-type: plaintext

 

 

여기까지 설정 후 호출해보면 정상 처리되는 것을 볼 수 있습니다.

반응형

'Web' 카테고리의 다른 글

[Ktor] Ktor 소소하게 알아보기 - Server 방식  (0) 2022.01.08
TDD와 BDD  (0) 2020.11.30