Thrift架構介紹

 


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


傳輸

支持的傳輸格式
  • TBinaryProtocol – 二進制格式.
  • TCompactProtocol – 壓縮格式
  • TJSONProtocol – JSON格式
  • TSimpleJSONProtocol –提供JSON只寫協議, 生成的文件很容易經過腳本語言解析。
  • TDebugProtocol – 使用易懂的可讀的文本格式,以便於debug
支持的數據傳輸方式
  • TSocket -阻塞式socker
  • TFramedTransport – 以frame爲單位進行傳輸,非阻塞式服務中使用。
  • TFileTransport – 以文件形式進行傳輸。
  • TMemoryTransport – 將內存用於I/O. java實現時內部實際使用了簡單的ByteArrayOutputStream。
  • TZlibTransport – 使用zlib進行壓縮, 與其餘傳輸方式聯合使用。當前無java實現。
支持的服務模型
  • TSimpleServer – 簡單的單線程服務模型,經常使用於測試
  • TThreadPoolServer – 多線程服務模型,使用標準的阻塞式IO。
  • TNonblockingServer – 多線程服務模型,使用非阻塞式IO(需使用TFramedTransport數據傳輸方式)

Thrift安裝

下載地址:http://incubator.apache.org/thrift/download/java

  • 安裝要求:Unix/linux 系統,windows+cygwin
  • C++語言:g++、boost
  • java 語言:JDK、Apache Ant
  • 其餘語言:Python、PHP、Perl, etc…

編譯安裝:python

./configure 
make
make install

基本語法

類型

Thrift類型系統包括預約義基本類型,用戶自定義結構體,容器類型,異常和服務定義linux

基本類型
  • bool:布爾類型(true or value),佔一個字節
  • byte:有符號字節
  • i16:16位有符號整型
  • i32:32位有符號整型
  • i64:64位有符號整型
  • double:64位浮點數
  • string:未知編碼或者二進制的字符串

注意,thrift不支持無符號整型,由於不少目標語言不存在無符號整型(如java)。git

容器類型

Thrift容器與類型密切相關,它與當前流行編程語言提供的容器類型相對應,採用java泛型風格表示的。Thrift提供了3種容器類型:github

  • List:一系列t1類型的元素組成的有序表,元素能夠重複
  • Set:一系列t1類型的元素組成的無序表,元素惟一
  • Map<t1,t2>:key/value對(key的類型是t1且key惟一,value類型是t2)。

容器中的元素類型能夠是除了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

說明:

  1. 末尾沒有逗號
  2. struct可使用typedef
枚舉類型

能夠像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"
}

說明:

  • 編譯器默認從0開始賦值
  • 能夠賦予某個常量某個整數
  • 容許常量是十六進制整數
  • 末尾沒有逗號
  • 給常量賦缺省值時,使用常量的全稱

注意,不一樣於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

說明:

  1. 轉化成namespace com { namespace example { namespace project {
  2. 轉換成package com.example.project
文件包含

Thrift容許thrift文件包含,用戶須要使用thrift文件名做爲前綴訪問被包含的對象,如:

include "tweet.thrift"           // a
...
struct TweetSearchResult {
    list<tweet.Tweet> tweets; // b
}

說明:

  1. thrift文件名要用雙引號包含,末尾沒有逗號或者分號
  2. 注意tweet前綴
常量

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;
}

說明:

  1. 每一個域有一個惟一的,正整數標識符
  2. 每一個域能夠標識爲required或者optional(也能夠不註明)
  3. 結構體能夠包含其餘結構體
  4. 域能夠有缺省值
  5. 一個thrift中可定義多個結構體,並存在引用關係

規範的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
}

說明:

  1. 函數定義可使用逗號或者分號標識結束
  2. 參數能夠是基本類型或者結構體,參數是隻讀的(const),不能夠做爲返回值!!!
  3. 返回值能夠是基本類型或者結構體
  4. 返回值能夠是void

注意,函數中參數列表的定義方式與struct徹底同樣

Service支持繼承,一個service可以使用extends關鍵字繼承另外一個service

生成代碼

本節介紹thrift產生各類目標語言代碼的方式。本節從幾個基本概念開始,逐步引導開發者瞭解產生的代碼是怎麼樣組織的,進而幫助開發者更快地明白thrift的使用方法。
概念

Thrift的網絡棧以下所示:

Transport

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

open close read write flush 

除了以上幾個接口,Thrift使用ServerTransport接口接受或者建立原始transport對象。正如名字暗示的那樣,ServerTransport用在server端,爲到來的鏈接建立Transport對象。

open listen accept close 
Protocol

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:

  1. binary:簡單的二進制編碼
  2. Compact:具體見THRIFT-11
  3. Json
Processor

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

interface TProcessor { bool process(TProtocol in, TProtocol out) throws TException } 

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

Server

Server將以上全部特性集成在一塊兒:

  1. 建立一個transport對象
  2. 爲transport對象建立輸入輸出protocol
  3. 基於輸入輸出protocol建立processor
  4. 等待鏈接請求並將之交給processor處理
應用舉例

下面,咱們討論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() } 
Java語言

