Thrift是一種支持多語言的軟件框架,在各個服務之間的RPC通信領域應用非常廣泛。RPC(遠程過程調用)是一個計算機通信協議,該協議允許運行於一臺計算機的程序調用另一臺計算機的子程序,而程序員無需額外地爲這個交互作用編程。(參考遠程過程調用)。
Thrift通過一箇中間語言(IDL, 接口定義語言)來定義RPC的接口和數據類型,然後通過一個編譯器生成不同語言的代碼(目前支持C++,Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, Smalltalk和OCaml),並由生成的代碼負責RPC協議層和傳輸層的實現。
一、核心組件和特點
Thrift的核心組件有:
- TProtocol 協議和編解碼組件
- TTransport 傳輸組件
- TProcessor 服務調用組件
- TServer,Client 服務器和客戶端組件
- IDL 服務描述組件,負責生產跨平臺客戶端
作爲一個高性能的RPC框架,Thrift的主要特點有:
- 基於二進制的高性能的編解碼框架
- 基於NIO的底層通信
- 相對簡單的服務調用模型
- 使用IDL支持跨平臺調用
二、Thrift Type
Thrift Type包含了基本類型,自定義的結構體,容器,異常等,以下內容主要來自https://matt33.com/2016/04/07/thrift-learn/
2.1 基本類型(Base Types)
- bool: 布爾變量(A boolean value, one byte);
- byte: 8位有符號整數(A signed byte);
- i16: 16位有符號整數(A 16-bit signed integer);
- i32: 32位有符號整數(A 32-bit signed integer);
- i64: 64位有符號整數(A 64-bit signed integer);
- double: 64位浮點數(A 64-bit floating point number);
- binary: byte數組(A byte array);
- string: 字符串(Encoding agnostic text or binary string);
2.2 容器類型(Containers)
- list: 一系列由T類型的數據組成的有序列表,元素可以重複;
- set: 一系列由T類型的數據組成的無序集合,元素不可重複
- map: 一個字典結構,key爲T1類型,value爲T2類型;
2.3 結構體(Struct)
結構體中包含一系列的強類型域,等同於無繼承的class。可以看出struct寫法很類似C語言的結構體。
struct Example {
1:i32 number=10,
2:i64 bigNumber,
3:list<double> decimals,
4:string name="thrifty"
}
2.4 可選與必選
Thrift提供兩個關鍵字required,optional,分別用於表示對應的字段時必填的還是可選的。例如:
struct People {
1: required string name;
2: optional i32 age;
}
表示name是必填的,age是可選的。
2.5 聯合(Union)
在一個結構體中,如果field之間的關係是互斥的,即只能有一個field被使用被賦值。在這種情況下,我們可以使用union來聲明這個結構體,而不是一堆堆optional的field,語意上也更明確了。例如:
union JavaObjectArg {
1: i32 int_arg;
2: i64 long_arg;
3: string string_arg;
4: bool bool_arg;
5: binary binary_arg;
6: double double_arg;
}
三、RPC 調用分以下兩種:
- 同步調用:客戶方等待調用執行完成並返回結果。
- 異步調用:客戶方調用後不用等待執行結果返回,但依然可以通過回調通知等方式獲取返回結果。若客戶方不關心調用返回結果,則變成單向異步調用,單向調用不用返回結果。
四、Thrift模型
如上圖架構中各個層級,我們看其中的兩個
4.1 協議層(傳輸格式)
- TBinaryProtocol: 二進制格式;
- TCompactProtocol:高效率的、密集的二進制編碼格式進行數據傳輸;
- TJSONProtocol:JSON格式;
- TSimpleJSONProtocol:提供JSON只寫協議, 生成的文件很容易通過腳本語言解析;
- TDebugProtocol:使用易懂的可讀的文本格式,以便於debug。
4.2 傳輸層(數據傳輸方式)
- TSocket:阻塞式socker;
- TFramedTransport:使用非阻塞方式,以frame爲單位進行傳輸。
- TFileTransport:以文件形式進行傳輸。
- TMemoryTransport:將內存用於I/O. java實現時內部實際使用了簡單的ByteArrayOutputStream。
- TZlibTransport:使用zlib進行壓縮, 與其他傳輸方式聯合使用。當前無java實現。
- TNonblockingTransport —— 使用非阻塞方式,用於構建異步客戶端
4.3 服務模型
- TSimpleServer:單線程服務器端使用標準的阻塞式 I/O,簡單的單線程服務模型,常用於測試;
- TThreadPoolServer:多線程服務模型,使用標準的阻塞式IO;
- TNonblockingServer:多線程服務模型,使用非阻塞式IO(需使用TFramedTransport數據傳輸方式)。
4.4 Thrift RPC過程:
Client端通過Client Stub將接口、方法、參數按照協議規定的進行編碼,並且通過網絡傳輸到Server端。Server端的收到請求後交給Server Stub進行解碼併發起後端調用,最終將執行結果返回Client
更詳細的過程:
(1). client代碼像普通函數調用一樣調用client stub函數,這個調用是本地調用,調用參數會和平常函數調用一樣進行返回地址和調用參數壓棧操作;
(2). client stub會把調用參數和其它信息(比如調用方法名、調用屬性等,可以稱爲metadata)進行打包封裝成message,然後通過系統調用發送該message,這個打包的過程是個序列化的過程;
(3). client尋求到服務端的地址,然後通過本地操作系統經由某種協議,將上述message發送給server端;
(4). server端的操作系統接收message後將其傳遞給server stub;
(5). server stub將message解包,得到原始傳遞過來的各項調用參數,解包的過程是反序列化的過程;
(6). server stub調用server端的本地函數,然後將得到的結果按照上述類似的步驟反向傳遞給client作爲結果返回。當然整個過程也可能不那麼順利,那麼也應該產生合適的狀態碼、異常信息作爲返回。
所以RPC實際是通過client stub和server stub起到一個代理的作用,將client的請求轉發到server端去操作,並將操作得到的結果回傳,因爲傳遞是跨進程、跨主機的,所以必須進行序列化和反序列化的過程來保證消息的正確性。關於stub的概念:https://www.jianshu.com/p/9ccdea882688
4.5 影響RPC性能點有哪些
- 傳輸協議:HTTP、TCP、UDP
- 序列化方案:Java自帶的序列化、其他序列化框架Protobuf、Kryo、Hession
- 工作模式:同步or異步
五、安裝使用
關於安裝過程可以參考 https://juejin.im/post/5b290dbf6fb9a00e5c5f7aaa和https://matt33.com/2016/04/07/thrift-learn/這兩篇文章
編寫一個thrift文件,定義服務端的接口定義個服務接口
namespace java com.momo.learn.learn.thrift
struct Param{
1: required i32 id;
2: required string name;
3: optional i32 age;
}
service HelloThriftService {
i32 hello(1:string param1, 2:Param param2, 3:map<string,string> param3);
}
執行下邊命令
thrift —gen java HelloThriftService.thrift
會看到下邊這個圖,相當於定義了接口的簽名,服務端和客戶端分別實現和引用接口就可以完成使用
參考文章:
http://thrift.apache.org/
https://juejin.im/post/5b290dbf6fb9a00e5c5f7aaa
https://matt33.com/2016/04/07/thrift-learn/
https://blog.csdn.net/mindfloating/article/details/39474123
重點推薦:https://www.kancloud.cn/digest/thrift/118986