본문 바로가기
개발용어

gRPC란, protobuf란, Java gRPC 예제 코드, grpc단점, grpc java code example

by devscb 2023. 6. 5.
반응형

gRPC란, protobuf란, Java gRPC 예제 코드, grpc단점, grpc java code example


최근 Micro service architecture 프로젝트를 진행하면서,
속도 향상에 대한 고민을 하다 gRPC에 대해 알아보았습니다.



1. gRPC란
2. protobuf란
3. gRPC 예제코드
4. gRPC VS REST 속도비교
5. gRPC 장/단점


1. gRPC란?


한줄요약 : 오픈소스 원격 프로시저 호출(*RPC) 방식중 하나.
* RPC : 함수가 실행프로그램의 원격위치에 있어도 동일한 코드를 이용하여 함수를 실행하는 기술

개발한곳 : 구글
특징:
1) HTTP/2 사용
--> HTTP/1보다 빠르다.

2) 인터페이스 정의 언어로 프로토콜 버퍼 사용 (protobuf를 사용하여 메시지 encode/decode)
--> 다양한 프로그래밍 언어 지원(Java, C#, Javascript, python 기타 등등)

3) 양방향 스트리밍 및 흐름 제어, 취소 및 타임아웃 등 제공
--> 통신을 위한 프로토콜제공



2. protobuf란


한줄요약 : 구조화된 데이터를 직렬화하는 방식 중 하나

특징
1) 여러 언어 지원 : python, kotlin, java, go, dart, c#, c++, javascript 등
2) proto 파일 작성하여 메시지 전송방식을 규정
3) proto 파일 -> 타 언어로 컴파일하여 사용.

proto buffer의 개발 흐름은 아래와 같습니다.


번역하면 아래와 같습니다.
1) .proto file을 만들고
2) protoc 컴파일러로 java, py, cc, 등의 코드를 생성
3) 해당 코드를 내가 사용하려는 프로젝트와 같이 빌드하여 사용


예를 들어, 아래와 같은 형식으로 된 파일을 작성하면
다양한 언어에서 이 클래스를 사용하기 위한 코드를 만들어줍니다.


message Person {
  optional string name = 1;
  optional int32 id = 2;
  optional string email = 3;
}



이를 protoc를 빌드하면 Java에서는 Builder 클래스를 만들게 되고,
java에서 아래와 같은 코드를 작성할 수 있습니다.


Person john = Person.newBuilder()
    .setId(1234)
    .setName("John Doe")
    .setEmail("jdoe@example.com")
    .build();
output = new FileOutputStream(args[0]);
john.writeTo(output);



동일한 proto파일을 C++로 컴파일하면,
C++ 프로젝트에서 다음과 같이 쓸 수 있습니다.


Person john;
fstream input(argv[1], ios::in | ios::binary);
john.ParseFromIstream(&input);
int id = john.id();
std::string name = john.name();
std::string email = john.email();





3. gRPC 예제 Java코드 (maven, gRPC, java, no spring framework)

 

gRPC를 Java에서 구현해보고자 합니다.
참고로 tomcat과 같은 servlet container에서는 gRPC를 구동할 수 없습니다.
제가 참여하는 프로젝트에서는 spring을 사용하고 있어서 spring framework와 같이 구동하려고 찾아보았는데,
gRPC 서버를 쉽게 구현하려면 spring boot를 써야만 사용가능하더군요.
이번 예제에서는 다른 프레임워크를 쓰지 않은 Java 코드로만 설명해보겠습니다.
또한, 인터넷의 대부분 자료는 gradle로 되어 있어, maven으로 구현한 예제를 정리합니다.

0) 사전준비사항
Java개발이기에 JDK설치가 필요하며, Java 코드를 작성하기 위한 tool(eclipse)등을 설치해주세요.


그 후에, https://github.com/protocolbuffers/protobuf 에서 protobuf 컴파일러를 다운받아 압축을 풀어줍니다.

 

컴파일러 다운받는 방법은 github에서 아래화면과 같이 우측에 있는 releases의 항목을 눌러줍니다.

그 후, assets에 있는 첨부파일을 다운받으면 됩니다.


