본문 바로가기

대학생활

[Python] gRPC server run with database

 서버 간, 또는 유저 간에 통신할 때, 저는 보통 REST API 방식을 활용했고 그것밖에 몰랐습니다. 하지만 gRPC라는 툴을 통해 스트리밍에 특화된 서비스의 능률을 높일 수 있다는 소식을 듣게 됐습니다. 여기서는.. streaming 통신까지 다루진 않는데,, gRPC 서버 하나 세우고 거기서 연결된 DB를 처리하도록 해보았습니다..

 

※ 배포도 잘 돼요! 네트워크, IP 설정만 꼼꼼히 신경쓰면 외부에서도 통신되게 할 수 있습니다!(보안 이슈는 잘 모르겠어요,,)

0. 프로젝트 디렉토리 구조

 

 

1. 프로젝트 폴더 생성

 

mkdir project
cd project

 

2. 파이썬 가상환경 생성 (전 파이참으로 생성) 및 진입

*적절한 위치에 가상환경 파일을 놓으면 파이참이 알아서 찾아서 진입해줘욤

 

python -m venv venv
cd venv\Scripts
activate
cd ..\..\project

 

3. 필요 라이브러리 설치

 

pip install grpcio
pip install grpcio-tools
pip install psycopg2  <-------- 저는 PostgreSQL을 쓸 거라 얘 깔았습니다~

 

4. .proto 파일 작성 (gRPC docs 예시 따라도 되고 이거 따라서 해도 됩니다)

 

syntax = "proto3";

package users;

service Functions {
    // Send a user id and get his information
    rpc GetInfo (UserRequest) returns (UserReply) {}
  }
  
  message UserRequest {
    int32 id = 1;
  }

  message UserReply {
    string nickname = 1;
    string phone = 2;
  }

 

5. .proto 파일을 바탕으로 gRPC 코드 파일들 생성

 

python -m grpc_tools.protoc -I protos --python_out=. --pyi_out=. --grpc_python_out=. protos\<proto_filename>

 

6. gRPC server 파일 작성 <= 얘한테 gRPC 요청을 보내는 거에욤

 

from concurrent import futures
import logging

import grpc
import users_pb2
import users_pb2_grpc

import psycopg2

db = psycopg2.connect(
        host="~~~~",
        database="~~~~",
        user="~~~~",
        password="~~~~"
)


class UserService(users_pb2_grpc.FunctionsServicer):

    def GetInfo(self, request, context):
        cur = db.cursor()
        cur.execute("<YOUR SQL QUOTE>")
        output = cur.fetchone()
		return users_pb2.UserReply(nickname="~~~~", phone="~~~~")


def serve():
    port = '50051'
    server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
    users_pb2_grpc.add_FunctionsServicer_to_server(UserService(), server)
    server.add_insecure_port('[::]:' + port)
    server.start()
    print("Server started, listening on " + port)
    server.wait_for_termination()


if __name__ == '__main__':
    logging.basicConfig()
    serve()

 

 여기선 .proto에서 선언한 함수들(interface같은 친구들)을 구체화하는 파일입니다. 게다가 request라는 요청을 활용하여 client 측에서 보내는 message 파일(.proto의 UserRequest, MannerRequest)들을 파헤쳐 볼 수 있어요~ 

 serve라는 함수에서 서버를 오픈하는데 저도 서버가 열리는 것만 알고 나머진 잘 모르겠네요 ㅎㅎ;;😬

 

7. gRPC client 파일 작성

client 파일은 다른 언어로 작성해서 요청 넣어도 원활히 작동 돼요 Python, Ruby 2가지 언어로 써보겠습니다.

 

from __future__ import print_function

import logging

import grpc
import users_pb2
import users_pb2_grpc


def run():
    with grpc.insecure_channel('localhost:50051') as channel:
        stub = users_pb2_grpc.FunctionsStub(channel)
        response = stub.GetInfo(users_pb2.UserRequest(id="~~~~"))


if __name__ == '__main__':
    logging.basicConfig()
    run()

 

this_dir = File.expand_path(File.dirname(__FILE__))
lib_dir = File.join(this_dir, 'lib')
$LOAD_PATH.unshift(lib_dir) unless $LOAD_PATH.include?(lib_dir)

require 'grpc'
require 'users_services_pb'

def main
  hostname = ARGV.size > 1 ?  ARGV[0] : 'localhost:50051'
  stub = Users::Functions::Stub.new(hostname, :this_channel_is_insecure)
  begin
    response = stub.get_info(Users::UserRequest.new(id: "~~~~"))
  rescue GRPC::BadStatus => e
    abort "ERROR: #{e.message}"
  end
end

main

 

 

 그냥 적당~히 .proto에 명세된 대로 따라가 주면 됩니다. 헷갈리지 않게 request나 reply(response) message잘 네이밍해주면 돼요~😎 너무 헷갈리시면 gPRC docs~(https://grpc.io/docs/languages/)

 

8. gRPC 통신

 먼저 서버를 열고

 

python <server_file>

 

  각 클라이언트 파일을 돌려주면 됩니다.  포트번호, 호스트 주소 통일해줘야 오류가 안나요!!

 

python <client_file>
ruby <client_file>

 

참고 자료

gRPC docs

https://grpc.io/docs/

 

Documentation

A high-performance, open source universal RPC framework

grpc.io