天天看點

初識ThriftThrift

  • Thrift
    • Thrift接口描述檔案
      • 注釋
      • 基礎類型
      • 容器
      • 結構體
      • 服務
      • 文法Example
    • Thrift的三個元件
      • protocol
      • transport
      • server
    • Thrift工具的使用
    • Python Examlple

本文為閱讀總結,如果時間充裕,建議閱讀參考的文章,Python Example為練習demo。

參考網址

  • http://thrift.apache.org/ 官網
  • http://blog.jobbole.com/107828/ 此處有Java Example
  • https://www.ibm.com/developerworks/cn/java/j-lo-apachethrift/ Java Example和環境搭建
  • http://dongxicheng.org/search-engine/thrift-guide/
  • http://www.cnblogs.com/cyfonly/p/6059374.html 最佳入門文章
  • http://blog.csdn.net/whycold/article/category/1251519 源碼研究

Thrift

Thrift(Apache thrift)是一款軟體開發RPC(Remote Procedure Call Protocol,遠端過程調用協定)架構,可以高效地實作跨語言的RPC服務。

Thrift 是IDL描述性語言的一個具體實作,适用于程式對程式靜态的資料交換,但是需要提前确定好資料結構。

Thrift 是完全靜态的,當資料結構發生變化時,必須重新編輯IDL檔案、代碼生成再編譯載入的流程,這也是Thrift的弱項。Thrift是用于搭建大型資料交換及存儲的通用工具,在大型系統中的内部資料傳輸上相對于JSON 和XML 無論在性能、傳輸大小上都有明顯的優勢。

Thrift 不僅僅是個高效的序列化工具,它是一個完整的 RPC 架構體系!

Thrift接口描述檔案

在編寫接口檔案時,需要對要傳輸的資料設定資料類型。因為Thrift支援衆多開發語言,是以Thrift提供了一套自己的資料類型編寫規範,隻有用這套獨立于任何語言的類型規範來編寫接口檔案,Thrift才能把它轉換成指定的那種開發語言。

Thrift中的類型包括基礎類型、結構、容器、異常、服務等。

注釋

單行注釋,#

多行注釋,

基礎類型

bool     布爾類型(true或false)
byte     位有符号整數
i16      位有符号整數
i32      位有符号整數
i64      位有符号整數
double   位浮點數
string   文本字元串,使用utf-編碼
binary   二進制資料類型(位元組數組)
           

容器

Thrift提供了三種最常用的容器

  1. list容器:一個元素可重複的有序清單。 會轉換成C++中的vector,Java中的ArrayList,Python中的list等;
  2. set容器:一個元素不可重複的無序集合。會轉換成C++中的set,Java中的HashSet、Python中的set等;
  3. map容器:一個含有多個key:value鍵值對的結構。會轉換成C++中的map,Java中的HashMap,PHP中的關聯數組,Python中的dict等。
對于上述三種容器,其元素的類型原則上可以是任何一種Thrift類型。但是map的key類型需要是基礎類型,因為很多開發語言并不支援map的key類型為複雜資料類型,如Python中的鍵值必須為不可變對象。

結構體

在形式上和C/C++中的結構體類型非常相似,Thrift接口檔案中的結構體類型,會被轉換成一個獨立的類(class)。類的屬性便是結構體中的各個類型,而類的方法便是對這些類型進行處理的相關函數。

struct UserGradeInfo {
: required string UserName = "Anonymous";
: required i16 UserGrade = ;
}
           

結構體中每一個變量都有一個正整數辨別符,這個辨別符并不要求連續,但是一旦定義,不建議再進行修改。

另外,每個變量前都會有required或optional的限定,前者表示是必填域,後者則表示是可選域。域是可以有預設值的。

  1. 如果一個變量設定了required,但是在實際構造結構體時又沒有給這個變量指派,那麼thrift會認為這是一個異常;
  2. 如果一個變量設定為optional,且在構造結構體時沒有給這個變量指派,thrift不會抛出異常。

服務

服務與面向對象中的接口類似。Thrift編譯工具會根據服務的定義來産生相應的方法和函數。

每個服務,都包括了若幹個函數,每個函數包括了若幹參數和一個傳回值(傳回值可以是void)。

文法Example

# 例子 - thrift接口描述檔案
# 編寫這個檔案是為了教會你如何寫thrift接口描述檔案。
# 第一個你應該掌握的知識點就是.thrift檔案
# 支援shell的注釋方式,那就是用#符号。

/**
*常用資料類型,如下所示:
* bool 布爾型,1個位元組
* byte 有符号整數,1個位元組
* i16 有符号16位整型
* i32 有符号32位整型
* i64 有符号64位整型
* double 64位浮點數值
* string 字元串類型
* binary 二進制資料類型(位元組數組)
* list 單類型有序清單,允許有重複元素
* set 單類型無需集合,不允許有重複元素
* map<t1,t2> Map型(key:value)
*
* 你發現了麼,.thrift檔案還支援C語言的多行注釋形式。
*/

// 不賣關子了,其實我們還支援C語言的單行注釋形式呢 ^_^

/**
* .thrift檔案可以引用其他.thrift檔案,這樣就可以友善地把一些公共結構和服務囊括進來。
* 在引用其他.thrift檔案時,既可以直接引用目前檔案夾下的檔案,也可以引用其他路徑下的
* 檔案,但後者需要在thrift編譯工具編譯時加上-I選項來設定路徑。
*
* 如果希望通路被包含的.thrift檔案中的内容,則需要使用.thrift檔案的檔案名作為字首,
* 比如shared.SharedObject。我們在本例中引用了檔案shared.thrift。
*/
include "shared.thrift"

/**
* Thrift支援對.thrift檔案中的類型設定namespace,這樣可以有效避免名字沖突。
* 這種機制在C++中也叫做namespace,而在Java中叫做Package。
* thrift支援針對不同的語言設定不同的namespace,比如下面的例子。
* thrift會在生成不同語言代碼時,進行相應的設定。
*/
namespace cpp tutorial
namespace go tutorial
namespace java tutorial
namespace php tutorial
namespace perl tutorial

/**
* thrift還可以使用typedef來給類型起别名。
*/
typedef i32 MyInteger

/**
* Thrift也支援定義常量。
* 對于結構複雜的常量,支援使用JSON形式來表示。
*/
const i32 MY_NUM = 
const map<string,string> MY_MAP = {'hello':'world', 'goodnight':'moon'}

/**
* 你還可以定義枚舉類型, 其被指定為32位整型。域的值是可以自定義的,而且
* 當不提供域的值時,預設會從1開始編号并遞增。
*/
enum Operation {
ADD = ,
SUBTRACT = ,
MULTIPLY = ,
DIVIDE = 
}

/**
* 結構體則是一個複雜的資料類型。它由多個域組成,每個域會對應一個整數辨別符,
* 每一行的格式為:一個冒号,一個類型,一個域名稱和一個(非必填的)預設值。
*
* 每個域都可以設定為optional或required來表示是否為必填域,以便thrift決定是否
* 在資料傳輸時要包含這個域。不指定時,預設為required。
*/
struct Work {
: i32 num1 = ,
: i32 num2,
: Operation op,
: optional string comment,
}

/**
* 在文法上,異常的定義方式和結構體是完全一樣的。在發生問題時,可以抛出異常。
*/
exception InvalidOperation {
: i32 what,
: string why
}

/**
* 啊哈,我們現在到了最Cool的環節,即定義服務。
* (一個服務可以使用extends來繼承另一個服務。)
*/
service Calculator extends shared.SharedService {

/**
* 服務中方法的定義非常類似于C語言的文法。它會包括一個傳回值,
* 一個參數清單以及一個可以抛出的異常清單(可選)
* 可以提前告訴大家的是,定義參數清單的方法、定義異常清單的方法,
* 和定義結構體的方法都是相似的,可以從下面的例子中看出。
* 除了最後一個方法,其他的方法最後都要有一個逗号,大家可不要忽略這個細節。
*/

void ping(),

i32 add(:i32 num1, :i32 num2),

/**
* 在異常清單前,需要加throws關鍵字。
*/
i32 calculate(:i32 logid, :Work w) throws (:InvalidOperation ouch),

/**
* 如下的這個方法有一個oneway修飾符
* 表示這個方法在調用後會立即傳回,不會等待遠端的回複。
* 要注意的是,oneway隻能修飾void傳回類型。
* oneway在英語裡就是“單向”的意思,還是很形象滴。
*/
oneway void zip()

}

/**
* 在你使用thrift編譯工具編譯此檔案後,
* 會在目前目錄産生一個“gen-<你選擇的開發語言>”
* 檔案夾,比如你選擇的是C++語言,則會産生gen-cpp檔案夾,
* 裡面放着的便是thrift幫你生成好的代碼,
* 代碼并不那麼晦澀,你可以打開看一看。
*/
           

