程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> JAVA綜合教程 >> 和 Thrift 的一場美麗邂逅,thrift一場邂逅

和 Thrift 的一場美麗邂逅,thrift一場邂逅

編輯:JAVA綜合教程

和 Thrift 的一場美麗邂逅,thrift一場邂逅


 一. 與 Thrift 的初識

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

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

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

圖 1.1

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

當時是我一個人負責這個項目,由於對 Thrift 的認識還是0,且項目時間短,所以總體上項目是非常趕的,一開始以為自己難以在規定時間內完成,但想不到 Thrift 開發起來還真的是相當的便捷。系統按時上線了,至今也沒出什麼幺蛾子。後來又通過學習進一步了解了 Thrift,深以為是個必須入手的技能。

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

二. 所謂的 RPC

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

先看下面這個栗子:

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 調用的栗子:

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 支持幾大類數據結構:基本類型、結構體和異常類型、容器類型、服務類型。
基本類型:

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

結構體和異常類型:

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

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

struct 具有以下特性:

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

備注1:數字標簽作用非常大,隨著項目開發的不斷發展,也許字段會有變化,但是建議不要輕易修改這些數字標簽,修改之後如果沒有同步客戶端和服務器端會讓一方解析出問題。

備注2:關於 struct 字段類型,規范的 struct 定義中的每個域均會使用 required 或者 optional 關鍵字進行標識,但是如果不指定則為無類型,可以不填充該值,但是在序列化傳輸的時候也會序列化進去。其中 optional 是不填充則不序列化,required 是必須填充也必須序列化。如果 required 標識的域沒有賦值,Thrift 將給予提示;如果 optional 標識的域沒有賦值,該域將不會被序列化傳輸;如果某個 optional 標識域有缺省值而用戶沒有重新賦值,則該域的值一直為缺省值;如果某個 optional 標識域有缺省值或者用戶已經重新賦值,而不設置它的 __isset 為 true,也不會被序列化傳輸。
異常在語法和功能上相當於結構體,差別是異常使用關鍵字 exception 而不是 struct 聲明。它在語義上不同於結構體:當定義一個 RPC 服務時,開發者可能需要聲明一個遠程方法拋出一個異常。
容器類型
Thrift 容器與目前流行編程語言的容器類型相對應,有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 如何定義:

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

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

在定義 services 的時候,我們還需要了解一下規則:

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

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

命名空間

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

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

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

關於以上幾種類型的傳輸協議,如果想更深入更具體的了解其實現及工作原理,可以參考站外相關文章《thrift源碼研究》。

傳輸方式

與傳輸協議一樣,Thrift 也支持幾種不同的傳輸方式。
1. TSocket:阻塞型 socket,用於客戶端,采用系統函數 read 和 write 進行讀寫數據。
2. TServerSocket:非阻塞型 socket,用於服務器端,accecpt 到的 socket 類型都是 TSocket(即阻塞型 socket)。
3. TBufferedTransportTFramedTransport 都是有緩存的,均繼承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. THttpClientTHttpServer 是基於 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. TSimpleServer
2. TNonblockingServer
3. THsHaServer
4. TThreadedSelectorServer
5. 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。消息處理器的代碼看上去就像下面這樣:

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. 一旦接受了一個連接,它就會被放入 ThreadPoolExecutor 中的一個 worker 線程裡處理。
3. worker 線程被綁定到特定的客戶端連接上,直到它關閉。一旦連接關閉,該 worker 線程就又回到了線程池中。
4. 你可以配置線程池的最小、最大線程數,默認值分別是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 文件。本文提供一個實現簡單功能的測試案例,如下所示:

/**
* 文件名為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 中進入文件目錄所在目錄,執行代碼生成命令:

thrift-0.9.2.exe -r -gen java TestQry.thrift

執行之後,我們在文件夾中可以看到生成的 java 代碼

圖 5.2

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

<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 接口,關鍵代碼如下

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,二進制傳輸協議),關鍵代碼如下

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 實現客戶端,關鍵代碼如下

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 時,結果如下

code=1 msg=success

當 qryCode = 0 時,結果如下

code=0 msg=fail

附上項目的代碼結構:

圖 5.3

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

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

六. 路漫漫其修遠兮

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

當然,筆者下一步的計劃就是深入研究 Thrift 的實現,希望能和大家一起交流共同進步。

 

 

 

參考文章

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

[2] 《Thrift Java Servers Compared》

 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved