Thrift是一個跨語言的服務部署框架,最初由Facebook於2007年開發,2008年進入Apache開源項目。Thrift經過一箇中間語言(IDL, 接口定義語言)來定義RPC的接口和數據類型,而後經過一個編譯器生成不一樣語言的代碼(目前支持C++,Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, Smalltalk和OCaml),並由生成的代碼負責RPC協議層和傳輸層的實現。javascript
Thrift其實是實現了C/S模式,經過代碼生成工具將接口定義文件生成服務器端和客戶端代碼(能夠爲不一樣語言),從而實現服務端和客戶端跨語言的支持。用戶在Thirft描述文件中聲明本身的服務,這些服務通過編譯後會生成相應語言的代碼文件,而後用戶實現服務(客戶端調用服務,服務器端提供服務)即可以了。其中protocol(協議層, 定義數據傳輸格式,能夠爲二進制或者XML等)和transport(傳輸層,定義數據傳輸方式,能夠爲TCP/IP傳輸,內存共享或者文件共享等)被用做運行時庫。html
下載地址:http://incubator.apache.org/thrift/download/java
編譯安裝:python
./configure make make install
Thrift類型系統包括預約義基本類型,用戶自定義結構體,容器類型,異常和服務定義linux
注意,thrift不支持無符號整型,由於不少目標語言不存在無符號整型(如java)。git
Thrift容器與類型密切相關,它與當前流行編程語言提供的容器類型相對應,採用java泛型風格表示的。Thrift提供了3種容器類型:github
容器中的元素類型能夠是除了service意外的任何合法thrift類型(包括結構體和異常)。web
Thrift結構體在概念上同C語言結構體類型—-一種將相關屬性彙集(封裝)在一塊兒的方式。在面嚮對象語言中,thrift結構體被轉換成類。shell
異常在語法和功能上相似於結構體,只不過異常使用關鍵字exception而不是struct關鍵字聲明。但它在語義上不一樣於結構體—當定義一個RPC服務時,開發者可能須要聲明一個遠程方法拋出一個異常。apache
服務的定義方法在語法上等同於面嚮對象語言中定義接口。Thrift編譯器會產生實現這些接口的client和server樁。
Thrift支持C/C++風格的typedef:
typedef i32 MyInteger \\a typedef Tweet ReTweet \\b
說明:
能夠像C/C++那樣定義枚舉類型,如:
enum TweetType { TWEET, //a RETWEET = 2, //b DM = 0xa, //c REPLY } //d struct Tweet { required i32 userId; required string userName; required string text; optional Location loc; optional TweetType tweetType = TweetType.TWEET // e optional string language = "english" }
說明:
注意,不一樣於protocol buffer,thrift不支持枚舉類嵌套,枚舉常量必須是32位的正整數
Thrfit支持shell註釋風格,C/C++語言中單行或者多行註釋風格
# This is a valid comment. /* * This is a multi-line comment. * Just like in C. */ // C++/Java style single-line comments work just as well.
Thrift中的命名空間同C++中的namespace和java中的package相似,它們均提供了一種組織(隔離)代碼的方式。由於每種語言均有本身的命名空間定義方式(如python中有module),thrift容許開發者針對特定語言定義namespace:
namespace cpp com.example.project // a namespace java com.example.project // b
說明:
Thrift容許thrift文件包含,用戶須要使用thrift文件名做爲前綴訪問被包含的對象,如:
include "tweet.thrift" // a ... struct TweetSearchResult { list<tweet.Tweet> tweets; // b }
說明:
Thrift容許用戶定義常量,複雜的類型和結構體可以使用JSON形式表示。
const i32 INT_CONST = 1234; // a const map<string,string> MAP_CONST = {"hello": "world", "goodnight": "moon"}
說明:分號是可選的,無關緊要;支持十六進制賦值。
結構體由一系列域組成,每一個域有惟一整數標識符,類型,名字和可選的缺省參數組成。如:
struct Tweet { required i32 userId; // a required string userName; // b required string text; optional Location loc; // c optional string language = "english" // d } struct Location { // e required double latitude; required double longitude; }
說明:
規範的struct定義中的每一個域均會使用required或者optional關鍵字進行標識。若是required標識的域沒有賦值,thrift將給予提示。若是optional標識的域沒有賦值,該域將不會被序列化傳輸。若是某個optional標識域有缺省值而用戶沒有從新賦值,則該域的值一直爲缺省值。
與service不一樣,結構體不支持繼承,即,一個結構體不能繼承另外一個結構體。
在流行的序列化/反序列化框架(如protocol buffer)中,thrift是少有的提供多語言間RPC服務的框架。
Thrift編譯器會根據選擇的目標語言爲server產生服務接口代碼,爲client產生樁代碼。
//「Twitter」與「{」之間須要有空格!!! service Twitter { // 方法定義方式相似於C語言中的方式,它有一個返回值,一系列參數和可選的異常 // 列表. 注意,參數列表和異常列表定義方式與結構體中域定義方式一致. void ping(), // a bool postTweet(1:Tweet tweet); // b TweetSearchResult searchTweets(1:string query); // c // 」oneway」標識符表示client發出請求後沒必要等待回覆(非阻塞)直接進行下面的操做, // 」oneway」方法的返回值必須是void oneway void zip() // d }
說明:
注意,函數中參數列表的定義方式與struct徹底同樣
Service支持繼承,一個service可以使用extends關鍵字繼承另外一個service
本節介紹thrift產生各類目標語言代碼的方式。本節從幾個基本概念開始,逐步引導開發者瞭解產生的代碼是怎麼樣組織的,進而幫助開發者更快地明白thrift的使用方法。
概念
Thrift的網絡棧以下所示:
Transport層提供了一個簡單的網絡讀寫抽象層。這使得thrift底層的transport從系統其它部分(如:序列化/反序列化)解耦。如下是一些Transport接口提供的方法:
open close read write flush
除了以上幾個接口,Thrift使用ServerTransport接口接受或者建立原始transport對象。正如名字暗示的那樣,ServerTransport用在server端,爲到來的鏈接建立Transport對象。
open listen accept close
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()
下面是一些對大部分thrift支持的語言都可用的protocol:
Processor封裝了從輸入數據流中讀數據和向數據數據流中寫數據的操做。讀寫數據流用Protocol對象表示。Processor的結構體很是簡單:
interface TProcessor { bool process(TProtocol in, TProtocol out) throws TException }
與服務相關的processor實現由編譯器產生。Processor主要工做流程以下:從鏈接中讀取數據(使用輸入protocol),將處理受權給handler(由用戶實現),最後將結果寫到鏈接上(使用輸出protocol)。
Server將以上全部特性集成在一塊兒:
下面,咱們討論thrift文件產生的特定語言代碼。下面給出thrift文件描述:
namespace cpp thrift.example namespace java thrift.example enum TweetType { TWEET, RETWEET = 2, DM = 0xa, REPLY } struct Location { required double latitude; required double longitude; } struct Tweet { required i32 userId; required string userName; required string text; optional Location loc; optional TweetType tweetType = TweetType.TWEET; optional string language = "english"; } typedef list<Tweet> TweetList struct TweetSearchResult { TweetList tweets; } const i32 MAX_RESULTS = 100; service Twitter { void ping(), bool postTweet(1:Tweet tweet); TweetSearchResult searchTweets(1:string query); oneway void zip() }
產生的文件
一個單獨的文件(Constants.java)包含全部的常量定義。
每一個結構體,枚舉或者服務各佔一個文件
$ tree gen-java `– thrift `– example |– Constants.java |– Location.java |– Tweet.java |– TweetSearchResult.java |– TweetType.java `– Twitter.java
類型
thrift將各類基本類型和容器類型映射成java類型:
bool: boolean byte: byte i16: short i32: int i64: long double: double string: String list<t1>: List<t1> set<t1>: Set<t1> map<t1,t2>: Map<t1, t2>
typedef
Java不支持typedef,它只使用原始類型,如,在上面的例子中,產生的代碼中,TweetSearchResult會被還原成list tweets
Enum
Thrift直接將枚舉類型映射成java的枚舉類型。用戶可使用geValue方法獲取枚舉常量的值。此外,編譯器會產生一個findByValue方法獲取枚舉對應的數值。
常量
Thrift把全部的常量放在一個叫Constants的public類中,每一個常量修飾符是public static final。
產生的文件
全部變量均存放在一個.cpp/.h文件對中
全部的類型定義(枚舉或者結構體)存放到另外一個.cpp/.h文件對中
每個service有本身的.cpp/.h文件
$ tree gen-cpp |– example_constants.cpp |– example_constants.h |– example_types.cpp |– example_types.h |– Twitter.cpp |– Twitter.h `– Twitter_server.skeleton.cpp
Python,Ruby,javascript等
thrift文件內容可能會隨着時間變化的。若是已經存在的消息類型再也不符合設計要求,好比,新的設計要在message格式中添加一個額外字段,但你仍想使用之前的thrift文件產生的處理代碼。若是想要達到這個目的,只需:
struct Example{ i32 id, string name, optional age, }
中的optional字段age賦值,須要將它的 __isset
值設爲true,這樣才能序列化並傳輸或者存儲(否則optional字段被認爲不存在,不會被傳輸或者存儲),
如:
Example example; example.age=10, example.__isset.age = true; //__isset是每一個thrift對象的自帶的public成員,來指定optional字段是否啓用並賦值。
OBSOLETE_
以防止其餘字段使用它的整數編號。主要流程:
通常將服務放到一個.thrift文件中,服務的編寫語法與C語言語法基本一致,在.thrift文件中有主要有如下幾個內容:
下面分析Thrift的tutorial中帶的例子tutorial.thrift
include 「shared.thrift」
namespace cpp tutorial
const i32 INT32CONSTANT = 9853
struct Work { i32 num1 = 0, i32 num2, Operation op, optional string comment, }
定義服務:
service Calculator extends shared.SharedService { void ping(), i32 add(1:i32 num1, 2:i32 num2), i32 calculate(1:i32 logid, 2:Work w) throws (1:InvalidOperation ouch), oneway void zip() }
client端和sever端代碼要調用編譯.thrift生成的中間文件。下面分析cpp文件下面的CppClient.cpp和CppServer.cpp代碼
在client端,用戶自定義CalculatorClient類型的對象(用戶在.thrift文件中聲明的服務名稱是Calculator, 則生成的中間代碼中的主類爲CalculatorClient), 該對象中封裝了各類服務,能夠直接調用(如client.ping()), 而後thrift會經過封裝的rpc調用server端同名的函數。
在server端,須要實如今.thrift文件中聲明的服務中的全部功能,以便處理client發過來的請求。