Thrift基本原理以及使用介紹

Thrift

初識Thrift

Thrift是一個跨語言通信的RPC軟件,最初是由FaceBook開發的,現在是Apache的一個頂級項目。

Thrift概念:

Thrift 最初是由 Facebook 開發用做系統內各語言之間的 RPC 通信的一個可擴展且跨語言的軟件框架,它結合了功能強大的軟件堆棧和代碼生成引擎,允許定義一個簡單的定義文件中的數據類型和服務接口,以作爲輸入文件,編譯器生成代碼用來方便地生成RPC客戶端和服務器通信的無縫跨編程語言。Thrift 是 IDL 描述性語言的一個具體實現,適用於程序對程序靜態的數據交換,需要先確定好數據結構。Thrift 是完全靜態化的,當數據結構發生變化時,必須重新編輯IDL文件、代碼生成再編譯載入的流程,跟其他IDL工具相比較可以視爲是 Thrift 的弱項。Thrift 適用於搭建大型數據交換及存儲的通用工具,在大型系統中的內部數據傳輸上相對於 JSON 和 XML 無論在性能、傳輸大小上有明顯的優勢。

什麼是RPC

Remote Procedure Call Protocol 遠程過程調用協議,我們 舉個例子說明:

本地調用

public void invoke() {
  String param1="string1";
  String param2="string2";
  String res = getStr(param1,param2);
  System.out.println("res = " + res);
}

private String getStr(String str1, String str2) {
  return str1 + str2;
}

調用方和被調用方都在一個程序內部,屬於進程內調用。CPU 在執行調用時切換去執行被調用函數,執行完後再切換回來執行後續的代碼。對調用方而言,執行被調用函數時會阻塞(非異步情況下)直到調用函數執行完畢。

RPC調用

public void test() {
TestQry.Client client = getClient("192.168.4.222", 7800, 5000);
  String param1="string1";
  String param2="string2";
  String res = client.getStr(param1,param2);
  System.out.println("res = " + res);
}

這裏進程間調用,但是調用方和被調用方不再同一個進程,甚至不同的服務器和機房。進程間調用需要通過網絡來傳輸數據,調用方在執行 RPC 調用時會阻塞(非異步情況下)直到調用結果返回才繼續執行後續代碼。

小結

現在應該知道什麼是RPC了,概括一下就是:RPC是一種通過網絡從遠程計算機程序上請求服務的方式,它使得開發包括網絡分佈式多程序在內的應用程序更加容易。

Thrift結構

  • 代碼框架層

Thrift 定義的服務接口描述文件生成的客戶端和服務器端代碼框架。

  • 數據讀寫操作層

是根據Thrift 文件生成代碼實現數據的讀寫操作。Thrift允許定義一個簡單的定義文件中的數據類型和服務接口,以作爲輸入文件,編譯器生成代碼用來方便地生成RPC客戶端和服務器通信的無縫跨編程語言。

Thrift通信

一張圖說明服務端啓動與提供服務的流程:

程序調用了 TThreadPoolServer 的 serve() 方法後,server 進入阻塞監聽狀態,其阻塞在 TServerSocket 的accept()方法上。當接收到來自客戶端的消息後,服務器發起一個新線程處理這個消息請求,原線程再次進入阻塞狀態。在新線程中,服務器通過 TBinaryProtocol 協議讀取消息內容,調用 HelloServiceImpl 的helloVoid() 方法,並將結果寫入 helloVoid_result 中傳回客戶端。

 再來一張圖說明服務端啓動服務以後,客戶端請求服務的流程:

程序調用了 Hello.Client 的 helloVoid() 方法,在 helloVoid() 方法中,通過 send_helloVoid() 方法發送對服務的調用請求,通過 recv_helloVoid() 方法接收服務處理請求後返回的結果。

 

Thrift數據類型

它支持這幾大數據結構:基本類型、結構體和異常類型、容器類型、服務類型。

基本類型

  • ​ bool: 布爾值(true or false)

  • ​ byte: 有符號字節

  • ​ i16: 16位有符號整型

  • ​ i32: 32位有符號整型

  • ​ i64: 64位有符號整型

  • ​ double:64位浮點型

  • ​ string: 二進制字符串

