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 예제 파일은 github에 있는 코드를 사용
- https://github.com/apache/thrift/tree/master/tutorial 참고
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
통신이 되는것을 확인 할 수 있음
잡담
모든 서비스가 동일한 인터페이스로 통신을 할 수 있다는 장점은 있으나 자료가 상당히 부족하여 버그나 이슈가 발생했을때, 삽질을 많이하게 된다. 사내에서 사용하고 있기 때문에 쓰리프트를 조사하고있지만, REST를 사용하는것도 좋을거 같다는 생각을 많이 한다.
Reference
'ETC' 카테고리의 다른 글
아스키 코드에 대해서 (ASCII) (0) | 2021.03.04 |
---|---|
문자 인코딩 (charater encoding), 글자 깨지는 이유 (0) | 2021.03.04 |
Ubuntu 20.04에 Zabbix 5.2 구축하기 (0) | 2021.02.22 |
Ubuntu에 JFrog OSS Artifactory 설치 (0) | 2021.02.21 |
Virtual Box에 Ubuntu 20.04 VM 생성하기 (0) | 2021.02.18 |