【轉載】和 Thrift 的一場美麗邂逅

http://www.cnblogs.com/cyfonly/p/6059374.htmlhtml

 

一. 與 Thrift 的初識

也許大多數人接觸 Thrift 是從序列化開始的。每次搜索 「java序列化」 + 「方式」、「對比」 或 「性能」 等關鍵字時,搜索引擎老是會返回一大堆有關各類序列化方式的使用方法或者性能對比的結果給你,而其中一定少不了 Thrift,而且其性能還不錯嘞,至少比那戰鬥力只有1的渣渣 java 原生序列化要強不少(好吧原諒個人小情緒……)。java

然而,我最初接觸 Thrift 倒是從公司的一個項目開始。git

也就在去年的這個時候,我所在事業部發現幾個 UGC 社區的小廣告特別嚴重,Boss 要求全部社區必須接入公司的富媒體監控系統(負責公司全部業務的內容審覈、處罰工做,如下簡稱監控系統),以實現 UGC 內容(包括文本、圖片、音視頻以及用戶頭像、暱稱等UserInfo)的準實時上報與垃圾信息的自動處理(如清理現場、帳號封禁等)。出於對業務服務的最小侵入、功能複用和流程統一等原則的考慮,抽象出介於業務系統和監控系統之間的接入系統,統一負責對數據的接收、上報、重推、搜索、結果查詢以及對監控系統處罰指令的轉發。該業務可簡單抽象成圖 1.1:github

圖 1.1apache

因爲監控系統使用 Thrift 提供服務,所以接入系統與監控系統之間的交互都使用 Thrift 協議。考慮到接入的便捷性,業務系統可使用 Thrift 和 Http 兩種協議與接入系統交互。編程

當時是我一我的負責這個項目,因爲對 Thrift 的認識仍是0,且項目時間短,因此整體上項目是很是趕的,一開始覺得本身難以在規定時間內完成,但想不到 Thrift 開發起來還真的是至關的便捷。系統按時上線了,至今也沒出什麼幺蛾子。後來又經過學習進一步瞭解了 Thrift,深覺得是個必須入手的技能。api

好吧,至此算是和 Thrift 正式結緣了。緩存

 

二. 所謂的 RPC

在瞭解 Thrift 以前,先來簡單科普一下什麼是 RPC(遠程過程調用)。安全

先看下面這個栗子:服務器

1
2
3
4
5
6
7
8
9
public  void  invoke(){
     String param1 =  "my String 1" ;
     String param2 =  "my String 2" ;
     String res = getStr(param1, param2);
     System.out.println( "res="  + res)
}
private  String getStr(String str1, String str2){
     return  str1 + str2;
}

這是一個最簡單不過的本地函數調用代碼,調用方和被調用方都在一個程序內部,屬於進程內調用。

CPU 在執行調用時切換去執行被調用函數,執行完後再切換回來執行後續的代碼。對調用方而言,執行被調用函數時會阻塞(非異步狀況下)直到調用函數執行完畢。過程如圖 2.1

圖 2.1

接下來看個 RPC 調用的栗子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public  void  test(){
     TestQry.Client client = getClient( "192.168.4.222" 7800 5000 );
     String param1 =  "my String 1" ;
     String param2 =  "my String 2" ;
     String res = client.getStr(param1, param2);
     System.out.println( "res="  + res);
}
private  TestQry.Client getClient(String ip,  int  port,  int  timeOut)  throws  Exception{
     TSocket tSocket =  new  TSocket();
     TTransport transport =  new  TFramedTransport(tSocket);
     tTransport.open();
     TProtocol protocol =  new  TBinaryProtocol(tTransport);
     return  new  TestQry.Client(protocol);
}

這是一個進程間調用,調用方和被調用方不在同一個進程(甚至不一樣的服務器或機房)。

進程間調用須要經過網絡來傳輸數據,調用方在執行 RPC 調用時會阻塞(非異步狀況下)直到調用結果返回才繼續執行後續代碼。過程如圖 2.2

圖 2.2

一言以蔽之,RPC 是一種經過網絡從遠程計算機程序上請求服務的方式,它使得開發包括網絡分佈式多程序在內的應用程序更加容易。

 