結構體類型

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

結構體有幾點要注意:

1:在java中,結構體會被轉換成對象的類;

2:其成員都有明確的類型;

3:成員都是被正整數編號過的,這個編號不能重複;

4:在結構體中,可以直接設置默認值;

服務類型

service querySrv {
    /** 根據名字和年齡來查找用戶 */
    UserDemo qryUser(1:string name, 2:i32 age);
​
    /** 根據id查找對應的手機號 */
    string queryPhone(1:i32 id);
}

服務類型說明:服務類型語法等同於Java中的接口。

小結

除了上面所提到的四大類型外,Thrift還支持枚舉和常量類型。當然,它也有不支持的類型,典型的就是Date類型不支持。

Thrit組件

下面我們將圍繞這三個組件展開介紹,內容略顯枯燥,但是是使用Thrift的關鍵。

Protocol:數據通信協議

定義了消息是怎樣序列化的,主要有二進制、文本。

常見的有:

◆TBinaryProtocol:這是Thrift的默認協議,使用二進制編碼格式進行數據傳輸。

◆TCompactProtocol:壓縮格式。

◆TJSONProtocol:以JSON數據編碼協議進行數據傳輸。

◆TDebugProtocol:常常用以編碼人員測試,以文本的形式展現方便閱讀。

用戶可以根據自己的實際需求選擇合適的類型,生產環境一般用二進制類型的多

Transport:傳輸方式

定義了消息是怎樣在客戶端和服務器端之間通信的。

常見的有:

◆TSocket::採用TCP Socket進行數據傳輸。

◆ TFileTransport:文件(日誌)傳輸類,允許client將文件傳給server,允許server 將收到的數據寫到文件中。

◆ THttpTransport:採用Http傳輸協議進行數據傳輸

◆ TZlibTransport:壓縮後對數據進行傳輸,或者將收到的數據解壓

下面幾個類主要是對上面幾個類的修飾(採用裝飾模式),提高傳輸效率

◆ TBufferedTransport:對某個Transport對象操作的數據進行buffer,即從buffer中讀取數據進行傳輸,或者將數據直接寫入buffer◆ TFramedTransport:以frame(幀)爲單位進行傳輸,非阻塞式服務中使用。同 TBufferedTransport類似,也會對相關數據進行buffer,同時,它支持定長數據發送和接收。◆ TMemoryBuffer:從一個緩衝區中讀寫數據◆ TTransport是所有Transport類的父類,爲上層提供了統一的接口而且通過 TTransport 即可訪問各個子類不同實現,類似多態。

Server Type: 服務端類型

server 用於從 transport 接收序列化的消息,根據 protocol 反序列化之,調用用戶定義的消息處理器,並序列化消息處理器(接口實現類)的響應,然後再將它們寫回 transport。

常用的server實現有:

◆ TSimpleServer:接受一個連接,處理連接請求,直到客戶端關閉了連接,它纔回去接受一個新的連接。正因爲它只在一個單獨的線程中以阻塞 I/O 的方式完成這些工作,所以它只能服務一個客戶端連接,其他所有客戶端在被服務器端接受之前都只能等待。

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

◆ TNonblockingServer:阻塞在多個連接上,而不是阻塞在單一的連接上。 server 可同時服務多個客戶端。

缺點:在業務處理上還是採用單線程順序來完成,在業務處理比較複雜、耗時的時候,例如某些接口函數需要讀取數據庫執行時間較長,此時該模式效率也不高,因爲多個調用請求任務依然是順序一個接一個執行。

◆ THsHaServer:是TNonblockingServer類的子類,主線程負責數據讀取,然後引入一個線程池來專門進行業務處理,緩解TNonblockingServer類的缺點。

缺點:主線程需要完成對所有socket的監聽以及數據讀寫的工作,當併發請求數較大時,且發送數據量較多時,監聽socket上新連接請求不能被及時接受。

◆ TThreadPoolServer TThreadPoolServer模式採用阻塞socket方式工作,主線程負責阻塞式監聽“監聽socket”中是否有新socket到來,數據讀取和業務處理都交由線程池完成,主線程只負責監聽新連接。

