memostack
article thumbnail
블로그를 이전하였습니다. 2023년 11월부터 https://bluemiv.tistory.com/에서 블로그를 운영하려고 합니다. 앞으로 해당 블로그의 댓글은 읽지 못할 수 도 있으니 양해바랍니다.
반응형

Thrift

쓰리프트는 다양한 언어를 지원하는 RPC 프레임워크이다. 페이스북에서 개발했고, 현재는 오픈소스 아파치 프로젝트로 등록되어 있다. 쓰리프트는 RPC 프레임워크라서, 원격 메소드를 호출하여 개발자는 비지니스 로직에만 신경 쓸 수 있다는 RPC 특징을 똑같이 가진다.

 

정의된 인터페이스(또는 스키마)를 가지고, 다양한 언어로 코드를 생성해주는 엔진을 가지고 있고 아래와 같은 언어를 지원해준다.

  • C++, Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, JavaScript, Node.js, Smalltalk, OCaml, Delphi

 

예시로 아래와 같은 쓰리프트 파일을 각 언어에 맞게 코드를 생성해준다.

enum PhoneType {
  HOME,
  WORK,
  MOBILE,
  OTHER
}

struct Phone {
  1: i32 id,
  2: string number,
  3: PhoneType type
}

service PhoneSvc {
  Phone findById(1: i32 id),
  list<Phone> findAll()
}

 

Thrift Type

쓰리프트는 아래와 같은 타입을 가진다. 그리고 integer의 종류는 16bit, 32bit, 64bit 3가지로 나눠진다.

  • bool: A boolean value (true or false): 불리안 타입
  • byte: An 8-bit signed integer: 8bit 부호있는 정수 타입
  • i16: A 16-bit signed integer: 16bit 부호있는 정수 타입
  • i32: A 32-bit signed integer: 32bit 부호있는 정수 타입
  • i64: A 64-bit signed integer: 64bit 부호있는 정수 타입
  • double: A 64-bit floating point number: 64bit의 실수 타입
  • string: A text string encoded using UTF-8 encoding: UTF-8로 인코딩된 문자열 타입

 

Thrift 사용해보기

서버는 nodejs로 클라이언트는 python으로 하여 thrift를 사용해보려고 한다.

 

thrift 빌드

쓰리프트 빌드를 위한 환경을 구축해도 되지만, docker 이미지로 빌드 환경을 제공하고 있다. 따라서, docker를 사용하면 훨씬 편리하다. 본 글에서는 docker를 사용하여 thrift를 빌드한다.

 

thrift 빌드를 위해서 아래 명령어를 수행하면 된다.

# docker run -v ${thrift 파일이 위치한 곳}:/data thrift thrift -o /data --gen ${빌드할 언어} /data/${thrift 파일}
$ docker run -v $PWD/infra:/data thrift thrift -o /data --gen js:node /data/tutorial.thrift

 

하지만, yarn으로 편하게 빌드하기 위해 본 글에선 아래와 같이 한다. 우선 아래와 같이 디렉토리를 생성한다.

thrift
|____infra
| |____tutorial.thrift
| |____shared.thrift
|____package.json

 

package.json에는 아래와 같이 작성한다.

{
  "name": "thrift_builder",
  "version": "0.0.1",
  "scripts": {
    "nodebuild": "ls infra/*.thrift | awk -F/ '{ cmd=\"docker run -v $PWD/infra:/data thrift thrift -o /data --gen js:node /data/\"$2;system(cmd) }'",
    "pythonbuild": "ls infra/*.thrift | awk -F/ '{ cmd=\"docker run -v $PWD/infra:/data thrift thrift -o /data --gen py /data/\"$2;system(cmd) }'"
  }
}

 

그리고, yarn으로 빌드를 수행해보자

$ yarn nodebuild
$ yarn pythonbuild

 

빌드가 끝나면 아래와 같이 gen-py와 gen-nodejs 가 생성된다.

thrift
|____infra
| |____gen-py
| | |____tutorial
| | | |____constants.py
| | | |______init__.py
| | | |____ttypes.py
| | | |____Calculator.py
| | | |____Calculator-remote
| | |______init__.py
| | |____shared
| | | |____constants.py
| | | |______init__.py
| | | |____ttypes.py
| | | |____SharedService-remote
| | | |____SharedService.py
| |____tutorial.thrift
| |____shared.thrift
| |____gen-nodejs
| | |____shared_types.js
| | |____SharedService.js
| | |____tutorial_types.js
| | |____Calculator.js
|____package.json

 

nodejs로 thrift 서버 생성

thrift 모듈을 다운로드 받는다.

$ yarn add thrift

 

추가로, package.json을 작성한다.

{
  "name": "thrift_test_app",
  "version": "0.0.1",
  "scripts": {
    "start": "node index.js"
  },
  "dependencies": {
    "thrift": "^0.14.2"
  }
}

 

index.js 를 작성한다.

var thrift = require("thrift");
var Calculator = require("./infra/gen-nodejs/Calculator");
var ttypes = require("./infra/gen-nodejs/tutorial_types");
var SharedStruct = require("./infra/gen-nodejs/shared_types").SharedStruct;

var data = {};