三. 不只僅是個序列化工具

Thrift 最初是由 Facebook 開發用作系統內各語言之間的 RPC 通訊的一個可擴展且跨語言的軟件框架,它結合了功能強大的軟件堆棧和代碼生成引擎,容許定義一個簡單的定義文件中的數據類型和服務接口,以做爲輸入文件,編譯器生成代碼用來方便地生成RPC客戶端和服務器通訊的無縫跨編程語言。

Thrift 是 IDL 描述性語言的一個具體實現,適用於程序對程序靜態的數據交換,須要先肯定好數據結構。

Thrift 是徹底靜態化的,當數據結構發生變化時,必須從新編輯IDL文件、代碼生成再編譯載入的流程,跟其餘IDL工具相比較能夠視爲是 Thrift 的弱項。Thrift 適用於搭建大型數據交換及存儲的通用工具,在大型系統中的內部數據傳輸上相對於 JSON 和 XML 不管在性能、傳輸大小上有明顯的優點。

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

3.1 堆棧結構

如圖 3.1所示,Thrift 包含一個完整的堆棧結構用於構建客戶端和服務器端。

圖 3.1

其中代碼框架層是根據 Thrift 定義的服務接口描述文件生成的客戶端和服務器端代碼框架,數據讀寫操做層是根據 Thrift 文件生成代碼實現數據的讀寫操做。

3.2 client/server調用流程

首先來看下 Thrift 服務端是如何啓動並提供服務的,以下圖 3.2所示(點擊此處看大圖):

圖 3.2

上圖所示是 HelloServiceServer 啓動的過程,以及服務被客戶端調用時服務器的響應過程。咱們能夠看到,程序調用了 TThreadPoolServer 的 serve() 方法後,server 進入阻塞監聽狀態,其阻塞在 TServerSocket 的 accept()方法上。當接收到來自客戶端的消息後,服務器發起一個新線程處理這個消息請求,原線程再次進入阻塞狀態。在新線程中,服務器經過 TBinaryProtocol 協議讀取消息內容,調用 HelloServiceImpl 的 helloVoid() 方法,並將結果寫入 helloVoid_result 中傳回客戶端。
在服務啓動後,客戶端就開始調用其服務,如圖 3.3所示(點擊此處看大圖):

圖 3.3

上圖展現的是 HelloServiceClient 調用服務的過程,以及接收到服務器端的返回值後處理結果的過程。咱們能夠看到,程序調用了 Hello.Client 的 helloVoid() 方法,在 helloVoid() 方法中,經過 send_helloVoid() 方法發送對服務的調用請求,經過 recv_helloVoid() 方法接收服務處理請求後返回的結果。

3.3 數據類型

上一節咱們已經大體瞭解了 Thrift 的 server 和 client 的工做流程,如今就來說講 Thrift 可定義的數據類型。Thrift 支持幾大類數據結構:基本類型、結構體和異常類型、容器類型、服務類型。

基本類型:

1
2
3
4
5
6
7
bool:布爾值 ( true  or  false ), one  byte
byte :有符號字節
i16: 16 位有符號整型
i32: 32 位有符號整型
i64: 64 位有符號整型
double 64 位浮點型
string:未知編碼或者二進制的字符串

結構體和異常類型:

Thrift 結構體 (struct) 在概念上相似於 C 語言結構體類型,在 java 中 Thrift 結構體將會被轉換成面嚮對象語言的類。struct 的定義以下:

1
2
3
4
5
6
struct UserDemo {
   1 : i32 id;
   2 : string name;
   3 : i32 age =  25 ;
   4 : string phone;
}

struct 具備如下特性: 

