什麼是 Thrift(RPC)?一種介面描述語言和二進位制通訊協議,用來定義和建立跨語言的服務

rtoax發表於2020-10-04

Table of Contents

什麼是Thrift

架構

什麼是RPC框架?

Thrift的協議棧結構

優點

建立一個Thrift服務

Thrift的第一個java小例項


 

Thrift是一種介面描述語言和二進位制通訊協議,它被用來定義和建立跨語言的服務。它被當作一個遠端過程呼叫(RPC)框架來使用,是由Facebook為“大規模跨語言服務開發”而開發的。它通過一個程式碼生成引擎聯合了一個軟體棧,來建立不同程度的、無縫的跨平臺高效服務,可以使用C#C++(基於POSIX相容系統)、Cappuccino、CocoaDelphiErlangGoHaskellJavaNode.jsOCamlPerlPHPPythonRubySmalltalk。雖然它以前是由Facebook開發的,但它現在是Apache軟體基金會開源專案了。該實現被描述在2007年4月的一篇由Facebook發表的技術論文中,該論文現由Apache掌管。

 

什麼是Thrift

https://www.jianshu.com/p/4723ce380b0e


Protobuf是一個語言中立、平臺中立,對結構化資料進行序列化的可擴充套件機制。

我們在開發的時候開發了一個restful web service,就是基於rest的http呼叫,A系統作為客戶端,B系統作為伺服器端。A系統可以通過URL的方式攜帶一些資料去呼叫B所提供的介面然後返回相應的結果資料。這種方式我們也可以認為是RPC的一種實現方式。對於這種方式我們可以認為是平臺獨立的、語言獨立的,也就是語言中立、平臺中立。也就是我們可以用Python編寫的客戶端去呼叫Java編寫的服務端,因為都是通過URL的方式呼叫。因為URL相當於契約,URL背後的程式碼呼叫者無需關心。

RPC框架呼叫基本模型:如person.getPersonByName(String name),首先客戶端先序列化呼叫資料,傳給服務端,服務端再反序列化提取呼叫資訊,查詢客戶端所需要的資料,完成之後再序列化結果傳回給客戶端。客戶端再反序列化得到結果。

Apache thrift是一個可伸縮的,並且跨語言的一種服務性的開發,他所完成的功能實際上和protobuf是類似的。簡單來說,是Facebook公佈的一款開源跨語言的RPC框架。

 

架構


Thrift包含一套完整的棧來建立客戶端和服務端程式。頂層部分是由Thrift定義生成的程式碼。而服務則由這個檔案客戶端和處理器程式碼生成。在生成的程式碼裡會建立不同於內建型別的資料結構,並將其作為結果傳送。協議和傳輸層執行時庫的一部分。有了Thrift,就可以定義一個服務或改變通訊和傳輸協議,而無需重新編譯程式碼。除了客戶端部分之外,Thrift還包括伺服器基礎設施來整合協議和傳輸,如阻塞、非阻塞及多執行緒伺服器。棧中作為I/O基礎的部分對於不同的語言則有不同的實現。

Thrift支援眾多通訊協議:

  • TBinaryProtocol – 一種簡單的二進位制格式,簡單,但沒有為空間效率而優化。比文字協議處理起來更快,但更難於除錯
  • TCompactProtocol – 更緊湊的二進位制格式,處理起來通常同樣高效。
  • TDebugProtocol – 一種人類可讀的文字格式,用來協助除錯。
  • TDenseProtocol – 與TCompactProtocol類似,將傳輸資料的元資訊剝離。
  • TJSONProtocol – 使用JSON對資料編碼。
  • TSimpleJSONProtocol – 一種只寫協議,它不能被Thrift解析,因為它使用JSON時丟棄了後設資料。適合用指令碼語言來解析。