윈도에서 개발하실 분들은  -win64로 끝나는 zip파일을 다운받으시면 됩니다.


1) 앞선 준비사항이 다 되었다면,

sample.proto 파일을 만들어, 아래와 같이 작성해줍니다.


syntax = "proto3";


option java_multiple_files = true;
package com.test.grpc;

message HelloRequest {
    string firstName = 1;
    string lastName = 2;
}

message HelloResponse {
    string greeting = 1;
}

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


service HelloServiceStream {
    rpc valuesStream(HelloRequest) returns (stream HelloResponse);
}





2) 1에서 작성한 sample.proto 파일을 0에서 압축푼 protoc 가 있는 폴더에 넣어주고,
dist폴더를 만들어줍니다.

전체구조를 보면 다음과 같습니다.



3) cmd창에서 아래 명령어를 사용하여, proto 파일을 사용하여 java class 파일을 만들어줍니다.
protoc -I=$SRC_DIR --java_out=$DST_DIR $SRC_DIR/sample.proto
예를 들어, 저는 아래와 같은 명령어를 사용하였습니다.


protoc -I=. --java_out=dist sample.proto

 




4) 그러면 dist 폴더에 아래와 같이 java 파일들이 만들어지게 됩니다.

 

 

5) 그런데 proto 파일에는 service도 명시해주었는데, service관련된 클래스는 생성되지 않아 있습니다.

이를 위해, grpc java 플러그인을 다운받아줍니다.

https://repo1.maven.org/maven2/io/grpc/protoc-gen-grpc-java/1.55.1/protoc-gen-grpc-java-1.55.1-windows-x86_64.exe 를 다운 받아, protoc 폴더에 넣어줍니다. (윈도 기준)

 버전이 바뀌었을경우, https://mvnrepository.com/artifact/io.grpc/protoc-gen-grpc-java 에서 다운받으시면 됩니다.

 

 


6) cmd창에서 아래 명령어를 사용하여, proto 파일을 사용하여 java class 파일을 만들어줍니다.
예를 들어, 저는 아래와 같은 명령어를 사용하였습니다.


protoc -I=. --grpc-java_out=dist --plugin=protoc-gen-grpc-java.exe sample.proto

 

그러면 아래와 같이 서비스에 대한 클래스 파일들도 만들어지게 됩니다.




7) 다음으로, gRPC를 사용한 서버를 만들어 보겠습니다.

java프로젝트를 만들고, 앞서 만든 class파일들을 복사해줍니다.

7) 그런데, IDE상에서 error가 발생하는데요, 

https://mvnrepository.com/artifact/com.google.protobuf/protobuf-java/3.23.1 에서 라이브러리를 다운받아 import해줍니다.

 

구체적으로는 링크에서 jar파일을 다운받고,

project를 우측클릭후, build path에서 add external archives를 선택해줍니다.

그 후, 아까 다운받은 jar파일을 선택해줍니다.

 

마찬가지 방식으로 아래 라이브러리들을 다운 및  import해줍니다.

https://mvnrepository.com/artifact/io.grpc/grpc-context/1.55.1

https://mvnrepository.com/artifact/io.grpc/grpc-api/1.55.1

https://mvnrepository.com/artifact/io.grpc/grpc-stub/1.55.1

https://mvnrepository.com/artifact/io.grpc/grpc-protobuf/1.55.1

https://mvnrepository.com/artifact/com.google.guava/guava

https://mvnrepository.com/artifact/io.grpc/grpc-core

https://mvnrepository.com/artifact/io.grpc/grpc-protobuf-lite/1.55.1

https://mvnrepository.com/artifact/com.google.guava/failureaccess/1.0.1

https://mvnrepository.com/artifact/io.perfmark/perfmark-api/0.26.0

https://mvnrepository.com/artifact/io.grpc/grpc-netty-shaded/1.55.1


6) 이제  서버측 java 프로젝트를 만들어보겠습니다.
아래와 같이 작성해줍니다.
grpc 서버를 9091포트로 오픈하고,
HelloServiceImpl과 HelloServiceStreamImpl 서비스를 추가한다는 내용입니다.



package com.test.grpc;

import java.io.IOException;

import io.grpc.Server;
import io.grpc.ServerBuilder;