缺點:線程池模式的處理能力受限於線程池的工作能力,當併發請求數大於線程池中的線程數時,新請求也只能排隊等待。

◆ TThreadedSelectorServer: TThreadedSelectorServer模式是目前Thrift提供的最高級的模式。看圖

 

TThreadedSelectorServer對於大部分應用場景性能都不會差,因此,如果實在不知道選擇哪種工作模式,使用TThreadedSelectorServer就可以。

如何使用Thrift

Step1

使用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值爲其他值時返回”失敗”
    */
    QryResult qryTest(1:i32 qryCode)
}

Step2

利用Thrift代碼生成程序生成相應的java代碼

1:將我們建立的TestQry.thrift文件與thrift-0.9.2.exe放在同一目錄,如下:

2:執行命令,生成相應的java代碼: thrift.exe -gen java TestQry.thrift

Step3

生成的java代碼導入到你的maven項目中,添加相應的依賴包

Step4

創建QueryImp.java實現TestQry.Iface接口,這裏寫業務。

/**
   文件名 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值爲其他值時返回”失敗”
    */
    QryResult qryTest(1:i32 qryCode)
}

Step5

創建ThriftServerDemo實現服務端,這裏用TNonblockingServerSocket

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);
            //1. 基於Handler創建Processer
            TestQry.Processor processor = new TestQry.Processor(new QueryImp());
            TNonblockingServer.Args arg = new TNonblockingServer.Args(socket);
            //2.創建protocol
            arg.protocolFactory(new TBinaryProtocol.Factory());
            //3.創建tansport
            arg.transportFactory(new TFramedTransport.Factory());
            arg.processorFactory(new TProcessorFactory(processor));
            //4.基於三者創建server
            server = new TNonblockingServer(arg);
            //5.運行server
            server.serve();
        } catch (TTransportException e) {
            e.printStackTrace();
        }
    }
}

Step6

public class ThriftClientDemo {
    private final static int DEFAULT_QRY_CODE = 1;
    public static void main(String[] args) {
        //1.創建transport
        TTransport tTransport = getTTransport();
        //2.創建protocol
        TProtocol protocol = new TBinaryProtocol(tTransport);
        //3.創建client
        TestQry.Client client = new TestQry.Client(protocol);
        QryResult result = null;
        
        try {
            //運行client的方法
            result = client.qryTest(DEFAULT_QRY_CODE);
        } catch (TException e) {
            e.printStackTrace();
        }
        System.out.println("code="+result.code+" "+"msg="+result.msg);
    }
​
    public static TTransport getTTransport() {
        TTransport tTransport = getTTransport("127.0.0.1", 30001, 5000);
        if (!tTransport.isOpen()){
            try {
                tTransport.open();
            } catch (TTransportException e) {
                e.printStackTrace();
            }
        }
        return tTransport;
    }
​
    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;
    }
}

Step7

所有準備工作都已經做好了,接下來我們就來進行 Client 和 Server 的通信。

先運行 ThriftServerDemo 啓動 Server,然後運行 ThriftClientDemo.java 創建 Client進行調用,以上步驟都是我自己寫過的,可以得到結果。

結語

Thrift有什麼優勢?

Thrift是一個跨語言通信的服務框架,不同語言開發的程序可以通過Thrift來進行通信:通過編譯一個後綴名爲.thrift的文件來生成指定語言的代碼,通過生成的代碼我們就可以編寫出跨語言通信的代碼了:如服務端是用Thrift生成的Java代碼,客戶端使用Thrift生成的C++/C#代碼,用Thtift可以完成C++代碼到Java代碼的調用,而不需要關心其他如網絡通信等內容,可以讓開發人員專注於業務實現。

Thrift爲什麼這麼好用?

網絡編程需要關注很多數據傳輸中的細節,比如數據如何序列化、如何在字節數組裏建立結構、如何在兩端解析字節數組、如何處理Handler裏的事件狀態、如何把多個Handler按順序串起來,Thrift掩蓋了數據傳輸這件事情,開發者使用的時候就是純純的RPC的使用感受。

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