Thrift的三個元件

Thrift 包含了三個主要的元件:protocol,transport 和 server。

  • protocol 定義了消息的序列化
  • transport 定義了消息是怎樣在用戶端和伺服器端之間通信的
  • server 用于從 transport 接收序列化的消息,根據 protocol 反序列化之,調用使用者定義的消息處理器,并序列化消息處理器的響應,然後再将它們寫回 transport

protocol

Thrift 可以讓使用者選擇用戶端與服務端之間傳輸通信協定的類别,在傳輸協定上總體劃分為文本 (text) 和二進制 (binary) 傳輸協定。

為節約帶寬,提高傳輸效率,一般情況下使用二進制類型的傳輸協定為多數。常用協定有以下幾種

TBinaryProtocol   二進制編碼格式進行資料傳輸
TCompactProtocol  高效率的,密集的二進制編碼格式進行資料傳輸
TJSONProtocol     使用 JSON 的資料編碼協定進行資料傳輸
TDebugProtocol    使用易懂的可讀的文本格式,以便于debug
           
注意,用戶端與服務端的通信協定要一緻

transport

常見的幾種transport

TBufferedTransport 有緩存,繼承了TBufferBase,調用下一層TTransport 類進行讀寫操作
TFramedTransport   有緩存,繼承了TBufferBase, 以幀為傳輸機關
TZlibTransport     調用下一層TTransport 類進行讀寫操作,不同的是對資料進行了壓縮,對發生的消息壓縮,對接收的消息先解壓縮
TMemoryBuffer      繼承了TBufferBase,用于程式内部通信用,不涉及任何網絡I/O
TFileTransport     繼承了TTransport,用于寫資料到檔案,兩個線程,一個讀,一個寫
TFDTransport       簡單地寫資料到檔案和從檔案讀資料,它的write和read直接調用系統函數write,read
           
TTransport 是所有 Transport 類的父類,為上層提供了統一的接口而且通過 TTransport 即可通路各個子類不同實作

server

常見的幾種erver

TSocket                阻塞型socket,用于用戶端, 采用系統函數read和write進行讀寫資料
TSSLSocket             繼承了TSocket,阻塞型socket,用于用戶端.采用 openssl 的接口進行讀寫資料

TServerSocket          非阻塞型socket,用于伺服器端
TSimpleServer          單線程伺服器端使用标準的阻塞式 I/O
TThreadPoolServer      多線程伺服器端使用标準的阻塞式 I/O
TNonblockingServer     多線程伺服器端使用非阻塞式 I/O
TSSLServerSocket       用于服務端,非阻塞型socket    
           

Thrift工具的使用

thrift -gen ${開發語言} ${thrift接口描述檔案}
# 運作了上述指令之後,就會在目前檔案夾下生成一個以“gen-${開發語言}”命名的檔案夾,
# 裡面便是自動生成的代碼。
           

Python Examlple

