前言
Apache Thrift 是 Facebook 實現的一種高效的、支持多種編程語言的遠程服務調用的框架。本文將從 Python開發人員角度簡單介紹 Apache Thrift 的架構、開發和使用。
Thrift簡介
Thrift network stack
Transport
Transport網絡讀寫(socket,http等)抽象,用於和其他thrift組件解耦。
Transport的接口包括:open, close, read, write, flush, isOpen, readAll。
Server端需要ServerTransport(對監聽socket的一種抽象),用於接收客戶端連接,接口包括:listen, accept, close。
python中Transport的實現包括:TSocket, THttpServer, TSSLSocket, TTwisted, TZlibTransport,都是對某種協議或框架的實現。還有兩個裝飾器,用於為已有的Transport添加功能,TBufferedTransport(增加緩沖)和TFramedTransport(添加幀)。
在創建server時,傳入的時Tranport的工廠,這些Factory包括:TTransportFactoryBase(沒有任何修飾,直接返回),TBufferedTransportFactory(返回帶緩沖的Transport)和TFramedTransportFactory(返回幀定位的Transport)。
Protocol
Protocol用於對數據格式抽象,在rpc調用時序列化請求和響應。
TProtocol的實現包括:TJSONProtocol,TSimpleJSONProtocol,TBinaryProtocol,TBinaryPotocolAccelerated,TCompactProtocol。
Processor
Processor對stream讀寫抽象,最終會調用用戶編寫的handler已響應對應的service。具體的Processor有compiler生成,用戶需要實現service的實現類。
Server
Server創建Transport,輸入、輸出的Protocol,以及響應service的handler,監聽到client的請求然后委托給processor處理。 TServer是基類,構造函數的參數包括: 1) processor, serverTransport 2) processor, serverTransport, transportFactory, protocolFactory 3) processor, serverTransport, inputTransportFactory, outputTransportFactory, inputProtocolFactory, outputProtocolFactory TServer內部實際上需要3)所列的參數,1)和2)會導致對應的參數使用默認值。 TServer的子類包括:TSimpleServer, TThreadedServer, TThreadPoolServer, TForkingServer, THttpServer, TNonblockingServer, TProcessPoolServer TServer的serve方法用於開始服務,接收client的請求。
Code generated
constants.py: 包含聲明的所有常量
ttypes.py: 聲明的struct,實現了具體的序列化和反序列化
SERVICE_NAME.py: 對應service的描述文件,包含了:
Iface: service接口定義
Client: client的rpc調用樁
用法
Thrift的用法實際上很簡單,定義好IDL,然后實現service對應的handler(方法名、參數列表與接口定義一致接口),最后就是選擇各個組件。
需要選擇的包括:Transport(一般都是socket,只是十分需要選擇buffed和framed裝飾器factory),Protocol,Server。
示例
簡單記錄下在mac下使用python thrift的過程
1. 安裝 Thrift 的 python 庫有兩種方案(1. pip安裝 2. 源碼安裝)具體參見文末鏈接
1)pip安裝: pip install thrift(最好在venv中使用)
2. 安裝 Thrift 的 IDL 編譯工具(windows/linux安裝見文末鏈接)
1)mac下安裝: brew install thrift
$ thrift -version,如果打印出來:Thrift version x.x.x 表明 complier 安裝成功
3. 建立項目目錄(thrift_demo)並開始編碼
1)目錄結構(example目錄及其下的文件不用手動創建,是通過命令自動生成的,具體細節請往下看)
<1> client目錄下的 client.py 實現了客戶端用於發送數據並打印接收到 server 端處理后的數據
<2> server 目錄下的 server.py 實現了服務端用於接收客戶端發送的數據,並對數據進行大寫處理后返回給客戶端
<3> thrift_file 用於存放 thrift 的 IDL 文件: *.thrift
2) 定義 Thrift RPC 接口IDL文件 example.thrift:
namespace py example struct Data { 1: string text 2: i32 id } service format_data { Data do_format(1:Data data), }
進入 thrift_file 目錄執行:$ thrift -out .. --gen py example.thrift,就會在 thrift_file 的同級目錄下生成 python 的包:example
3) 實現 server 端server.py:
#! /usr/bin/env python # -*- coding: utf-8 -*- import os import sys cur_path =os.path.abspath(os.path.join(os.path.dirname('__file__'), os.path.pardir)) sys.path.append(cur_path) from example import format_data from example import ttypes from thrift.transport import TSocket from thrift.transport import TTransport from thrift.protocol import TBinaryProtocol from thrift.server import TServer __HOST = 'localhost' __PORT = 9000 class FormatDataHandler(object): def do_format(self, data): print(data.text, data.id) # can do something return ttypes.Data(data.text.upper(), data.id) if __name__ == '__main__': handler = FormatDataHandler() processor = format_data.Processor(handler) transport = TSocket.TServerSocket(__HOST, __PORT) # 傳輸方式,使用buffer tfactory = TTransport.TBufferedTransportFactory() # 傳輸的數據類型:二進制 pfactory = TBinaryProtocol.TBinaryProtocolFactory() # 創建一個thrift 服務 rpcServer = TServer.TSimpleServer(processor,transport, tfactory, pfactory) print('Starting the rpc server at', __HOST,':', __PORT) rpcServer.serve() print('done')
4) 實現 client 端client.py:
#! /usr/bin/env python # -*- coding: utf-8 -*- import os import sys sys.path.append(os.path.abspath(os.path.join(os.path.dirname('__file__'), os.path.pardir))) from thrift.transport import TSocket from thrift.transport import TTransport from thrift.protocol import TBinaryProtocol from example.format_data import Client from example.format_data import Data __HOST = 'localhost' __PORT = 9000 try: tsocket = TSocket.TSocket(__HOST, __PORT) transport = TTransport.TBufferedTransport(tsocket) protocol = TBinaryProtocol.TBinaryProtocol(transport) client = Client(protocol) data = Data('hello,world!', 123) transport.open() print('client-requets') res = client.do_format(data) # print(client.do_format(data).text) print('server-answer', res) transport.close() except Thrift.TException as ex: print(ex.message)
4. 執行驗證結果
1) 先啟動 server(進入server目錄,執行python server.py),之后再另一個窗口執行 client(進入client目前,執行python client.py):
client 側控制台打印的結果為:
server側控制台打印的結果為:
證明 Thrift 的 RPC 接口定義成功
================================部分詳細介紹========================================
傳輸協議
在傳輸協議上總體划分為文本和二進制 ,為節約帶寬,提高傳輸效率,一般情況下使用二進制類型的傳輸協議為多數.
- TBinaryProtocol — 二進制編碼格式進行數據傳輸
- TCompactProtocol — 高效率的、密集的二進制編碼格式進行數據傳輸
- TJSONProtocol — 使用 JSON 的數據編碼協議進行數據傳輸
- TSimpleJSONProtocol — 只提供 JSON 只寫的協議,適用於通過腳本語言解析
- TDebugProtocol – 使用易懂的可讀的文本格式,以便於 debug
數據傳輸
- TSocket — 使用阻塞式 I/O 進行傳輸,是最常見的模式
- TFramedTransport — 使用非阻塞方式,按塊的大小進行傳輸
- TNonblockingTransport — 使用非阻塞方式,用於構建異步客戶端
- TMemoryTransport – 將內存用於 I/O
- TZlibTransport – 使用 zlib 進行壓縮, 與其他傳輸方式聯合使用
- TFileTransport – 以文件形式進行傳輸
服務端類型
- TSimpleServer — 單線程服務器端使用標准的阻塞式 I/O
- TThreadPoolServer —— 多線程服務器端使用標准的阻塞式 I/O
- TNonblockingServer —— 多線程服務器端使用非阻塞式 I/O
數據類型
Thrift 腳本可定義的數據類型包括以下幾種類型:
- 基本類型:
- bool:布爾值,true 或 false
- byte:8 位有符號整數
- i16:16 位有符號整數
- i32:32 位有符號整數
- i64:64 位有符號整數
- double:64 位浮點數
- string:未知編碼文本或二進制字符串
- 結構體類型:
- struct:定義公共的對象,類似於 C 語言中的結構體定義
- 容器類型:
- list:一系列 t1 類型的元素組成的有序表,元素可以重復
- set:一系列 t1 類型的元素組成的無序表,元素唯一
- map<t1,t2>:key/value 對(key 的類型是 t1 且 key 唯一,value 類型是 t2)
- 異常類型:
- exception 異常在語法和功能上類似於結構體,它在語義上不同於結構體—當定義一個 RPC 服務時,開發者可能需要聲明一個遠程方法拋出一個異常。
- 服務類型:
- service:對應服務的類
參考: