1. 程式人生 > >和 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 具有以下特性: 

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種可用容器型別:

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 的時候,我們還需要了解一下規則:

繼承類必須實現這些方法
引數可以是基本型別或者結構體
所有的引數都是const型別,不能作為返回值
返回值可以是void(oneway的返回值一定是void)
服務支援繼承,一個service可使用extends關鍵字繼承另一個service
服務不支援過載

除上面所提到的四大資料型別外,Thrift 還支援列舉型別(enum)和常量型別(const)。

名稱空間

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

3.4 傳輸體系

傳輸協議

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

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 實現:

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。訊息處理器的程式碼看上去就像下面這樣:

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 不同的是:

有一個專用的執行緒用來接受連線
一旦接受了一個連線,它就會被放入 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 檔案。本文提供一個實現簡單功能的測試案例,如下所示:

/**
* 檔名為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 的實現,希望能和大家一起交流共同進步。

參考文章

相關推薦

Thrift美麗邂逅

 一. 與 Thrift 的初識 也許大多數人接觸 Thrift 是從序列化開始的。每次搜尋 “java序列化” + “方式”、“對比” 或 “效能” 等關鍵字時,搜尋引擎總是會返回一大堆有關各種序列化方式的使用方法或者效能對比的結果給你,而其中必定少不了 Thrift,並且其效能還不錯嘞,至少比那戰鬥力只

秋意正濃,儲存大師們來美麗邂逅

秋天的武漢是美麗的,你可以去落雁島看一次夕陽,鸕鶿們結伴飛向遠處,泛著金色漣漪的湖面,倒映在水中,亦或是約上幾個好友一起去木蘭雲霧山打板慄,那裡的板栗不僅個大、肉嫩,而且皮薄、味甜,或者是騎車去東湖綠道,體驗一次東湖綠道中的“郊野道”,在親水場所、林中棧道中穿梭,當然,你還可以參加

C#基礎系列——風花雪月的邂逅:介面抽象類

前言:最近一個認識的朋友準備轉行做程式設計,看他自己邊看視訊邊學習,挺有幹勁的。那天他問我介面和抽象類這兩個東西,他說,既然它們如此相像, 我用抽象類就能解決的問題,又整個接口出來幹嘛,這不是誤導初學者嗎。博主呵呵一笑,回想當初的自己,不也有此種疑惑麼。。。今天打算針對他的問題,結合一個實際的使用場景來說明下

次與sql註入 & webshell 的美麗邂逅

webshell waf 攻擊 木馬 sql註入 引言 ?一波未平,一波又起。金融公司的業務實在是太引人耳目,何況我們公司的業處正處於風口之上(區塊鏈金融),並且每天有大量現金交易,所以不知道有多少黑客躲在暗處一直在盯著你的系統,讓你防不勝防,並且想方設法的找到突破點,以達到黑客的目的來獲

教育思考:選擇編程是父母孩子的和解[圖]

經歷 進一步 工程師 界面 科技 col 習慣 帶來 男孩子 教育思考:選擇編程是一場父母和孩子的和解[圖]:之前有個很熱的段子是這樣講的:深夜十點的時候,某小區一女子大聲喊叫“什麽關系?啊?!到底什麽關系?你說!”最後發現原來是一位媽媽陪孩子做作業時發出的吶喊!聽完,我們

沈從文與張兆:愛情是無聲戰役,哪有誰對誰錯

戀愛 胡適 自己 喜歡 擁有 勤奮 文學 現狀 年齡 家家有本難念的經,關於婚姻, 沈從文有一句話早就說明了一切。 “愛情是一場無聲戰役,無法輕易判斷誰對誰錯。” 1922年,沈從文服完兵役,來到北京,他僅受過小學教育,又沒有半點經濟來源,在北京大學旁聽,一邊打工一邊勤奮寫

由於錯誤使用final宣告方法CGLib引發的問題

前言: 近期發現某系統在業務高峰時,訪問量回突然下降,開發測試並抓包後發現是呼叫獲取城市列表功能時發生異常所致。下面是發現和解決問題的大概步驟: 1:分析程式碼,發現出問題的介面查詢的所有資料均已快取到redis中,然後查系統日誌,發現在這個時間點有大量的redis訪問超時。 2:問題比

大資料:改變我們如何生活、工作思考的革命(讓資料說話)

讓資料說話 資訊社會的果實很容易被看到,例如每個口袋裡的手機,每個包裡的電腦和在各處辦公室中的大的後臺系統。但不容易看到的是資訊自己。電腦進入主流社會半個世紀以來,資料開始積累到一個點,一些新的特殊的事情即將發生。不僅僅是資訊爆炸,而且資訊增長的速度更快。規模的改變導致狀

神祕巨星百度EasyDL浮現:不可思議大賽挖掘到的驚喜未來新常態

作者|震霆           出品|遇見人工智慧        公眾號|GOwithAI曾經有

從隨機過程的熵率馬爾科夫穩態過程引出的一些思考 - 人生逃不過馬爾科夫穩態

1. 引言 0x1:人生就是一個馬爾科夫穩態 每一秒我們都在做各種各樣的選擇,要吃青菜還是紅燒肉、看電影還是看書、跑步還是睡覺,咋一看起來,每一個選擇都是隨機的,而人生又是由無數個這樣的隨機選擇組成的結果。從這個前提往下推導,似乎可以得出一個結論,即人生是無常的,未來是不可預測的。但事實真的是如此嗎?

字符串除了第一位最後位中間用*替換

username subst sub name user bst rim his while $(".user-name").each(function(){ var userName = $(this).html().trim(); var

今晚巔峰你對編程的認識分享會,再晚就不等你了

今晚一場巔峰你對編程的認識分享會 再晚就不等你了 本文出自 “知乎技術” 博客,請務必保留此出處http://liuzhiying.blog.51cto.com/5850988/1929819今晚一場巔峰你對編程的認識分享會,再晚就不等你了

老男孩教育每日題-第61天-使用命令調換 /etc/passwd 文件裏所有的第一列最後列位置

sed awk 每日一題 調換位置 老男孩教育每日一題-第61天-使用命令調換 /etc/passwd 文件裏 root 位置和/bin/bash 位置?即將所有的第一列和最後一列位置調換?例:默認:root:x:0:0:root:/root:/bin/bash修改後:/bin/bash:x:

以電競的名義,遊戲公益杯背後的思考

三星顯示器  在人類社會的發展秩序中,公益一直是連接人性與社會文明的紐帶。隨著社會的不斷進步以及新興事物的影響,這種契合關系形成的影響力除了在大眾群體層面持續發揚之外,在如今的各個領域也已經成為新的潮流,誕生了一些新模式、新打法。  6月13日,京東遊戲公益杯在北京舉行,電競和公益兩個看似毫不相幹的概念被捆綁

比賽

ima spa alt 比賽 -1 image mic ros cnblogs   今天考了一場讓我差點爆0的比賽,似乎最初只有10分。其中一題數據有誤,修正過後也只是多了60分。   改得很辛苦啊!   A: 一場比賽

SQL Server學習記錄之獲取每月每季度每年第一天最後

第一天 ediff code spa -1 指定日期 ctc 算法 datetime DECLARE@dtdatetime SET@dt=GETDATE() DECLARE@numberint SET@number=3 --1.指定日期該年的第一天或最後

2017全球 C++ 及系統軟件技術大會:大師智慧+實踐幹貨的技術盛宴!

影響 oid 高端培訓 iad 通過 開發框架 ble 並發 work 從1985年由Bjarne Stroustrup在貝爾實驗室發明以來 ,C++作為一門系統級語言,早已超越一門編程語言的影響。由其構建的龐大的系統級軟件,已經成為當今世界IT與互聯網應用的關鍵支撐。秉承

C# 每月第一天最後

end style logs bsp code gin class date pan //每月第一天 var beginTime=DateTime.Now.AddDays(1 - DateTime.Now.Day); //每月最後一天 var endTime=DateT

【設計開發套簡單自己主動化UI框架】

depth 定義 其它 而是 例如 選擇 span debug etl !有興趣的朋友請直接移步Github,本帖子已經不做更新,框架的詳細的實現已經做了優化和代碼整理,本文僅僅介紹了詳細的設計思路! 目標:編寫一個簡單通用UI框架用於管理頁面和完

成都策劃慶典活動、舞臺燈光、音響設備租賃需要多少錢

組織 電話 要點 name 編寫 組織者 jpg 中心 不同 一場重要的的活動策劃或者節日慶典都是一個企業或者商家的品牌,至於價格需要看規模,一般在5000-20萬不等,詳情可以聯系:15390022185對商界人士來講,組織慶典與參加慶典時,往往會各有多方面的不同要求。慶