下載下傳連結http://download.csdn.net/detail/lis_12/9866815

  • Example 1
    1. Thrift檔案 Hello.thrift
      service HelloWorld {
      string ping(),
      string say(:string msg)
      }
                 
    2. 利用Thrift工具生成Python代碼

      在生成的gen-py目錄下建立server.py和client.py

      python還需要安裝thrift,輸入pip install thrift即可

      thrift在windows下和linux下生成的檔案結構可能不同,注意下

    3. server.py
      #!/usr/bin/python
      
      
      # -*- coding: utf-8 -*-
      
      import socket
      import sys
      sys.path.append('./gen-py')
      
      from thrift.transport import TSocket
      from thrift.transport import TTransport
      from thrift.protocol import TBinaryProtocol
      from thrift.server import TServer
      from Hello import HelloWorld
      
      from Hello.ttypes import *
      
      class HelloWorldHandler(object):
       """實作HellowWorld.Iface的接口"""
       def say(self, msg):
           print "server receive say msg = %s"%msg
           ret = "server apply " + msg
           return ret
       def ping(self):
           print "server receive ping"
           return "server reply ping"
      
      def startServer():
       """開啟服務"""
       # 服務端處理執行個體
       handler = HelloWorldHandler()
       # 關聯處理器與HelloWorld服務的實作
       processor = HelloWorld.Processor(handler)
       # 設定服務端端口
       transport = TSocket.TServerSocket("localhost", )
       # 傳輸層
       tfactory = TTransport.TBufferedTransportFactory()
       # 資料傳輸協定
       pfactory = TBinaryProtocol.TBinaryProtocolFactory()
       # 服務執行個體
       server = TServer.TSimpleServer(processor, transport, tfactory, pfactory)
       print "Starting thrift server in python..."
       server.serve()
       print "done!"
      
      if __name__ == "__main__":
       startServer()
                 
    4. client.py
      #!/usr/bin/python
      
      
      # -*- coding: utf-8 -*-
      
      import sys
      sys.path.append('./gen-py')
      from thrift import Thrift
      from thrift.transport import TSocket
      from thrift.transport import TTransport
      from thrift.protocol import TBinaryProtocol
      
      from Hello import HelloWorld
      
      class Client(HelloWorld.Client):
       """docstring for Client"""
       def __init__(self, protocol):
           super(Client, self).__init__(protocol)
      
       def say(self, msg):
           msg = "client %s"%msg
           return super(Client, self).say(msg)
      
       def ping(self):
           return super(Client, self).ping()
      
      def main():
       try:
           # 建立端口
           transport = TSocket.TSocket('localhost', )
           # 傳輸層
           transport = TTransport.TBufferedTransport(transport)
           # 資料傳輸協定
           protocol = TBinaryProtocol.TBinaryProtocol(transport)
           # 用戶端的執行個體
           client = Client(protocol)
           # 打開端口
           transport.open()
           print "client ping, server reply = " + client.ping()
           print "client say, server reply = " + client.say("Hello!")
           # 關閉端口
           transport.close()
       except Thrift.TException, ex:
           print "%s" % (ex.message)
      
      if __name__ == "__main__":
       main()
                 
    command
    python server.py
               
    python client.py
               
    初識ThriftThrift
  • Example2

    傳回目前時間,如果了解了Example1,事例2可忽略

    1. thrift檔案,timepacket.thrift
      service requestTime {
      string curTime()
      }
                 
    2. 将gen-py重命名為Time
    3. server.py
      #!/usr/bin/python
      
      
      # -*- coding: utf-8 -*-
      
      import socket
      import sys
      import time
      sys.path.append('./Time')
      
      from thrift.transport import TSocket
      from thrift.transport import TTransport
      from thrift.protocol import TBinaryProtocol
      from thrift.server import TServer
      from timepacket import requestTime
      from timepacket.ttypes import *
      
      class handlerRequest(object):
       def curTime(self):
           print "recv request"
           return time.ctime()
      
      def startServer():
       """開啟服務"""
       # 服務端處理執行個體
       handler = handlerRequest()
       # 關聯處理器與HelloWorld服務的實作
       processor = requestTime.Processor(handler)
       # 設定服務端端口
       transport = TSocket.TServerSocket("localhost", )
       # 傳輸層
       tfactory = TTransport.TBufferedTransportFactory()
       # 資料傳輸協定
       pfactory = TBinaryProtocol.TBinaryProtocolFactory()
       # 服務執行個體
       server = TServer.TSimpleServer(processor, transport, tfactory, pfactory)
       print "Starting thrift server in python..."
       server.serve()
       print "done!"
      
      if __name__ == "__main__":
       startServer()
                 
    4. client.py
      #!/usr/bin/python
      
      
      # -*- coding: utf-8 -*-
      
      import sys
      sys.path.append('./time')
      from thrift import Thrift
      from thrift.transport import TSocket
      from thrift.transport import TTransport
      from thrift.protocol import TBinaryProtocol
      from timepacket import requestTime
      from timepacket.ttypes import *
      
      class Client(requestTime.Client):
       """docstring for Client"""
       def __init__(self, protocol):
           super(Client, self).__init__(protocol)
      
       def curTime(self):
           return super(Client, self).curTime()
      
      def main():
       try:
           # 建立端口
           transport = TSocket.TSocket('localhost', )
           # 傳輸層
           transport = TTransport.TBufferedTransport(transport)
           # 資料傳輸協定
           protocol = TBinaryProtocol.TBinaryProtocol(transport)
           # 用戶端的處理
           client = Client(protocol)
           # 打開端口
           transport.open()
           print "client requestTime, server reply = " + client.curTime()
           # 關閉端口
           transport.close()
       except Thrift.TException, ex:
           print "%s" % (ex.message)
      
      if __name__ == "__main__":
       main()