1
2
3
4
5
6
7
struct 不能繼承,可是能夠嵌套,不能嵌套本身
其成員都是有明確類型
成員是被正整數編號過的,其中的編號使不能重複的,這個是爲了在傳輸過程當中編碼使用(詳情往下看備註 1
成員分割符能夠是逗號(,)或是分號(;),並且能夠混用,可是爲了清晰期間,建議在定義中只使用一種,好比java學習者能夠就使用逗號(;)
字段會有optional和required之分(詳情往下看備註 2
每一個字段能夠設置默認值
同一文件能夠定義多個struct,也能夠定義在不一樣的文件,進行include引入

備註1:數字標籤做用很是大,隨着項目開發的不斷髮展,也許字段會有變化,可是建議不要輕易修改這些數字標籤,修改以後若是沒有同步客戶端和服務器端會讓一方解析出問題。

備註2:關於 struct 字段類型,規範的 struct 定義中的每一個域均會使用 required 或者 optional 關鍵字進行標識,可是若是不指定則爲無類型,能夠不填充該值,可是在序列化傳輸的時候也會序列化進去。其中 optional 是不填充則不序列化,required 是必須填充也必須序列化。若是 required 標識的域沒有賦值,Thrift 將給予提示;若是 optional 標識的域沒有賦值,該域將不會被序列化傳輸;若是某個 optional 標識域有缺省值而用戶沒有從新賦值,則該域的值一直爲缺省值;若是某個 optional 標識域有缺省值或者用戶已經從新賦值,而不設置它的 __isset 爲 true,也不會被序列化傳輸。
異常在語法和功能上至關於結構體,差異是異常使用關鍵字 exception 而不是 struct 聲明。它在語義上不一樣於結構體:當定義一個 RPC 服務時,開發者可能須要聲明一個遠程方法拋出一個異常。

容器類型

Thrift 容器與目前流行編程語言的容器類型相對應,有3種可用容器類型:

1
2
3
list<t>:元素類型爲t的有序表,允許元素重複。對應java的ArrayList
set<t>:元素類型爲t的無序表,不允許元素重複。對應java的HashSet
map<t,t>:鍵類型爲t,值類型爲t的kv對,鍵不允許重複。對對應Java的HashMap

其中容器中元素類型能夠是除了 service 外的任何合法 Thrift 類型(包括結構體和異常)。

服務類型

服務的定義方法在語義上等同於面嚮對象語言中的接口。Thrift 編譯器會產生執行這些接口的 client 和 server 存根(詳情下一節會具體描述)。下面咱們就舉個簡單的例子解釋 service 如何定義:

1
2
3
4
5
6
7
8
9
10
11
service QuerySrv{
   /**
  * 本方法實現根據名字和年齡來找到對應的用戶信息
  */
  UserDemo qryUser( 1 :string name,  2 :i32 age);
 
   /**
  * 本方法實現根據id找到對應用戶的手機號碼
  */
  string queryPhone( 1 :i32 id);
}

在上面的例子中咱們定義了一個 service 類型的結構,裏面包含兩個方法的定義。

在定義 services 的時候,咱們還須要瞭解一下規則:

1
2
3
4
5
6
繼承類必須實現這些方法
參數能夠是基本類型或者結構體
全部的參數都是 const 類型,不能做爲返回值
返回值能夠是 void (oneway的返回值必定是 void
服務支持繼承,一個service可以使用 extends 關鍵字繼承另外一個service
服務不支持重載

除上面所提到的四大數據類型外,Thrift 還支持枚舉類型(enum)和常量類型(const)。

命名空間

Thrift 中的命名空間相似於 java 中的 package,它們提供了一種組織(隔離)代碼的簡便方式。名字空間也能夠用於解決類型定義中的名字衝突。

3.4 傳輸體系

傳輸協議

Thrift 支持多種傳輸協議,用戶能夠根據實際需求選擇合適的類型。Thrift 傳輸協議上整體可劃分爲文本 (text) 和二進制 (binary) 傳輸協議兩大類,通常在生產環境中使用二進制類型的傳輸協議爲多數(相對於文本和 JSON 具備更高的傳輸效率)。經常使用的協議包含:

1
2
3
4
TBinaryProtocol:是Thrift的默認協議,使用二進制編碼格式進行數據傳輸,基本上直接發送原始數據
TCompactProtocol:壓縮的、密集的數據傳輸協議,基於Variable-length quantity的zigzag 編碼格式
TJSONProtocol:以JSON (JavaScript Object Notation)數據編碼協議進行數據傳輸
TDebugProtocol:經常用以編碼人員測試,以文本的形式展示方便閱讀

關於以上幾種類型的傳輸協議,若是想更深刻更具體的瞭解其實現及工做原理,能夠參考站外相關文章《thrift源碼研究》。

傳輸方式

與傳輸協議同樣,Thrift 也支持幾種不一樣的傳輸方式。
1. TSocket:阻塞型 socket,用於客戶端,採用系統函數 read 和 write 進行讀寫數據。
2. TServerSocket:非阻塞型 socket,用於服務器端,accecpt 到的 socket 類型都是 TSocket(即阻塞型 socket)。
3. TBufferedTransport 和 TFramedTransport 都是有緩存的,均繼承TBufferBase,調用下一層 TTransport 類進行讀寫操做嗎,結構極爲類似。其中 TFramedTransport 以幀爲傳輸單位,幀結構爲:4個字節(int32_t)+傳輸字節串,頭4個字節是存儲後面字節串的長度,該字節串纔是正確須要傳輸的數據,所以 TFramedTransport 每傳一幀要比 TBufferedTransport 和 TSocket 多傳4個字節。
4. TMemoryBuffer 繼承 TBufferBase,用於程序內部通訊用,不涉及任何網絡I/O,可用於三種模式:(1)OBSERVE模式,不可寫數據到緩存;(2)TAKE_OWNERSHIP模式,需負責釋放緩存;(3)COPY模式,拷貝外面的內存塊到TMemoryBuffer。
5. TFileTransport 直接繼承 TTransport,用於寫數據到文件。對事件的形式寫數據,主線程負責將事件入列,寫線程將事件入列,並將事件裏的數據寫入磁盤。這裏面用到了兩個隊列,類型爲 TFileTransportBuffer,一個用於主線程寫事件,另外一個用於寫線程讀事件,這就避免了線程競爭。在讀完隊列事件後,就會進行隊列交換,因爲由兩個指針指向這兩個隊列,交換隻要交換指針便可。它還支持以 chunk(塊)的形式寫數據到文件。
6. TFDTransport 是很是簡單地寫數據到文件和從文件讀數據,它的 write 和 read 函數都是直接調用系統函數 write 和 read 進行寫和讀文件。
7. TSimpleFileTransport 直接繼承 TFDTransport,沒有添加任何成員函數和成員變量,不一樣的是構造函數的參數和在 TSimpleFileTransport 構造函數裏對父類進行了初始化(打開指定文件並將fd傳給父類和設置父類的close_policy爲CLOSE_ON_DESTROY)。
8. TZlibTransport 跟 TBufferedTransport 和 TFramedTransport同樣,調用下一層 TTransport 類進行讀寫操做。它採用<zlib.h>提供的 zlib 壓縮和解壓縮庫函數來進行壓解縮,寫時先壓縮再調用底層 TTransport 類發送數據,讀時先調用 TTransport 類接收數據再進行解壓,最後供上層處理。
9. TSSLSocket 繼承 TSocket,阻塞型 socket,用於客戶端。採用 openssl 的接口進行讀寫數據。checkHandshake()函數調用 SSL_set_fd 將 fd 和 ssl 綁定在一塊兒,以後就能夠經過 ssl 的 SSL_read和SSL_write 接口進行讀寫網絡數據。
10. TSSLServerSocket 繼承 TServerSocket,非阻塞型 socket, 用於服務器端。accecpt 到的 socket 類型都是 TSSLSocket 類型。
11. THttpClient 和 THttpServer 是基於 Http1.1 協議的繼承 Transport 類型,均繼承 THttpTransport,其中 THttpClient 用於客戶端,THttpServer 用於服務器端。二者都調用下一層 TTransport 類進行讀寫操做,均用到TMemoryBuffer 做爲讀寫緩存,只有調用 flush() 函數纔會將真正調用網絡 I/O 接口發送數據。

TTransport 是全部 Transport 類的父類,爲上層提供了統一的接口並且經過 TTransport 便可訪問各個子類不一樣實現,相似多態。

 

四. 選擇 java server 的藝術

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

其中,protocol 定義了消息是怎樣序列化的;transport 定義了消息是怎樣在客戶端和服務器端之間通訊的;server 用於從 transport 接收序列化的消息,根據 protocol 反序列化之,調用用戶定義的消息處理器,並序列化消息處理器的響應,而後再將它們寫回 transport。

Thrift 模塊化的結構使得它能提供各類 server 實現。下面列出了 Java 中可用的 server 實現:

1
2
3
4
5
TSimpleServer
TNonblockingServer
THsHaServer
TThreadedSelectorServer
TThreadPoolServer

有多個選擇當然是很好的,但若是不清楚箇中差異則是個災難。因此接下來就談談這些 server 之間的區別,並經過一些簡單的測試以說明它們的性能特色。

TSimpleServer

TSimplerServer 接受一個鏈接,處理鏈接請求,直到客戶端關閉了鏈接,它纔回去接受一個新的鏈接。正由於它只在一個單獨的線程中以阻塞 I/O 的方式完成這些工做,因此它只能服務一個客戶端鏈接,其餘全部客戶端在被服務器端接受以前都只能等待。

TSimpleServer 主要用於測試目的,不要在生產環境中使用它!

TNonblockingServer vs. THsHaServer

TNonblockingServer 使用非阻塞的 I/O 解決了 TSimpleServer 一個客戶端阻塞其餘全部客戶端的問題。它使用了 java.nio.channels.Selector,經過調用 select(),它使得你阻塞在多個鏈接上,而不是阻塞在單一的鏈接上。當一或多個鏈接準備好被接受/讀/寫時,select() 調用便會返回。TNonblockingServer 處理這些鏈接的時候,要麼接受它,要麼從它那讀數據,要麼把數據寫到它那裏,而後再次調用 select() 來等待下一個可用的鏈接。通用這種方式,server 可同時服務多個客戶端,而不會出現一個客戶端把其餘客戶端所有「餓死」的狀況。
然而,還有個棘手的問題:全部消息是被調用 select() 方法的同一個線程處理的。假設有10個客戶端,處理每條消息所需時間爲100毫秒,那麼,latency 和吞吐量分別是多少?當一條消息被處理的時候,其餘9個客戶端就等着被 select,因此客戶端須要等待1秒鐘才能從服務器端獲得迴應,吞吐量就是10個請求/秒。若是能夠同時處理多條消息的話,會很不錯吧?
所以,THsHaServer(半同步/半異步的 server)就應運而生了。它使用一個單獨的線程來處理網絡I/O,一個獨立的 worker 線程池來處理消息。這樣,只要有空閒的 worker 線程,消息就會被當即處理,所以多條消息能被並行處理。用上面的例子來講,如今的 latency 就是100毫秒,而吞吐量就是100個請求/秒。
爲了演示作了一個測試,有10客戶端和一個修改過的消息處理器——它的功能僅僅是在返回以前簡單地 sleep 100 毫秒。使用的是有10個 worker 線程的 THsHaServer。消息處理器的代碼看上去就像下面這樣:

1
2
3
4
5
6
7
public  ResponseCode sleep()  throws  TException{  
     try  {
         Thread.sleep( 100 );
     catch  (Exception ex) {
     }
     return  ResponseCode.Success;
}

 特別申明,本章節的測試結果摘自站外文章,詳情請看文末連接

圖 4.1

 

圖 4.2

結果正如咱們想像的那樣,THsHaServer 可以並行處理全部請求,而 TNonblockingServer 只能一次處理一個請求。

THsHaServer vs. TThreadedSelectorServer

Thrift 0.8 引入了另外一種 server 實現,即 TThreadedSelectorServer。它與 THsHaServer 的主要區別在於,TThreadedSelectorServer 容許你用多個線程來處理網絡 I/O。它維護了兩個線程池,一個用來處理網絡 I/O,另外一個用來進行請求的處理。當網絡 I/O 是瓶頸的時候,TThreadedSelectorServer 比 THsHaServer 的表現要好。爲了展示它們的區別進行一個測試,令其消息處理器在不作任何工做的狀況下當即返回,以衡量在不一樣客戶端數量的狀況下的平均 latency 和吞吐量。對 THsHaServer,使用32個 worker 線程;對 TThreadedSelectorServer,使用16個 worker 線程和16個 selector 線程。

 

圖 4.3

 

圖 4.4

結果顯示,TThreadedSelectorServer 比 THsHaServer 的吞吐量高得多,而且維持在一個更低的 latency 上。

TThreadedSelectorServer vs. TThreadPoolServer

最後,還剩下 TThreadPoolServer。TThreadPoolServer 與其餘三種 server 不一樣的是:

1
2
3
4
有一個專用的線程用來接受鏈接
一旦接受了一個鏈接,它就會被放入 ThreadPoolExecutor 中的一個 worker 線程裏處理。
worker 線程被綁定到特定的客戶端鏈接上,直到它關閉。一旦鏈接關閉,該 worker 線程就又回到了線程池中。
你能夠配置線程池的最小、最大線程數,默認值分別是 5 (最小)和 Integer.MAX_VALUE(最大)。

這意味着,若是有1萬個併發的客戶端鏈接,你就須要運行1萬個線程。因此它對系統資源的消耗不像其餘類型的 server 同樣那麼「友好」。此外,若是客戶端數量超過了線程池中的最大線程數,在有一個 worker 線程可用以前,請求將被一直阻塞在那裏。

咱們已經說過,TThreadPoolServer 的表現很是優異。在我正在使用的計算機上,它能夠支持1萬個併發鏈接而沒有任何問題。若是你提早知道了將要鏈接到你服務器上的客戶端數量,而且你不介意運行大量線程的話,TThreadPoolServer 對你多是個很好的選擇。

 

圖 4.5

 

圖 4.6

我想你能夠從上面的描述能夠幫你作出決定:哪種 Thrift server 適合你。

TThreadedSelectorServer 對大多數案例來講都是個安全之選。若是你的系統資源容許運行大量併發線程的話,建議你使用 TThreadPoolServer。

 

五. Let's do it

上面已經介紹了不少理論知識了,不少同窗仍是不知道如何使用呢!好吧,是時候表演真正的技術了(LOL...)。

所謂大道至簡,講的就是最簡單的代碼就是最優美的代碼,只要功能強悍,最簡單的代碼也掩蓋不了它出衆的氣質。下面就來給大夥兒講講如何使用 Thrift 強大的代碼生成引擎來生成 java 代碼,並經過詳細的步驟實現 Thrift Server 和 Client 調用。

備註:本文實現基於 Thrift-0.9.2 版本,實現過程忽略日誌處理等非關鍵代碼。

步驟一:首先從官網中下載對應的 Window 平臺編譯器(點擊下載 thrift-0.9.2.exe)。使用 IDL 描述語言創建 .thrift 文件。本文提供一個實現簡單功能的測試案例,以下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* 文件名爲TestQry.thrift
* 實現功能:建立一個查詢結果struct和一個服務接口service
* 基於:thrift-0.9.2
**/
namespace java com.thrift
struct QryResult {
         /**
         *返回碼, 1成功,0失敗
         */
         1 :i32 code;
         /**
         *響應信息
         */
         2 :string msg;
}
service TestQry{
         /**
         * 測試查詢接口,當qryCode值爲1時返回"成功"的響應信息,qryCode值爲其餘值時返回"失敗"的響應信息
         * @param qryCode測試參數
         */
         QryResult qryTest( 1 :i32 qryCode)
}

步驟二:將上述 TestQry.thrift 文件與 thrift-0.9.2.exe 放在同一目錄,以下:

 

圖 5.1

在命令提示符 CMD 中進入文件目錄所在目錄,執行代碼生成命令:

1
thrift- 0.9 . 2 .exe -r -gen java TestQry.thrift

執行以後,咱們在文件夾中能夠看到生成的 java 代碼

圖 5.2

步驟三:接下來咱們新建 Maven Project(注意:JDK 版本1.5及以上),將上一步驟生成的代碼拷貝到項目,並在 pom.xml 中加載 Thrift 的依賴,以下

1
2
3
4
5
6
7
8
9
10
11
12
<dependencies>
  <dependency>
    <groupId>org.apache.thrift</groupId>
    <artifactId>libthrift</artifactId>
    <version> 0.9 . 2 </version>
  </dependency>
  <dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version> 1.7 . 13 </version>
  </dependency>
</dependencies>

步驟四:建立 QueryImp.java 實現 TestQry.Iface 接口,關鍵代碼以下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public  class  QueryImp  implements  TestQry.Iface{
   @Override
   public  QryResult qryTest( int  qryCode)  throws  TException {
    QryResult result =  new  QryResult();
     if (qryCode== 1 ){
      result.code =  1 ;
      result.msg =  "success" ;
    } else {
      result.code =  0 ;
      result.msg =  "fail" ;
    }
     return  result;
  }
}

步驟五:建立 ThriftServerDemo.java 實現服務端(本例採用非阻塞I/O,二進制傳輸協議),關鍵代碼以下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public  class  ThriftServerDemo {
   private  final  static  int  DEFAULT_PORT =  30001 ;
   private  static  TServer server =  null ;
   public  static  void  main(String[] args){
     try  {
      TNonblockingServerSocket socket =  new  TNonblockingServerSocket(DEFAULT_PORT);
      TestQry.Processor processor =  new  TestQry.Processor( new  QueryImp());
      TNonblockingServer.Args arg =  new  TNonblockingServer.Args(socket);
      arg.protocolFactory( new  TBinaryProtocol.Factory());
      arg.transportFactory( new  TFramedTransport.Factory());
      arg.processorFactory( new  TProcessorFactory(processor));
      server =  new  TNonblockingServer (arg);
      server.serve();
    }  catch  (TTransportException e) {
      e.printStackTrace();
    }
  }
}

步驟六:建立 ThriftClientDemo.java 實現客戶端,關鍵代碼以下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public  class  ThriftClientDemo {
   private  final  static  int  DEFAULT_QRY_CODE =  1 ;
   public  static  void  main(String[] args){
     try  {
      TTransport tTransport = getTTransport();
      TProtocol protocol =  new  TBinaryProtocol(tTransport);
      TestQry.Client client =  new  TestQry.Client(protocol);
      QryResult result = client.qryTest(DEFAULT_QRY_CODE);
      System.out.println( "code=" +result.code+ " msg=" +result.msg);
    } catch  (Exception e) {
      e.printStackTrace();
    }
  }
   private  static  TTransport getTTransport()  throws  Exception{
     try {
      TTransport tTransport = getTTransport( "127.0.0.1" 30001 5000 );
       if (!tTransport.isOpen()){
        tTransport.open();
      }
       return  tTransport;
    } catch (Exception e){
      e.printStackTrace();
    }
     return  null ;
  }
   private  static  TTransport getTTransport(String host,  int  port,  int  timeout) {
     final  TSocket tSocket =  new  TSocket(host, port, timeout);
     final  TTransport transport =  new  TFramedTransport(tSocket);
     return  transport;
  }
}

好的,全部準備工做都已經作好了,接下來咱們就來進行 Client 和 Server 的通訊。先運行 ThriftServerDemo 啓動 Server,而後運行 ThriftClientDemo.java 建立 Client 進行調用,當 qryCode = 1 時,結果以下

1
code= 1  msg=success

當 qryCode = 0 時,結果以下

1
code= 0  msg=fail

附上項目的代碼結構:

圖 5.3

你看我沒騙你吧,是否是 so easy ?

固然在項目中使用時絕對沒有這麼簡單,但上面的栗子已經足夠用來指導你進行 Thrift 服務端和客戶端開發了。

 

六. 路漫漫其修遠兮

到目前爲止你所看到的都不是源碼分析層面的知識,本文的目的也並不是在此。掌握任何一門技術,都應該先從其宏觀體系和架構開始瞭解,而後再去深刻研究其中的細節和精髓。若是一開始就追求所謂的源碼解析等「高大上」的東西,反而會由於擁有了一顆大樹而失去了欣賞整個森林的美妙。

固然,筆者下一步的計劃就是深刻研究 Thrift 的實現,但願能和你們一塊兒交流共同進步。

 

參考文章

[1] 《Apache Thrift - 可伸縮的跨語言服務開發框架

[2] 《Thrift Java Servers Compared

相關文章
相關標籤/搜索