產生的文件

一個單獨的文件(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。

C++語言

產生的文件

全部變量均存放在一個.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文件產生的處理代碼。若是想要達到這個目的,只需:

  • 不要修改已存在域的整數編號
  • 新添加的域必須是optional的,以便格式兼容。對於一些語言,若是要爲optional的字段賦值,須要特殊處理,好比對於C++語言,要爲
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字段是否啓用並賦值。
  • 非required域能夠刪除,前提是它的整數編號不會被其餘域使用。對於刪除的字段,名字前面可添加 OBSOLETE_以防止其餘字段使用它的整數編號。
  • thrift文件應該是unix格式的(windows下的換行符與unix不一樣,可能會致使你的程序編譯不過),若是是在window下編寫的,可以使用dos2unix轉化爲unix格式。
  • 貌似當前的thrift版本(0.6.1)不支持常量表達式的定義(如 const i32 DAY = 24 * 60 * 60),這多是考慮到不一樣語言,運算符不盡相同。

利用Thrift部署服務

主要流程:

  1. 編寫服務說明,保存到.thrift文件
  2. 根據須要, 編譯.thrift文件,生成相應的語言源代碼
  3. 根據實際須要, 編寫client端和server端代碼。
thrift文件編寫

通常將服務放到一個.thrift文件中,服務的編寫語法與C語言語法基本一致,在.thrift文件中有主要有如下幾個內容:

  1. 變量聲明
  2. 數據聲明(struct)
  3. 服務接口聲明(service, 能夠繼承其餘接口)。

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

  • 包含頭文件:59行:include 「shared.thrift」
  • 指定目標語言:65行:namespace cpp tutorial
  • 定義變量:80行:const i32 INT32CONSTANT = 9853
  • 定義結構體:103行:
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()
}
  • 要生成C++代碼:./thrift –gen cpp tutorial.thrift,結果代碼存放在gen-cpp目錄下
  • 要生成java代碼:./thrift –gen java tutorial.thrift,結果代碼存放在gen-java目錄下

client端和server端代碼編寫

client端和sever端代碼要調用編譯.thrift生成的中間文件。下面分析cpp文件下面的CppClient.cpp和CppServer.cpp代碼

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

在server端,須要實如今.thrift文件中聲明的服務中的全部功能,以便處理client發過來的請求。


參考資料

  1. http://diwakergupta.github.com/thrift-missing-guide/#_versioning_compatibility
  2. http://dongxicheng.org/search-engine/thrift-framework-intro/
  3. http://dongxicheng.org/search-engine/thrift-guide/
  4. http://dongxicheng.org/search-engine/search-engine/thrift-rpc/
  5. http://dongxicheng.org/search-engine/thrift-internals/
  6. http://wiki.apache.org/thrift/
  7. http://jnb.ociweb.com/jnb/jnbJun2009.html
  8. http://blog.rushcj.com/tag/thrift/
  9. http://www.vvcha.cn/c.aspx?id=31984
相關文章
相關標籤/搜索