public class GrpcServer{
public static void main(String[] args) throws InterruptedException, IOException {
        Server server = ServerBuilder
                .forPort(9091)
                .addService(new HelloServiceImpl())
                .addService(new HelloServiceStreamImpl())
                .build();
        server.start();
        server.awaitTermination();
    }
}




7) 위와 같이 코드를 작성하면 HelloServiceImpl과 HelloServiceStreamImpl 또 class를 따로 만들어서 구현해줘야 하는데요, 아래와 같이 샘플코드를 작성해보겠습니다.


package com.test.grpc;

import com.test.grpc.HelloResponse.Builder;
import com.test.grpc.HelloServiceGrpc.HelloServiceImplBase;

import io.grpc.stub.StreamObserver;

public class HelloServiceImpl extends HelloServiceImplBase {
@Override
public void hello(HelloRequest request, StreamObserver<HelloResponse> responseObserver) {
  Builder builder = HelloResponse.newBuilder();
  String fullName = request.getFirstName()+" "+request.getLastName();
  builder.setGreeting("Hello! "+fullName);
  responseObserver.onNext(builder.build());
  responseObserver.onCompleted();
}






package com.test.grpc;

import com.test.grpc.HelloResponse.Builder;
import com.test.grpc.HelloServiceStreamGrpc.HelloServiceStreamImplBase;

import io.grpc.stub.StreamObserver;

public class HelloServiceStreamImpl extends HelloServiceStreamImplBase {
@Override
public void valuesStream(HelloRequest request, StreamObserver<HelloResponse> responseObserver) {
Builder builder = HelloResponse.newBuilder();
String fullName = request.getFirstName()+" "+request.getLastName();
builder.setGreeting("Hello! "+fullName);
responseObserver.onNext(builder.build());
    responseObserver.onCompleted();
}
}




8) 다음으로 클라이언트 코드를 작성해보겠습니다.
서버 코드 작성과 마찬가지로, proto 파일을 빌드해서 나온 class파일들을 포함해주고,
아래와 같이 코드를 작성합니다.

package com.test.grpc;

import java.util.Iterator;

import com.test.grpc.HelloServiceGrpc.HelloServiceBlockingStub;
import com.test.grpc.HelloServiceStreamGrpc.HelloServiceStreamBlockingStub;
import com.test.grpc.HelloServiceStreamGrpc.HelloServiceStreamStub;

import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;

public class GrpcClient{

  public static void main(String[] args) {
   ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost", 9091)
     .maxInboundMessageSize(Integer.MAX_VALUE)
           .usePlaintext()
           .build();
  
   //blocking 방식
   HelloServiceBlockingStub stub = HelloServiceGrpc.newBlockingStub(channel);

  
   HelloRequest request = HelloRequest
   .newBuilder()
   .setFirstName("first")
   .setLastName("last")
   .build();
   
   HelloResponse result = stub.hello(request);
      
   System.out.println(result);
  
   
   
   //stream 방식
   HelloServiceStreamBlockingStub stub2 = HelloServiceStreamGrpc.newBlockingStub(channel);
   
   Iterator<HelloResponse> result2 = stub2.valuesStream(request);
  
  
   String tmp = "";
   while(result2.hasNext()) {
   tmp = result2.next().getGreeting();
   System.out.println(tmp);
   }
   channel.shutdown();
  }
}


전체 프로젝트 구조를 보면 아래와 같습니다.


서버를 실행시킨 후, 클라이언트를 실행시키면 아래와 같은 결과가 나오게 됩니다.






4. gRPC vs REST 비교, gRPC vs REST 장점, 단점

HTTP를 사용할때, 통신방식으로 많이 쓰이는 REST API와 비교하여 장단점을 비교해보겠습니다.
하나의 표로 표현하면 아래와 같이 요약할 수 있습니다.

 


@startuml
title
  |= 특징 |= gRPC |= REST API |
  | HTTP 프로토콜 | HTTP 2 | HTTP 1.1 |
  | 메시지 포맷 | protobuf | JSON / XML 등 |
  | 통신방식 | client 요청/양방향/스트리밍 | client 요청 |
  | 개발난이도 | 높음 | 낮음 |
end title
@enduml






총평 & gRPC 단점

 

개인적으로 gRPC를 알아보면서 실망한 부분이 많았는데요,
실제로 개발을 해보았을때 아래와 같은 부분이 아쉬웠습니다.
1) 개발공수 증가 : proto 파일 관리 및 빌드필요
--> 빌드시 실패시 원인확인에 대해 많은 시간이 소요됩니다.
proto파일을 이용하여 java 코드가 빌드가 안되는 경우가 있었고,
익숙하지 않아서인지 확인이 어려웠습니다.
또한, 서버와 클라이언트 양측에 공통된 proto로 생성된 클래스가 없다면 양측 통신이 아예 안되기도 하였습니다.

2) 개발공수 증가 : 같은 동작에 대해 REST에 비해 코드수 많으며, 개발속도가 느림
--> proto파일을 만드는것부터 시작해서, import를 하고, 빌드하는데도 시간이 걸리는 등
REST로 동일한 동작을 구현할 때 보다 어려움이 있었습니다.

3) 실직적인 속도 느림
--> 이론적으로는 속도가 빠르지만, 실제로 테스트를 해보니 속도가 REST API 보다 느렸습니다.
이론처럼 빨리 동작하려면 코드를 튜닝할 필요성이 있는데, gRPC에 대한 이해에 대한 노하우를 잘 배워야 하므로 개발공수가 많이 늘어날 수 있다고 봅니다.

4) 레퍼런스 부족
--> gRPC는 2016년에 개발이 되어 세상에 알려져서 어느정도 레퍼런스가 있긴 하지만,
REST API 에 비해서는 아직은 적은것으로 보입니다.

그리고 현재 제가 다니고 있는 회사에서도 gRPC를 쓰는 경우가 거의 없어,
문제가 발생하거나, 더 잘 사용하기 위한 방법을 찾을 때 어려움이 있는거 같습니다.

5) proto 에 대한 이해 필요
--> 예를 들어 protobuf만의 type들을 숙지하고 있어야 합니다. int32, int64 는 java의 int, long 과 비교되는 등
또 새로운 언어를 배운다는 느낌이 있습니다.
또한, repeated라던지 필드명에 = 1, 2, 3 등을 쓴다던지 하는 익숙하지 않은 문법들이 있었습니다.
가장 인상깊은 부분은 빈 파라미터의 함수를 만들 수 가 없는 점이 였습니다.
예를 들어, rpc로 test()란 함수를 사용하고 싶다고 하면,
proto파일에서 rpc test(google.protobuf.Empty) 와 같이 선언해주어야 합니다.


6) 비지니스로직과 무관한 코드들
--> 비즈니스로직과 무관한 코드들을 작성하는 일이 많아
개발공수가 늘어날 것으로 보였습니다.
예를들어, request객체를 만들때 xxx.newBuilder().setNum(aa).build() 와 같은 형식으로,
POJO를 쓰지 않고 빌더클리스를 사용해야 하는 불편한점이 있었습니다.
rpc method를 만들대에는 StreamObserver responseObserver 와 같은 파라미터를 무조건 넣어야 하는등 여러 불편한 점이 있었습니다.

 

 

 

빠르다고는 하나, 실제로는 빠르지 않는거 같고, 개발공수도 생각보다 많이 들어 효용이 큰지 잘 모르겠습니다.
튜닝을 하면 좀 빨라질 수도 있겠으나, 튜닝하는 방법에 대한 노하우, 러닝커브등이 문제가 될 거 같아서
일반적인 서비스를 개발할 때에는 잘 쓰지 않는게 좋을거 같다는 생각이 듭니다.
속도가 너무나도 느린 서비스가 있다면 개선을 위해 다시한번 더 깊숙히 파고들어봐야겠습니다.

#gRPC, #protobuf, #예제, #코드, #proto,#grpc단점


https://devscb.com/post/173

 

What is gRPC, what is protobuf, Java gRPC example code, grpc shortcomings, grpc java code example

What is gRPC, what is protobuf, Java gRPC example code, grpc disadvantages, grpc java code example While working on a recent micro service architecture project,While worrying about improving speed, I

devscb.com

 

728x90
반응형

댓글