Apache Thrift - 可伸縮的跨語言服務(wù)開發(fā)框架
RPC技術(shù)及實(shí)現(xiàn)簡介
首先思考一下分布式系統(tǒng)中的 RPC (Remote Procedure Call) 問題,一個(gè)完整的 RPC 模塊需要可以分為三個(gè)層次
- 服務(wù)層(service):RPC 接口定義與實(shí)現(xiàn)
- 協(xié)議層(protocol):RPC 報(bào)文格式和數(shù)據(jù)編碼格式
- 傳輸層(transport):實(shí)現(xiàn)底層的通信(如 socket)以及系統(tǒng)相關(guān)的功能(如事件循環(huán)、多線程)
在實(shí)際的大型分布式系統(tǒng)中,不同的服務(wù)往往會(huì)使用不同的語言來實(shí)現(xiàn),所以一般的 RPC 系統(tǒng)會(huì)提供一種跨語言的過程調(diào)用功能,比如一段用C++實(shí)現(xiàn)的客戶端代碼可以遠(yuǎn)程調(diào)用一個(gè)用 Java 實(shí)現(xiàn)的服務(wù)。實(shí)現(xiàn)跨語言 RPC 有兩種方法: - 靜態(tài)代碼生成:開發(fā)者用一種中間語言(IDL,接口定義語言)來定義 RPC 的接口和數(shù)據(jù)類型,然后通過一個(gè)編譯器來生成不同語言的代碼(如C++, Java, Python),并由生成的代碼來負(fù)責(zé) RPC 協(xié)議層和傳輸層的實(shí)現(xiàn)。例如,服務(wù)的實(shí)現(xiàn)用C++,則服務(wù)端需要生成實(shí)現(xiàn)RPC協(xié)議和傳輸層的C++代碼,服務(wù)層使用生成的代碼來實(shí)現(xiàn)與客戶端的通信;而如果客戶端用 Python,則客戶端需要生成Python代碼。
- 基于“自省”的動(dòng)態(tài)類型系統(tǒng)來實(shí)現(xiàn):協(xié)議和傳輸層可以只用一種語言實(shí)現(xiàn)成一個(gè)庫,但是這種語言需要關(guān)聯(lián)一個(gè)具備“自省”或者反射機(jī)制的動(dòng)態(tài)類型系統(tǒng),對外提供其他語言的綁定,客戶端和服務(wù)端通過語言綁定來使用 RPC。比如,可以考慮用 C 和 GObject 實(shí)現(xiàn)一個(gè) RPC 庫,然后通過 GObject 實(shí)現(xiàn)其他語言的綁定。
第一種方法的優(yōu)點(diǎn)是RPC的協(xié)議層和傳輸層的實(shí)現(xiàn)不需要和某種動(dòng)態(tài)類型系統(tǒng)(如GObject)綁定在一起,同時(shí)避免了動(dòng)態(tài)類型檢查和轉(zhuǎn)換,程序效率比較高,但是它的缺點(diǎn)是要為不同語言提供不同的 RPC 協(xié)議層和傳輸層實(shí)現(xiàn)。第二種方法的主要難度在于語言綁定和通用的對象串行化機(jī)制的實(shí)現(xiàn),同時(shí)也需要考慮效率的問題。
Thrift 是一個(gè)基于靜態(tài)代碼生成的跨語言的RPC協(xié)議棧實(shí)現(xiàn),它可以生成包括C++, Java, Python, Ruby, PHP 等主流語言的代碼,這些代碼實(shí)現(xiàn)了 RPC 的協(xié)議層和傳輸層功能,從而讓用戶可以集中精力于服務(wù)的調(diào)用和實(shí)現(xiàn)。Cassandra 的服務(wù)訪問協(xié)議是基于 Thrift 來實(shí)現(xiàn)的。
Thrift介紹
Thrift源于大名鼎鼎的facebook之手,在2007年facebook提交Apache基金會(huì)將Thrift作為一個(gè)開源項(xiàng)目,對于當(dāng)時(shí)的facebook來說創(chuàng)造thrift是為了解決facebook系統(tǒng)中各系統(tǒng)間大數(shù)據(jù)量的傳輸通信以及系統(tǒng)之間語言環(huán)境不同需要跨平臺(tái)的特性。所以thrift可以支持多種程序語言,例如: C++, C#, Cocoa, Erlang, Haskell, Java, Ocami, Perl, PHP, Python, Ruby, Smalltalk. 在多種不同的語言之間通信thrift可以作為二進(jìn)制的高性能的通訊中間件,支持?jǐn)?shù)據(jù)(對象)序列化和多種類型的RPC服務(wù)。Thrift適用于程序?qū)Τ?序靜態(tài)的數(shù)據(jù)交換,需要先確定好他的數(shù)據(jù)結(jié)構(gòu),他是完全靜態(tài)化的,當(dāng)數(shù)據(jù)結(jié)構(gòu)發(fā)生變化時(shí),必須重新編輯IDL文件,代碼生成,再編譯載入的流程,跟其他IDL工具相比較可以視為是Thrift的弱項(xiàng),Thrift適用于搭建大型數(shù)據(jù)交換及存儲(chǔ)的通用工具,對于大型系統(tǒng)中的內(nèi)部數(shù)據(jù)傳輸相對于JSON和xml無論在性能、傳輸大小上有明顯的優(yōu)勢。
Thrift 主要由5個(gè)部分組成:
- 類型系統(tǒng)以及 IDL 編譯器:負(fù)責(zé)由用戶給定的 IDL 文件生成相應(yīng)語言的接口代碼
- TProtocol:實(shí)現(xiàn) RPC 的協(xié)議層,可以選擇多種不同的對象串行化方式,如 JSON, Binary。
- TTransport:實(shí)現(xiàn) RPC 的傳輸層,同樣可以選擇不同的傳輸層實(shí)現(xiàn),如socket, 非阻塞的 socket, MemoryBuffer 等。
- TProcessor:作為協(xié)議層和用戶提供的服務(wù)實(shí)現(xiàn)之間的紐帶,負(fù)責(zé)調(diào)用服務(wù)實(shí)現(xiàn)的接口。
- TServer:聚合 TProtocol, TTransport 和 TProcessor 幾個(gè)對象。
上述的這5個(gè)部件都是在 Thrift 的源代碼中通過為不同語言提供庫來實(shí)現(xiàn)的,這些庫的代碼在 Thrift 源碼目錄的 lib 目錄下面,在使用 Thrift 之前需要先熟悉與自己的語言對應(yīng)的庫提供的接口。
Thrift 包含一個(gè)完整的堆棧結(jié)構(gòu)用于構(gòu)建客戶端和服務(wù)器端。下圖描繪了 Thrift 的整體架構(gòu)。
數(shù)據(jù)類型
Thrift 腳本可定義的數(shù)據(jù)類型包括以下幾種類型:
基本類型:
bool:布爾值,true 或 false,對應(yīng) C#的 bool
byte:8 位有符號(hào)整數(shù),對應(yīng) C#的 byte
i16:16 位有符號(hào)整數(shù),對應(yīng) C#的 short
i32:32 位有符號(hào)整數(shù),對應(yīng) C#的 int
i64:64 位有符號(hào)整數(shù),對應(yīng) C#的 long
double:64 位浮點(diǎn)數(shù),對應(yīng) C#的 double
string:未知編碼文本或二進(jìn)制字符串,對應(yīng) C#的 string結(jié)構(gòu)體類型:
struct:定義公共的對象,類似于 C 語言中的結(jié)構(gòu)體定義,在 C#中是一個(gè)實(shí)體類容器類型:
list:對應(yīng) C#的 List<T> 有序集合
set:對應(yīng) C#的 HashSet<T>無序但是不能重復(fù)的集合
map:對應(yīng) C#的 Dictionary<TKey,TValue>鍵值對集合,鍵不能重復(fù)異常類型:
exception:對應(yīng) C#的 Exception服務(wù)類型:
service:對應(yīng)服務(wù)的類
Thrift的協(xié)議棧結(jié)構(gòu)
Thrift是一種c/s的架構(gòu)體系.在最上層是用戶自行實(shí)現(xiàn)的業(yè)務(wù)邏輯代碼.第二層是由thrift編譯器自動(dòng)生成的代碼,主要用于結(jié)構(gòu)化數(shù)據(jù)的解析,發(fā)送和接收。TServer主要任務(wù)是高效的接受客戶端請求,并將請求轉(zhuǎn)發(fā)給Processor處理。Processor負(fù)責(zé)對客戶端的請求做出響應(yīng),包括RPC請求轉(zhuǎn)發(fā),調(diào)用參數(shù)解析和用戶邏輯調(diào)用,返回值寫回等處理。從TProtocol以下部分是thirft的傳輸協(xié)議和底層I/O通信。TProtocol是用于數(shù)據(jù)類型解析的,將結(jié)構(gòu)化數(shù)據(jù)轉(zhuǎn)化為字節(jié)流給TTransport進(jìn)行傳輸。TTransport是與底層數(shù)據(jù)傳輸密切相關(guān)的傳輸層,負(fù)責(zé)以字節(jié)流方式接收和發(fā)送消息體,不關(guān)注是什么數(shù)據(jù)類型。底層IO負(fù)責(zé)實(shí)際的數(shù)據(jù)傳輸,包括socket、文件和壓縮數(shù)據(jù)流等。
Thrift支持的傳輸協(xié)議
Thrift支持多種傳輸協(xié)議,我們可以根據(jù)自己的需要來選擇合適的類型,總體上來說,分為文本傳輸和二進(jìn)制傳輸,由于二進(jìn)制傳輸在傳輸速率和節(jié)省帶寬上有優(yōu)勢,所以大部分情況下使用二進(jìn)制傳輸是比較好的選擇.
- TBinaryProtocol:使用二進(jìn)制編碼格式傳輸,是thrift的默認(rèn)傳輸協(xié)議
- TCompactProtocol:使用壓縮格式傳輸
- TJSONProtocol :使用JSON格式傳輸
- TDebugProtocol – 使用易懂可讀的文本格式進(jìn)行傳輸,以便于debug
- TSimpleJSONProtocol – 提供JSON只寫的協(xié)議,適用于通過腳本語言解析
Thrift支持的服務(wù)模型
TSimpleServer:
這種工作模式只有一個(gè)線程,循環(huán)監(jiān)聽傳過來的請求并對其進(jìn)行處理,處理完才能接受下一個(gè)請求,是一種阻塞式IO的實(shí)現(xiàn),因?yàn)樾时容^低,實(shí)際線上環(huán)境一般用不到.一般用于開發(fā)時(shí)候演示工作流程時(shí)使用.TNonblockingServer:
這種模式與TsimpleServer最大的區(qū)別就是使用NIO,也就是非阻塞是IO的方式實(shí)現(xiàn)IO的多路復(fù)用,它可以同時(shí)監(jiān)聽多個(gè)socket的變化,但因?yàn)闃I(yè)務(wù)處理上還是單線程模式,所以在一些業(yè)務(wù)處理比較復(fù)雜耗時(shí)的時(shí)候效率還是不高,因?yàn)槎鄠€(gè)請求任務(wù)依然需要排隊(duì)一個(gè)一個(gè)進(jìn)行處理.TThreadPoolServer:
這種模式引入了線程池,主線程只負(fù)責(zé)accept,即監(jiān)聽Socket,當(dāng)有新的請求(客戶端Socket)來時(shí),就會(huì)在線程池里起一個(gè)線程來處理業(yè)務(wù)邏輯,這樣在并發(fā)量比較大的時(shí)候(但不超過線程池的數(shù)量)每個(gè)請求都能及時(shí)被處理,效率比較高,但一旦并發(fā)量很大的時(shí)候(超過線程池?cái)?shù)量),后面來的請求也只能排隊(duì)等待.TThreadedSelectorServer:
這是一種多線程半同步半異步的服務(wù)模型,是Thrift提供的最復(fù)雜最高級的服務(wù)模型,內(nèi)部有一個(gè)專門負(fù)責(zé)處理監(jiān)聽Socket的線程,有多個(gè)專門處理業(yè)務(wù)中網(wǎng)絡(luò)IO的線程,有一個(gè)專門負(fù)責(zé)決定將新Socket連接分配給哪一個(gè)線程處理的起負(fù)載均衡作用的線程,還有一個(gè)工作線程池.這種模型既可以響應(yīng)大量并發(fā)連接的請求又可以快速對wangluoIO進(jìn)行讀寫,能適配很多場景,因此是一種使用比較高頻的服務(wù)模型.