gRPC Stream

gRPC의 통신 방법과 stream 소개

Posted by Start Bootstrap on February 01, 2022

gRPC의 통신 방법

프로세스간 통신 방법인 RPC는 직접 구현하려고 하면 많은 노력과 전문 지식이 필요 합니다. 따라서 미리 구현된 형태인 gRPC를 활용해서 이용하는 형식이 많습니다. gRPC는 크게 네 가지 방법으로 통신을 구현합니다.

단방향 구현(Unary RPC)하나의 요청과 하나의 응답으로 처리하는 방식

서버 스트리밍(Server streaming RPC)클라이언트는 하나의 요쳥을 보내지만 서버는 스트림으로 응답을 보내는 방식

클라이언트 스트리밍(Client streaming RPC)클라이언트는 스트림으로 요청을 보내고 서버는 하나의 응답으로 처리하는 방식

Bidirectional streaming RPC:클라이언트와 서버 모두 스트림으로 처리

스트리밍과 그렇지 않은 것의 차이는 proto의 stream 키워드로 구분할 수 있습니다. 따라서 아래의 해당 예시 처럼 쉽게 처리할 수 있습니다.

proto stream 작성하기

stream 키워드는 요청과 응답에 사용해서 해당 방식을 설명할 수 있습니다. 물론 stream 키워드를 통해 결과를 보면 사용한 것과 아닌것의 결과가 다르다는 것을 확인할 수 있습니다. 일반적으로 아래와 같이 작성합니다.

        
            syntax = "proto3";

            service UserService {
            // Unary
            rpc GetUserUnary (UserRequest) returns (UserResponse);

            // Server streaming
            rpc GetUserServerStream (UserRequest) returns (stream UserResponse);

            // Client streaming
            rpc GetUserClientStream (stream UserRequest) returns (UserResponse);

            // Bi-directional streaming
            rpc GetUserStream (stream UserRequest) returns (stream UserResponse);
            }
        
    

실제로 해당 proto를 통해 만들어진 코드를 보면서 어떻게 다른지 한번 살펴보겠습니다. 먼저 kotlin 코드를 보면서 어떤 면이 다른지 한번 비교해 보겠습니다.

        
            // response를 stream으로 생성한 결과
            fun getUsersStream(request: User.GetUsersRequest, headers: Metadata = Metadata()):
                    Flow = serverStreamingRpc(
                channel,
                UserServiceGrpc.getGetUsersStreamMethod(),
                request,
                callOptions,
                headers
            )

            // 요청 응답 모드 단일 요청으로 생성한 결과
            suspend fun getUsers(request: User.GetUsersRequest, headers: Metadata = Metadata()):
                    User.GetUsersResponse = unaryRpc(
                channel,
                UserServiceGrpc.getGetUsersMethod(),
                request,
                callOptions,
                headers
            )
        
    

실제로 코드를 분석하면 response를 Flow로 구현한 차이가 있습니다. Mono와 Flux도 다른 글에서 다룰 기회가 있을것 같습니다만 지금은 event stream을 위한 자료 구조라고 생각하시면 될것 같습니다. 그리고 단일 응답에는 unaryRpc, stream 키워드가 붙은 응답에는 serverStreamingRpc가 있는 차이점이 있습니다.

        
            // 요청 응답 모드 단일 요청으로 생성한 결과
            func (c *loginServiceClient) LoginSimpleRPC(ctx context.Context, in *LoginRequest, opts ...grpc.CallOption) (*LoginResponse, error) {
                out := new(LoginResponse)
                err := c.cc.Invoke(ctx, "/rpc_auth.LoginService/LoginSimpleRPC", in, out, opts...)
                if err != nil {
                    return nil, err
                }
                return out, nil
            }
            
            // response를 stream으로 생성한 결과
            func (c *loginServiceClient) LoginServerStreamRPC(ctx context.Context, in *LoginRequestList, opts ...grpc.CallOption) (LoginService_LoginServerStreamRPCClient, error) {
                stream, err := c.cc.NewStream(ctx, &LoginService_ServiceDesc.Streams[0], "/rpc_auth.LoginService/LoginServerStreamRPC", opts...)
                if err != nil {
                    return nil, err
                }
                x := &loginServiceLoginServerStreamRPCClient{stream}
                if err := x.ClientStream.SendMsg(in); err != nil {
                    return nil, err
                }
                if err := x.ClientStream.CloseSend(); err != nil {
                    return nil, err
                }
                return x, nil
            }
        
    

go로 생성된 코드도 stream 처리와 unary 처리가 확실히 코드가 다름을 확인했습니다.

stream 코드 작성 재정의

작성중....