Thrift簡介及語法

本文介紹thrift如何定義及編寫接口描述文檔*.thrift


1. 簡介

是什麼?

Thrift是Facebook開發的軟件庫和代碼生成工具,可用於加速高效可擴展的後臺服務的開發實現[1]

爲什麼?

隨着流量和網絡結構的擴展,許多操作(即搜索、廣告選擇和交付、事件記錄)的資源需求已經遠遠超出了LAMP框架(Linux、Apache、MySQL和PHP或3P的簡稱,)的範圍。實現這些服務的過程中,我們選擇了各種編程語言來優化性能、開發的簡便性和速度、現有庫的可用性等方面的最佳組合。簡單來說,選擇最好的工具和實現,而不是標準化任何一種編程語言。

Thirift在多種編程語言之間構建一個透明的、高性能的橋樑。而其他解決方案不能支持足夠的數據類型

目標

通過將每一種語言中最需要定製的部分抽象到用每一種語言實現的公共庫中,從而實現跨編程語言的高效可靠的通信。Thrift允許開發人員在一個獨立於語言的文件中定義數據類型服務接口,並生成構建RPC客戶機和服務器所需的所有代碼。

做什麼?

爲描述不同網絡環境的跨語言藉口,定義了一些組件。

  • 類型
  • 傳輸
  • 協議
  • 版本控制
  • 處理器

2. 語法

2.1 頭部

頭文件可以包含thrift文件c++頭文件,以及命名空間聲明[2][3]

2.1.1 thrift頭文件

include使另一個文件中的所有符號都可見(帶有前綴),並將相應的include語句添加到爲這個Thrift文檔生成的代碼中。
例如編寫了shared.thrift,可以在tutorial.thrift文件中包含。注意在include前沒有#

 include "shared.thrfit"

2.1.2 C++頭文件

c++ include將自定義c++ include添加到此Thrift文檔的c++代碼生成器的輸出中。

cpp_include "test.h"

2.1.3 namespace

namespace字段聲明的命名空間(namespace)、包(package)、模塊(models)等。thrift允許開發者針對特定語言定義namespace。對於所有目標語言使用命名空間用*表示。

namespace cl tutorial
namespace cpp tutorial
namespace d tutorial
namespace dart tutorial
namespace java tutorial
namespace php tutorial
namespace perl tutorial
namespace haxe tutorial
namespace netstd tutorial

2.2 定義標識符

包含

  • Const
  • Typedef
  • Enum
  • Senum :棄用
  • Struct
  • Union
  • Exception
  • Service

2.2.1 const :常量

// 語法格式:const FieldType Identifier = ConstValue ListSeparator?
const i32 INT32CONSTANT = 9853

2.2.2 typedef:類型更名

// 語法格式:typedef DefinitionType Identifier
typedef i32 MyInteger

2.2.3 enum:枚舉

  • 如果沒有提供常量值,則第一個元素的值爲0,後續任何元素的值都大於前一個值。提供的任何常數值必須是非負的。
  • 注意所有包含{}的,Identifier都與{}之間有個空格
// 語法格式:enum Identifier { (Identifier (= IntConstant)? ListSeparator?)* }
enum Operation {
  ADD = 1,
  SUBTRACT = 2,
  MULTIPLY = 3,
  DIVIDE = 4
}

2.2.4 struct:結構體

  • 與service不同,結構體不支持繼承,一個結構體不能繼承另一個結構體。

  • 如果required標識的字段沒有賦值,thrift將給予提示。

  • 如果optional標識的字段沒有賦值,該字段將不會被序列化傳輸。如果某個optional標識字段有缺省值而用戶沒有重新賦值,則該字段的值一直爲缺省值。

// 語法格式:struct Identifier xsd_all? { Field* }
struct Work {
  1: i32 num1 = 0,
  2: i32 num2,
  3: Operation op,
  4: optional string comment,
}

2.2.5 union:共用體

union與struct類似,就像c++中的union{}[4]一樣,所有成員佔用同一段內存,修改一個成員會影響其餘所有成員。因此,union成員被隱式地認爲是可選的(參見requireness)。

