마이크로 서비스와 gRPC를 활용한 서버 간 통신 적용기 2

배우다·2021년 5월 2일
0
post-thumbnail

Rest API를 사용하지 않고 GRC를 선택한 이유?

마이크로서비스 api 간 통신을 굳이 사용한 이유는 gRPC만의 장점이 서비스 성능에 있어 더 좋은 결과를 낼 수 있다고 생각했기 때문이다. Rest Api의 경우 클라이언트가 요청할 때마다 connection을 생성해야 한다. 그로 인한 병목현상이 생길 수도 있다. 그러나 gRPC의 경우는 처음 요청 시 생성된 connection을 통해 지속적인 통신을 할 수 있으며 요청에 대한 데이터를 바이너리로 보내기 때문에 훨씬 빠른 성능을 보여준다. 아직 많은 정보가 없어 시행착오가 많이 생기겠지만 GRC를 서비스에 적용해 좀 더 서비스에 도움이 되길 기대해 본다.

GRC를 활용한 간단한 RPC서버 통신 구축하기

spring boot와 gRpc 에 대한 예제를 찾던 중 간단하게 gRPC를 적용할 수 있는 gRPC-spring-boot-starter를 찾았다. grpc-spring-boot-starter를 활용하여 간단하게 gRPC 서버를 만들어 보자. 우선 프로시져 실행을 요청할 client와 요청한 프로시져를 실행 후 결과를 반환할 server를 두 가지 서버를 구축해 볼 것이다.



Client

1. 클라이언트 서버에 필요한 의존성을 추가.

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:2.4.5")
    }
}

plugins {
    id "com.google.protobuf" version "0.8.16"
    id 'org.springframework.boot' version '2.4.5'
    id 'io.spring.dependency-management' version '1.0.11.RELEASE'
    id 'java'
}

group = 'com.tandohak'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'

repositories {
    mavenCentral()
}

dependencies {
    // for gRpc'
    implementation 'net.devh:grpc-client-spring-boot-starter:2.11.0.RELEASE'

    // for spring boot
    implementation 'org.springframework.boot:spring-boot-starter-web'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'

    compileOnly 'org.projectlombok:lombok:1.18.20'
    annotationProcessor 'org.projectlombok:lombok:1.18.20'

    testCompileOnly 'org.projectlombok:lombok:1.18.20'
    testAnnotationProcessor 'org.projectlombok:lombok:1.18.20'
}

configurations.all {
    resolutionStrategy.eachDependency { details ->
        if ("io.grpc".equalsIgnoreCase(details.requested.group)) {
            details.useVersion '1.37.0'
        }
    }
}

test {
    useJUnitPlatform()
}

2.ProtoBuf 생성을 위해 task 생성 스크립트 추가

  • Proto File을 작성한 뒤 빌드 하면 main/java/protoGen 패키지 아래에 protoBuf를 위한 자바 파일이 생성된다.
sourceSets {
    main {
        java {
            srcDir 'src/main/protoGen'
        }
    }
}

protobuf {
    protoc {
        artifact = 'com.google.protobuf:protoc:3.5.1'
    }


    clean {
        delete "$generatedFilesBaseDir/main/protoGen"
    }

    plugins {
        grpc {
            artifact = "io.grpc:protoc-gen-grpc-java:1.37.0"
        }
    }

    generateProtoTasks {
        ofSourceSet('main').each { task ->
            task.builtins {
                java {
                    outputSubDir = 'protoGen'
                }
            }
            task.plugins {
                grpc {
                    outputSubDir = 'protoGen'
                }
            }
        }
    }

    generatedFilesBaseDir = "$projectDir/src/"

}

3. application.yml 에 설정 추가

gpring:
  application:
    name: local-grpc-client
    
grpc:
  client:
    local-grpc-server:
      address: 'static://127.0.0.1:6666'
      enableKeepAlive: true
      keepAliveWithoutCalls: true
      negotiationType: plaintext

4.ProtoBuf를 통해 주고받을 데이터를 위한 프로토 파일 생성.

Greeter.proto 생성

syntax = "proto3";
option java_multiple_files = true;
option java_package = "net.devh.boot.grpc.examples.lib";
option java_outer_classname = "GreeterProto";

service Greeter {
  rpc SayHello ( HelloRequest ) returns ( HelloReply ) {}
}


// The request message containing the user's name.
message HelloRequest {
  string name = 1;
}

message MapFieldEntry {
  string name = 1;
  bool result = 2;
}

// The response message containing the greetings
message HelloReply {
  MapFieldEntry result = 1;
}

5. 요청을 위한 서비스 작성.

@Service
public class GreeterService {
	
    @GrpcClient("local-grpc-server")
    private GreeterGrpc.GreeterBlockingStub greeterStub;

    public MapFieldEntry hello(final String name) {
        try {
            final HelloReply response =
                    this.greeterStub.sayHello(HelloRequest.newBuilder().setName(name).build());
            return response.getResult();
        } catch (final StatusRuntimeException e) {
            return MapFieldEntry.newBuilder().setResult(false).build();
        }
    }
}

6. Application 파일 작성.

@RestController
@RequiredArgsConstructor
@SpringBootApplication
public class GrpcSpringBootClientApplication {

    private final GreeterService greeterService;

    public static void main(String[] args) {
        SpringApplication.run(GrpcSpringBootClientApplication.class, args);
    }
    
    @GetMapping("/")
    public ResponseEntity<?> greeter(String name) {

        MapFieldEntry entry = greeterService.hello(name);

        if (entry.getResult()) {

            return ResponseEntity.ok(entry.getName());

        } else {
            return ResponseEntity.badRequest().build();
        }

    }

}

Server

1. 의존성 추가

    implementation 'net.devh:grpc-server-spring-boot-starter:2.11.0.RELEASE'

* client 서버와 같이 protoBuf를 생성해주는 task를 만들어 준다.

2. application.yml 설정 추가.

gpring:
  application:
    name: local-grpc-server
grpc:
  server:
    port: 9898

3. client와 같이 proto file을 작성해 준다.

4. gRPC 통신을 위한 서비스 생성.


@Slf4j
@GrpcService
public class GreeterService extends GreeterGrpc.GreeterImplBase {

    @Override
    public void sayHello(HelloRequest request, StreamObserver<HelloReply> responseObserver) {
        String message = "SERVER 1 - Hello" + request.getName();

        MapFieldEntry mapFieldEntry = MapFieldEntry.newBuilder()
                .setName(request.getName())
                .setResult(true)
                .build();

        final HelloReply.Builder replyBuilder = HelloReply.newBuilder().setResult(mapFieldEntry);
        responseObserver.onNext(replyBuilder.build());
        responseObserver.onCompleted();
        log.info("SERVER 1 - Returning" + message);


    }
}

Post Man 으로 요청해보기.

client (포트:8081) 에 요청을 하면 결괏값을 반환한다. 여기까지 봤을 때는 평범한 Rest API와 다르지 않다.

Server의 로그를 보면 클라이언트에 요청한 파라미터 값이 전달 된 것을 볼 수 있다.

간단한 예제로 gRPC 통신을 만들어 보았다. 앞으로 갈 길이 멀지만 좀 더 알아보고 연구해 실제 서비스에 적용해 보아야겠다.


참고 사이트

grpc-spring-boot-starter github 링크

profile
안녕하세요. JAVA, SPRING, VUEJS, NUXT 사용하는 백엔드 개발자입니다.

0개의 댓글