var server = thrift.createServer(Calculator, {
  ping: function (result) {
    console.log("ping()");
    result(null);
  },

  add: function (n1, n2, result) {
    console.log("add(", n1, ",", n2, ")");
    result(null, n1 + n2);
  },

  calculate: function (logid, work, result) {
    console.log("calculate(", logid, ",", work, ")");

    var val = 0;
    if (work.op == ttypes.Operation.ADD) {
      val = work.num1 + work.num2;
    } else if (work.op === ttypes.Operation.SUBTRACT) {
      val = work.num1 - work.num2;
    } else if (work.op === ttypes.Operation.MULTIPLY) {
      val = work.num1 * work.num2;
    } else if (work.op === ttypes.Operation.DIVIDE) {
      if (work.num2 === 0) {
        var x = new ttypes.InvalidOperation();
        x.whatOp = work.op;
        x.why = "Cannot divide by 0";
        result(x);
        return;
      }
      val = work.num1 / work.num2;
    } else {
      var x = new ttypes.InvalidOperation();
      x.whatOp = work.op;
      x.why = "Invalid operation";
      result(x);
      return;
    }

    var entry = new SharedStruct();
    entry.key = logid;
    entry.value = "" + val;
    data[logid] = entry;

    result(null, val);
  },

  getStruct: function (key, result) {
    console.log("getStruct(", key, ")");
    result(null, data[key]);
  },

  zip: function () {
    console.log("zip()");
  },
});

console.log("Start thrift server... port: 9090");
server.listen(9090);

 

그리고, 위에서 thrift 빌드한 gen-nodejs 파일을 가지고 온다. 위 파일들을 다 만들고 나면, 디렉토리 구조는 아래와 같다.

server
|____infra
| |____gen-nodejs
| | |____shared_types.js
| | |____SharedService.js
| | |____tutorial_types.js
| | |____Calculator.js
|____node_modules
      ...
|____index.js
|____yarn.lock
|____package.json

 

python으로 thrift client 생성

client.py 를 생성한다.

import sys
import os
sys.path.append(os.path.join(os.path.dirname(__file__), "gen-py"))

from tutorial import Calculator
from tutorial.ttypes import InvalidOperation, Operation, Work

from thrift import Thrift
from thrift.transport import TSocket
from thrift.transport import TTransport
from thrift.protocol import TBinaryProtocol


def main():
    # Make socket
    transport = TSocket.TSocket('localhost', 9090)

    # Buffering is critical. Raw sockets are very slow
    transport = TTransport.TBufferedTransport(transport)

    # Wrap in a protocol
    protocol = TBinaryProtocol.TBinaryProtocol(transport)

    # Create a client to use the protocol encoder
    client = Calculator.Client(protocol)

    # Connect!
    transport.open()

    client.ping()
    print('ping()')

    sum_ = client.add(1, 1)
    print('1+1=%d' % sum_)

    work = Work()

    work.op = Operation.DIVIDE
    work.num1 = 1
    work.num2 = 0

    try:
        quotient = client.calculate(1, work)
        print('Whoa? You know how to divide by zero?')
        print('FYI the answer is %d' % quotient)
    except InvalidOperation as e:
        print('InvalidOperation: %r' % e)

    work.op = Operation.SUBTRACT
    work.num1 = 15
    work.num2 = 10

    diff = client.calculate(1, work)
    print('15-10=%d' % diff)

    log = client.getStruct(1)
    print('Check log: %s' % log.value)

    # Close!
    transport.close()


if __name__ == '__main__':
    try:
        main()
    except Thrift.TException as tx:
        print('%s' % tx.message)

 

그리고, 똑같이 thrift를 python으로 빌드한 gen-py를 가지고 온다. 디렉토리 전체 구조는 아래와 같다.

client
|____client.py
|____gen-py
| |____tutorial
| | |____constants.py
| | |______init__.py
| | |______pycache__
| | | |____Calculator.cpython-37.pyc
| | | |____ttypes.cpython-37.pyc
| | | |______init__.cpython-37.pyc
| | |____ttypes.py
| | |____Calculator.py
| | |____Calculator-remote
| |______init__.py
| |____shared
| | |____constants.py
| | |______init__.py
| | |______pycache__
| | | |____SharedService.cpython-37.pyc
| | | |____ttypes.cpython-37.pyc
| | | |______init__.cpython-37.pyc
| | |____ttypes.py
| | |____SharedService-remote
| | |____SharedService.py

 

서버 & 클라이언트 실행

이제 서버를 실행해보자

$ cd server
$ yarn start

 

클라이언트도 실행해보자

$ cd client
$ python client.py

 

통신이 되는것을 확인 할 수 있음

thrift 예제

 

잡담

모든 서비스가 동일한 인터페이스로 통신을 할 수 있다는 장점은 있으나 자료가 상당히 부족하여 버그나 이슈가 발생했을때, 삽질을 많이하게 된다. 사내에서 사용하고 있기 때문에 쓰리프트를 조사하고있지만, REST를 사용하는것도 좋을거 같다는 생각을 많이 한다.

 

Reference

반응형
블로그를 이전하였습니다. 2023년 11월부터 https://bluemiv.tistory.com/에서 블로그를 운영하려고 합니다. 앞으로 해당 블로그의 댓글은 읽지 못할 수 도 있으니 양해바랍니다.
profile

memostack

@bluemiv_mm

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!