// 語法格式:union Identifier xsd_all? { Field* }
union Sample {
  1: map<string, string> u1,
  2: binary u2,
  3: list<string> u3
}

2.2.6 exception:異常

異常類似於struct,只是異常使用關鍵字exception而不是struct關鍵字來聲明,在語義上當定義一個RPC服務時,開發者可能需要聲明一個遠程方法拋出一個異常。每個字段的名稱在異常內必須是唯一的。

// 語法格式:exception Identifier { Field* }
exception InvalidOperation {
  1: i32 what,
  2: string why
}

2.2.7 service:服務

service爲thrift服務器一組函數提供接口。接口只是一個函數列表。一個服務可以擴展另一個服務,這僅僅意味着它除了提供自己的服務外,還提供擴展服務的功能。

  • ”oneway”標識符表示client發出請求後不必等待回覆(非阻塞)直接進行下面的操作
  • ”oneway”方法的返回值必須是void
  • 參數可以是基本類型或者結構體,參數只能是隻讀的(const),不可以作爲返回值
// 語法格式:service Identifier ( extends Identifier )? { Function* }
service SharedService {
  SharedStruct getStruct(1: i32 key)
}

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()
}

2.3 註釋

Thrift支持shell註釋風格、C/C++語言中的單行或多行註釋風格

2.4 字段

  • Field ID
  • Field Requiredness

2.4.1 field id: 字段id

  • 整數,常數
// 語法格式:IntConstant :
struct Work {
  1: i32 num1 = 0,
  2: i32 num2,
  3: Operation op,
  4: optional string comment,
}

2.4.2 字段請求狀況

  • required

  • write:必填項總是被寫,並且被期望被設置。

  • read:必填項字段總是被讀取的,並且被期望包含在輸入流中。

  • 默認值:總是寫入

  • optional

  • 寫:可選字段只有在設置時才寫

  • 讀:可選字段可能是輸入流的一部分,也可能不是。

  • 默認值:在設置isset標誌時寫入

2.5 函數

像C代碼。它有一個返回類型、參數和可選的可能拋出的異常列表。注意,參數列表和異常列表是使用與結構或異常定義中的字段列表完全相同的語法。

  • oneway:標識符表示client發出請求後不必等待回覆(非阻塞)直接進行下面的操作,返回值必須是void
  • throws:異常拋出列表。參數爲exception類型
   i32 calculate(1:i32 logid, 2:Work w) throws (1:InvalidOperation ouch),
   oneway void zip()

2.6 數據類型

數據類型包含基礎數據類型容器類型

2.6.1 基礎數據類型

  • bool
  • byte
  • i8 / i16 / i32 / i64(很少有平臺能支持64位整數哦,一般都是4字節(32位)整數,數字表示位數,注意沒有無符號型)
  • double
  • string
  • binary
  • slist:棄用了

2.6.2 容器類型

  • 容器(字典):map CppType? < FieldType , FieldType >
  • 集合:set CppType? < FieldType >
  • 列表:list < FieldType > CppType?

2.7 基礎定義規範

  • 命名:可以包含下劃線/ 點/ 短線 / 數字 / 字母 ‘.’ | ‘_’ | ‘-’
  • 分隔:逗號或分號(英文) ‘,’ | ‘;’
  • 字母:[‘A’-‘Z’] | [‘a’-‘z’]
  • 數字:[‘0’-‘9’]

2.8 示例

namespace * Skiptest.Two

const i32 SKIPTESTSERVICE_VERSION = 2

enum PingPongEnum {
	PingOne = 0,
	PongOne = 1,
	PingTwo = 2,
	PongTwo = 3,
}

struct Pong {
  1 : optional i32 version1
  2 : optional i16 version2
  100 : PingPongEnum EnumTest
}

struct Ping {
  1 : optional i32 version1
  10 : optional bool boolVal
  11 : optional byte byteVal
  12 : optional double dbVal
  13 : optional i16 i16Val
  14 : optional i32 i32Val
  15 : optional i64 i64Val
  16 : optional string strVal
  17 : optional Pong structVal
  18 : optional map< list< Pong>, set< string>> mapVal
  100 : PingPongEnum EnumTest
}