支援的傳輸協議有:

  • TFileTransport – 該傳輸協議會寫檔案。
  • TFramedTransport – 當使用一個非阻塞伺服器時,要求使用這個傳輸協議。它按幀來傳送資料,其中每一幀的開頭是長度資訊。
  • TMemoryTransport – 使用儲存器對映輸入輸出。(Java的實現使用了一個簡單的ByteArrayOutputStream。)
  • TSocket – 使用阻塞的套接字I/O來傳輸。
  • TZlibTransport – 用zlib執行壓縮。用於連線另一個傳輸協議。

Thrift還提供眾多的伺服器,包括:

  • TNonblockingServer – 一個多執行緒伺服器,它使用非阻塞I/O(Java的實現使用了NIO通道)。TFramedTransport必須跟這個伺服器配套使用。
  • TSimpleServer – 一個單執行緒伺服器,它使用標準的阻塞I/O。測試時很有用。
  • TThreadPoolServer – 一個多執行緒伺服器,它使用標準的阻塞I/O。

 

什麼是RPC框架?

https://www.jianshu.com/p/4723ce380b0e


RPC全稱為Remote Procedure Call,意為遠端過程呼叫。

假設有兩臺伺服器A,B.A伺服器上部署著一個應用a,B伺服器上部署著一個應用b,現在a希望能夠呼叫b應用的某個函式(方法),但是二者不在同一個程式內,不能直接呼叫,就需要通過網路傳輸,在AB伺服器之間建一條網路傳輸通道,a把引數傳過去,b接收到引數呼叫自己的方法得到結果,再通過網路傳回給a。

簡單講就是A通過網路來呼叫B的過程,這個過程要涉及的東西很多,比如多執行緒、Socket、序列化反序列化、網路I/O,很複雜。於是牛掰的程式設計師把這些封裝起來做成一套框架供大家使用,就是RPC框架。

thrift通過一箇中間語言IDL(介面定義語言)來定義RPC的資料型別和介面,這些內容寫在以.thrift結尾的檔案中,然後通過特殊的編譯器來生成不同語言的程式碼,以滿足不同需要的開發者。比如java開發者,就可以生成java程式碼,c++開發者可以生成c++程式碼,生成的程式碼中不但包含目標語言的介面定義、方法、資料型別,還包含有RPC協議層和傳輸層的實現程式碼。

 

Thrift的協議棧結構


Thrift是一種c/s的架構體系。TServer主要任務是高效的接受客戶端請求,並將請求轉發給Processor處理。

  • 最上層是使用者自行實現的業務邏輯程式碼;
  • Processor是由thrift編譯器自動生成的程式碼,它封裝了從輸入資料流中讀資料和向資料流中寫資料的操作,它的主要工作是:從連線中讀取資料,把處理交給使用者實現impl,最後把結果寫到連線上。
  • TProtocol是用於資料型別解析的,將結構化資料轉化為位元組流給TTransport進行傳輸。從TProtocol以下部分是thirft的傳輸協議和底層I/O通訊。
  • TTransport是與底層資料傳輸密切相關的傳輸層,負責以位元組流方式接收和傳送訊息體,不關注是什麼資料型別。
  • 底層IO負責實際的資料傳輸,包括socket、檔案和壓縮資料流等。

 

優點


Thrift一些已經明確的優點包括:

  • 跟一些替代選擇,比如SOAP相比,跨語言序列化的代價更低,因為它使用二進位制格式。
  • 它有一個又瘦又幹淨的庫,沒有編碼框架,沒有XML配置檔案。
  • 繫結感覺很自然。例如,Java使用java.util.ArrayList<String>;C++使用std::vector<std::string>。
  • 應用層通訊格式與序列化層通訊格式是完全分離的。它們都可以獨立修改。
  • 預定義的序列化格式包括:二進位制格式、對HTTP友好的格式,以及緊湊的二進位制格式。
  • 兼作跨語言檔案序列化
  • 協議使用軟版本號機制軟體版本管理。Thrift不要求一箇中心化的和顯式的版本號機制,例如主版本號/次版本號。鬆耦合的團隊可以輕鬆地控制RPC呼叫的演進。
  • 沒有構建依賴也不含非標準化的軟體。不存在不相容的軟體許可證混用的情況。

 

建立一個Thrift服務


Thrift由C++編寫,但可以為眾多語言建立程式碼。要建立一個Thrift服務,必須寫一些Thrift檔案來描述它,為目標語言生成程式碼,並且寫一些程式碼來啟動伺服器及從客戶端呼叫它。

Thrift將由這個描述資訊生成獨立的程式碼。例如,在Java裡,PhoneType將是Phone類中一個簡單的enum。

百度百科:https://baike.baidu.com/item/thrift/3879058

 

Thrift的第一個java小例項


建立一個服務Hello,建立檔案Hello.thrift,程式碼如下:

namespace java service.demo
service Hello{
    string helloString(1:string para)
}

終端進入Hello.thrift所在目錄,執行命令:

thrift -r -gen java Hello.thrift

發現在當前目錄下多了一個gen-java的目錄,裡面的有一個Hello.java的檔案。這個java檔案包含Hello服務的介面定義Hello.Iface,以及服務呼叫的底層通訊細節,包括客戶端的呼叫邏輯Hello.Client以及服務端的處理邏輯Hello.Processor

建立一個Maven管理的Java專案,pom.xml中新增相關的依賴,並將Hello.java檔案複製到專案中:

<dependency>
      <groupId>org.apache.thrift</groupId>
      <artifactId>libthrift</artifactId>
      <version>0.10.0</version>
</dependency>
<dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-log4j12</artifactId>
      <version>1.7.5</version>
</dependency>

建立HelloServiceImpl實現Hello.Iface介面:
 

package service.demo;
import org.apache.thrift.TException;

public class HelloServiceImpl implements Hello.Iface {
    public String helloString(String para) throws TException {
        return "result:"+para;
    }
}

建立服務端實現程式碼HelloServiceServer,把HelloServiceImpl作為一個具體的處理器傳遞給Thrift伺服器:

public class HelloServiceServer {
    /**
     * 啟動thrift伺服器
     */
    public static void main(String[] args) {
        try {
            System.out.println("服務端開啟....");
            // 1.建立TProcessor
            TProcessor tprocessor = new Hello.Processor<Hello.Iface>(new HelloServiceImpl());
            // 2.建立TserverTransport
            TServerSocket serverTransport = new TServerSocket(9898);
            // 3.建立TProtocol
            TBinaryProtocol.Factory factory = new TBinaryProtocol.Factory();

            TServer.Args tArgs = new TServer.Args(serverTransport);
            tArgs.processor(tprocessor);
            tArgs.protocolFactory(factory);

            // 4.建立Tserver,傳入需要的引數,server將以上內容整合在一起
            TServer server = new TSimpleServer(tArgs);
            // 5.啟動server
            server.serve();
        }catch (TTransportException e) {
            e.printStackTrace();
        }
    }
}

建立客戶端實現程式碼HelloServiceClient,呼叫Hello.client訪問服務端的邏輯實現:

public class HelloServiceClient {

    public static void main(String[] args) {
        System.out.println("客戶端啟動....");
        TTransport transport = null;
        try {
            transport = new TSocket("localhost", 9898, 30000);
            // 協議要和服務端一致
            TProtocol protocol = new TBinaryProtocol(transport);
            Hello.Client client = new Hello.Client(protocol);
            transport.open();
            String result = client.helloString("哈哈");
            System.out.println(result);
        } catch (TTransportException e) {
            e.printStackTrace();
        } catch (TException e) {
            e.printStackTrace();
        } finally {
            if (null != transport) {
                transport.close();
            }
        }
    }
}

全部工作完成後,下面來測試一下,先執行服務端main方法,在執行客戶端main方法,會在客戶端控制檯列印出:哈哈

 

 

 

相關文章