thrift 的原理和使用

Thrift 架構?

Thrift是一個跨語言的服務部署框架,最初由Facebook于2007年開發,2008年進入Apache開源項目。Thrift通過IDL(Interface Definition Language,接口定義語言)來定義RPC(Remote Procedure Call,遠程過程調用)的接口和數據類型,然后通過thrift編譯器生成不同語言的代碼(目前支持C++,Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, Smalltalk和OCaml),并由生成的代碼負責RPC協議層和傳輸層的實現。

PS:CentOS下的Thrift的安裝流程可以參考這里


Thrift架構


圖中,TProtocol(協議層),定義數據傳輸格式,例如:

TBinaryProtocol:二進制格式;

TCompactProtocol:壓縮格式;

TJSONProtocol:JSON格式;

TSimpleJSONProtocol:提供JSON只寫協議, 生成的文件很容易通過腳本語言解析;

TDebugProtocol:使用易懂的可讀的文本格式,以便于debug

TTransport(傳輸層),定義數據傳輸方式,可以為TCP/IP傳輸,內存共享或者文件共享等)被用作運行時庫。

TSocket:阻塞式socker;

TFramedTransport:以frame為單位進行傳輸,非阻塞式服務中使用;

TFileTransport:以文件形式進行傳輸;

TMemoryTransport:將內存用于I/O,java實現時內部實際使用了簡單的ByteArrayOutputStream;

TZlibTransport:使用zlib進行壓縮, 與其他傳輸方式聯合使用,當前無java實現;


Thrift支持的服務模型

TSimpleServer:簡單的單線程服務模型,常用于測試;

TThreadPoolServer:多線程服務模型,使用標準的阻塞式IO;

TNonblockingServer:多線程服務模型,使用非阻塞式IO(需使用TFramedTransport數據傳輸方式);


Thrift實際上是實現了C/S模式,通過代碼生成工具將thrift文生成服務器端和客戶端代碼(可以為不同語言),從而實現服務端和客戶端跨語言的支持。用戶在Thirft文件中聲明自己的服務,這些服務經過編譯后會生成相應語言的代碼文件,然后客戶端調用服務,服務器端提服務便可以了。


一般將服務放到一個.thrift文件中,服務的編寫語法與C語言語法基本一致,在.thrift文件中有主要有以下幾個內容:變量聲明(variable)、數據聲明(struct)和服務接口聲明(service, 可以繼承其他接口)。

下面分析Thrift的tutorial中帶的例子tutorial.thrift:

// 包含頭文件include “shared.thrift”? ? ? ? // 指定目標語言namespace cpp tutorial? ? ? ? ? ? // 定義變量consti32 INT32CONSTANT =9853// 定義結構體struct Work {

? 1: i32 num1 =0,

? 2: i32 num2,

? 3: Operation op,

? 4: optionalstring comment,

}// 定義服務service Calculator extends shared.SharedService {

/**

? * A method definition looks like C code. It has a return type, arguments,

? * and optionally a list of exceptions that it may throw. Note that argument

? * lists and exception lists are specified using the exact same syntax as

? * field lists in struct or exception definitions.

? */void ping(),

? i32 add(1:i32 num1,2:i32 num2),

? i32 calculate(1:i32 logid,2:Work w) throws (1:InvalidOperation ouch),

? /**

? ? * This method has a oneway modifier. That means the client only makes

? ? * a request and does not listen for any response at all. Oneway methods

? ? * must be void.

? ? */? oneway void zip()

}



編譯thrift文件,生成C++代碼:

./thrift --gencpptutorial.thrift   #結果代碼存放在gen-cpp目錄下


如果是要生成java代碼:

./thrift --gen java tutorial.thrift  #結果代碼存放在gen-java目錄下


client端和sever端代碼要調用編譯.thrift生成的中間文件。

下面分析cpp文件下面的CppClient.cpp和CppServer.cpp代碼




在client端,用戶自定義CalculatorClient類型的對象(用戶在.thrift文件中聲明的服務名稱是Calculator, 則生成的中間代碼中的主類為CalculatorClient), 該對象中封裝了各種服務,可以直接調用(如client.ping()), 然后thrift會通過封裝的rpc調用server端同名的函數。

在server端,需要實現在.thrift文件中聲明的服務中的所有功能,以便處理client發過來的請求。



Thrift語法

Thrift文件支持shell命令,因此thrift是可執行的。