exception PingFailed {
  1 : optional i32 pingErrorCode
}

exception PongFailed {
  222 : optional i32 pongErrorCode
  10 : optional bool boolVal
  11 : optional byte byteVal
  12 : optional double dbVal
  13 : optional i16 i16Val
  14 : optional i32 i32Val
  15 : optional i64 i64Val
  16 : optional string strVal
  17 : optional Pong structVal
  18 : optional map< list< Pong>, set< string>> mapVal
}


service SkipTestService {
  Ping PingPong( 1: Ping ping, 3: Pong pong) throws (1: PingFailed pif, 444: PongFailed pof);
}

3. 傳輸

生成的代碼使用傳輸層來促進數據傳輸。

3.1 接口

Thrift實現中的一個關鍵設計選擇是將傳輸層與代碼生成層解耦。雖然Thrift通常用在TCP/IP棧的頂部,流套接字作爲通信的底層,但是沒有必要將這種約束構建到系統中。與實際的I/O操作(通常調用系統調用)的成本相比,抽象的I/O層(每個操作有一個虛擬方法查找/函數調用)帶來的性能權衡是微不足道的。

通常,生成的Thrift代碼只需要知道如何讀寫數據。數據源和目的是不相關的;它可以是套接字、共享內存段或本地磁盤上的文件。Thrift傳輸接口支持以下方法:

  • open: 打開傳輸
  • close:關閉傳輸
  • isOpen:傳輸是否打開
  • read:從傳輸中讀入
  • write:寫到傳輸
  • flush:刷新

除了上述的TTransport接口,還有TServerTransport接口用來接收或創建私有傳輸對象。這些接口包括:

  • open:打開傳輸
  • listen:開始監聽連接
  • accept:返回一個新的客戶端傳輸
  • close:關閉傳輸

3.2 實現

傳輸接口爲各種編程語言都設計了簡單實現。一個新的傳輸機制可以根據需求輕鬆地定義。

  • TSocket
    TSocket類是跨所有目標語言實現的。它爲TCP/IP流套接字提供了一個通用的、簡單的接口。
  • TFileTransport
    TFileTransport是將磁盤上的文件抽象爲數據流。它可以用來將一組傳入的Thrift請求寫到磁盤上的一個文件中。然後,可以從日誌中恢復磁盤上的數據,以進行後處理。
  • 通用
    傳輸接口的設計目的是使用常見的OOP技術[5](如組合)支持簡單的擴展。如BufferedTransport,緩衝區寫入和讀。TFramedTransport,傳送數據幀大小頭的分塊優化或非阻塞操作。TMemoryBuffer,它允許直接從堆或堆棧內存讀寫所擁有的過程。

4. 協議

Thrift的第二個主要抽象是分離數據結構和傳輸表示

Thrift定義了傳輸數據結構,但是對於協議編碼來說這個結構是模糊的。也就是說,無論數據是XML,還是ASCII,或者二進制格式編碼都不重要。只要數據支持一組固定的操作,使生成的代碼能夠確定地讀寫數據。

4.1 接口

Thrift的協議接口十分重要。它支持兩件事情:

  • 雙向測序信息
  • 基礎類型、容器和結構的編碼
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, type, id = readStructBegin()
				 readStructEnd()
name = 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()

4.2 結構

Thrift結構的設計用來支持流協議編碼。


5. 版本控制

在版本控制和數據定義更改的情況下,Thrift是健壯的。這對於支持對已部署的服務進行分階段的更改非常重要。系統必須能夠支持從日誌文件中讀取舊數據,以及從過期客戶端到新服務器的請求,反之亦然。

版本控制通過field identifiers實現。

當遇到unexpected字段時,可以安全地忽略它並丟棄它。當未找到預期字段時,必須通過某種方式向開發人員發出信號,表明該字段不存在。這是通過定義對象內部的isset結構實現的。


6. RPC實現


參考

[1] Thrift interface description language

[2] Thrift: Scalable Cross-Language Services Implementation

[3] Thrift語法參考

[4] C語言共用體(C語言union用法)詳解

[5] 面向對象程序設計

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章