Thrfit支持shell注釋風格(#),也支持C/C++語言中單行(//)或者多行(/* */)注釋風格


數據類型

1、基本類型

bool,布爾型,1個字節;

byte,有符號單字節;

i16,有符號16位整型;

i32,有符號32位整型;

i64,有符號64位整型;

double,64位浮點數;

string,字符串;

binary,字節數組;

注意:thrift不支持無符號整型。


2、容器

map<t1,t2>,字典;

list<t1>,列表;

set<t1>,集合;

注意:容器中的元素類型可以是除了service 以外的任何合法thrift類型(包括結構體和異常)。


3、結構體 struct

Thrift結構體在概念上同C語言結構體類型—-一種將相關屬性聚集(封裝)在一起的方式;

在面向對象語言中,thrift結構體被轉換成類。

struct Work {

? 1: i32 num1 =0,

? 2: i32 num2,

? 3: Operation op,

? 4: optionalstring comment,

}

結構體中,每個字段包含一個整數ID,數據類型、字段名,和一個可選的默認值。

字段還可以聲明為"optional",當該字段沒有設置的時候,不會被序列化輸出;

規范的struct定義中的每個域均會使用required或者optional關鍵字進行標識。如果required標識的域沒有賦值,thrift將給予提示。如果optional標識的域沒有賦值,該域將不會被序列化傳輸。如果某個optional標識域有缺省值而用戶沒有重新賦值,則該域的值一直為缺省值。


4、異常?exception

異常在語法和功能上類似于結構體,只不過異常使用關鍵字exception而不是struct關鍵字聲明。但它在語義上不同于結構體,當定義一個RPC服務時,開發者可能需要聲明一個遠程方法拋出一個異常。

exception InvalidOperation {

? 1: i32 what,

? 2:string why

}


5、服務 service?

在流行的序列化/反序列化框架(如protocol buffer)中,Thrift是少有的提供多語言間RPC服務的框架。

Thrift編譯器會根據選擇的目標語言為server產生服務接口代碼,為client產生樁代碼。

//“Twitter”與“{”之間需要有空格!!!service Twitter {

// 方法定義方式類似于C語言中的方式,它有一個返回值,一系列參數和可選的異常

// 列表. 注意,參數列表和異常列表定義方式與結構體中域定義方式一致.voidping(),// 函數定義可以使用逗號或者分號標識結束boolpostTweet(1:Tweet tweet);// 參數可以是基本類型或者結構體,參數是只讀的(const),不可以作為返回值!!!

TweetSearchResult searchTweets(1:stringquery);// 返回值可以是基本類型或者結構體

// ”oneway”標識符表示client發出請求后不必等待回復(非阻塞)直接進行下面的操作,

// ”oneway”方法的返回值必須是void

oneway voidzip()// 返回值可以是void

}

service中的函數,其參數列表的定義方式與struct完全一樣;

service支持繼承,一個service可使用extends關鍵字繼承另一個service,struct不支持繼承;


6、枚舉類型 enum

enum TweetType {

TWEET,? ? ? ? // 編譯器默認從1開始賦值RETWEET =2,// 可以賦予某個常量某個整數DM =0xa,//允許常量是十六進制整數REPLY? ? ? ? // 末尾沒有逗號

}? ? ? ?

struct Tweet {1: required i32 userId;2: requiredstring userName;3: requiredstring text;4: optional Location loc;5: optional TweetType tweetType = TweetType.TWEET// 給常量賦缺省值時,使用常量的全稱16: optionalstringlanguage ="english"}

注意:枚舉常量必須是32位的正整數


7、常量 const

Thrift允許用戶定義常量,復雜的類型和結構體可使用JSON形式表示。

consti32 INT_CONST =1234;// 分號是可選的constmap MAP_CONST = {"hello":"world","goodnight":"moon"}


PS:跟C語言類似,Thrift也支持typedef語句,例如:

typedef i32 MyInteger


命名空間

Thrift中的命名空間同C++中的namespace類似,它們均提供了一種組織(隔離)代碼的方式。因為每種語言均有自己的命名空間定義方式(如python中有module),thrift允許開發者針對特定語言定義namespace:

namespace cpp com.example.project namespacejava com.example.project



產生代碼

下面介紹Thrift產生各種目標語言代碼的方式,


Thrift的網絡棧如下所示:


Transport層提供了一個簡單的網絡讀寫抽象層。這使得thrift底層的transport從系統其它部分(如:序列化/反序列化)解耦。

以下是一些Transport接口提供的方法:

open

close

read

write

listen

accept

flush


Protocol抽象層定義了一種將內存中數據結構映射成可傳輸格式的機制。換句話說,Protocol定義了datatype怎樣使用底層的Transport對自己進行編解碼。因此,Protocol的實現要給出編碼機制并負責對數據進行序列化。

Protocol接口的定義如下:

writeMessageBegin(name, type, seq)

writeMessageEnd()

writeStructBegin(name)

writeStructEnd()

writeFieldBegin(name, type, id)

writeFieldEnd()

writeFieldStop()

writeMapBegin(ktype, vtype, size)

writeMapEnd()

writeListBegin(etype, size)

writeListEnd()

writeSetBegin(etype, size)

writeSetEnd()

writeBool(bool)

writeByte(byte)

writeI16(i16)

writeI32(i32)

writeI64(i64)

writeDouble(double)

writeString(string)

name, type, seq = readMessageBegin()

readMessageEnd()

name = readStructBegin()

readStructEnd()

name, type, id = readFieldBegin()

readFieldEnd()

k, v, size = readMapBegin()

readMapEnd()

etype, size = readListBegin()

readListEnd()

etype, size = readSetBegin()

readSetEnd()bool= readBool()byte= readByte()

i16 = readI16()

i32 = readI32()

i64 = readI64()double= readDouble()string= readString()


Processor封裝了從輸入數據流中讀數據和向數據數據流中寫數據的操作。讀寫數據流用Protocol對象表示。Processor的結構體非常簡單:

interface TProcessor {

boolprocess(TProtocolin, TProtocolout) throws TException

}

與服務相關的processor實現由編譯器產生。Processor主要工作流程如下:從連接中讀取數據(使用輸入protocol),將處理授權給handler(由用戶實現),最后將結果寫到連接上(使用輸出protocol)。


Server將以上所有特性集成在一起:

(1) 創建一個transport對象

(2) 為transport對象創建輸入輸出protocol

(3) 基于輸入輸出protocol創建processor

(4) 等待連接請求并將之交給processor處理







參考文檔:

http://dongxicheng.org/search-engine/thrift-framework-intro/

http://dongxicheng.org/search-engine/thrift-guide/

http://dongxicheng.org/search-engine/thrift-internals/

http://dongxicheng.org/search-engine/thrift-bidirectional-async-rpc/

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容