1. 程式人生 > >Apache Thrift系列詳解(一)

Apache Thrift系列詳解(一)

前言

Thrift是一個輕量級跨語言遠端服務呼叫框架,最初由Facebook開發,後面進入Apache開源專案。它通過自身的IDL中間語言, 並藉助程式碼生成引擎生成各種主流語言的RPC服務端/客戶端模板程式碼。

Thrift支援多種不同的程式語言,包括C++JavaPythonPHPRuby等,本系列主要講述基於Java語言的Thrift的配置方式和具體使用。

正文

Thrift的技術棧

Thrift軟體棧的定義非常的清晰, 使得各個元件能夠鬆散的耦合, 針對不同的應用場景, 選擇不同是方式去搭建服務。

Thrift軟體棧分層從下向上分別為:傳輸層(Transport Layer

)、協議層(Protocol Layer)、處理層(Processor Layer)和服務層(Server Layer)。

  • 傳輸層(Transport Layer):傳輸層負責直接從網路中讀取寫入資料,它定義了具體的網路傳輸協議;比如說TCP/IP傳輸等。

  • 協議層(Protocol Layer):協議層定義了資料傳輸格式,負責網路傳輸資料的序列化反序列化;比如說JSONXML二進位制資料等。

  • 處理層(Processor Layer):處理層是由具體的IDL介面描述語言)生成的,封裝了具體的底層網路傳輸序列化方式,並委託給使用者實現的Handler進行處理。

  • 服務層(Server Layer

    ):整合上述元件,提供具體的網路執行緒/IO服務模型,形成最終的服務。

Thrift的特性

(一) 開發速度快

通過編寫RPC介面Thrift IDL檔案,利用編譯生成器自動生成服務端骨架(Skeletons)和客戶端樁(Stubs)。從而省去開發者自定義維護介面編解碼訊息傳輸伺服器多執行緒模型等基礎工作。

  • 服務端:只需要按照服務骨架介面,編寫好具體的業務處理程式(Handler)即實現類即可。
  • 客戶端:只需要拷貝IDL定義好的客戶端樁服務物件,然後就像呼叫本地物件的方法一樣呼叫遠端服務。

(二) 介面維護簡單

通過維護Thrift格式的IDL(介面描述語言)檔案(注意寫好註釋),即可作為給Client

使用的介面文件使用,也自動生成介面程式碼,始終保持程式碼和文件的一致性。且Thrift協議可靈活支援介面可擴充套件性

(三) 學習成本低

因為其來自Google Protobuf開發團隊,所以其IDL檔案風格類似Google Protobuf,且更加易讀易懂;特別是RPC服務介面的風格就像寫一個面向物件Class一樣簡單。

(四) 多語言/跨語言支援

Thrift支援C++JavaPythonPHPRubyErlangPerlHaskellC#CocoaJavaScriptNode.jsSmalltalk等多種語言,即可生成上述語言的伺服器端客戶端程式

對於我們經常使用的JavaPHPPythonC++支援良好,雖然對iOS環境的Objective-C(Cocoa)支援稍遜,但也完全滿足我們的使用要求。

(五) 穩定/廣泛使用

Thrift在很多開源專案中已經被驗證是穩定高效的,例如CassandraHadoopHBase等;國外在Facebook中有廣泛使用,國內包括百度、美團小米、和餓了麼等公司。

Thrift的資料型別

Thrift 指令碼可定義的資料型別包括以下幾種型別:

  1. 基本型別:   bool: 布林值   byte: 8位有符號整數   i16: 16位有符號整數   i32: 32位有符號整數   i64: 64位有符號整數   double: 64位浮點數   string: UTF-8編碼的字串   binary: 二進位制串
  2. 結構體型別:   struct: 定義的結構體物件
  3. 容器型別:   list: 有序元素列表   set: 無序無重複元素集合   map: 有序的key/value集合
  4. 異常型別:   exception: 異常型別
  5. 服務型別:   service: 具體對應服務的類

Thrift的協議

Thrift可以讓使用者選擇客戶端服務端之間傳輸通訊協議的類別,在傳輸協議上總體劃分為文字(text)和二進位制(binary)傳輸協議。為節約頻寬提高傳輸效率,一般情況下使用二進位制型別的傳輸協議為多數,有時還會使用基於文字型別的協議,這需要根據專案/產品中的實際需求。常用協議有以下幾種:

  • TBinaryProtocol:二進位制編碼格式進行資料傳輸
  • TCompactProtocol:高效率的、密集二進位制編碼格式進行資料傳輸
  • TJSONProtocol: 使用JSON文字的資料編碼協議進行資料傳輸
  • TSimpleJSONProtocol:只提供JSON只寫的協議,適用於通過指令碼語言解析

Thrift的傳輸層

常用的傳輸層有以下幾種:

  • TSocket:使用阻塞式I/O進行傳輸,是最常見的模式
  • TNonblockingTransport:使用非阻塞方式,用於構建非同步客戶端
  • TFramedTransport:使用非阻塞方式,按塊的大小進行傳輸,類似於Java中的NIO

Thrift的服務端型別

  • TSimpleServer:單執行緒伺服器端,使用標準的阻塞式I/O
  • TThreadPoolServer:多執行緒伺服器端,使用標準的阻塞式I/O
  • TNonblockingServer:單執行緒伺服器端,使用非阻塞式I/O
  • THsHaServer:半同步半非同步伺服器端,基於非阻塞式IO讀寫和多執行緒工作任務處理
  • TThreadedSelectorServer:多執行緒選擇器伺服器端,對THsHaServer非同步IO模型上進行增強

Thrift入門示例

(一) 編寫Thrift IDL檔案

b). 下載Windows安裝環境的.exe檔案,將thrift.exe的路徑加入環境變數中。在Idea上安裝Thrift編輯外掛。

c). 編寫hello.thriftIDL檔案:

service HelloWorldService {
  string say(1: string username)
}

d). 使用程式碼生成工具生成程式碼,執行以下命令:

thrift -gen java hello.thrift

e). 由於未指定程式碼生成的目標目錄,生成的類檔案預設存放在gen-java目錄下。這裡生成一個HelloWorldService.java類檔案,檔案大小超過數千行,下面擷取一部分核心程式碼

public class HelloWorldService {
    public interface Iface {
        public String say(String username) throws org.apache.thrift.TException;
    }

    public interface AsyncIface {
        public void say(String username, org.apache.thrift.async.AsyncMethodCallback<String> resultHandler) throws org.apache.thrift.TException;
    }

    public static class Client extends org.apache.thrift.TServiceClient implements Iface {
        public static class Factory implements org.apache.thrift.TServiceClientFactory<Client> {
            public Factory() {
            }

            public Client getClient(org.apache.thrift.protocol.TProtocol prot) {
                return new Client(prot);
            }

            public Client getClient(org.apache.thrift.protocol.TProtocol iprot, org.apache.thrift.protocol.TProtocol oprot) {
                return new Client(iprot, oprot);
            }
        }

        public Client(org.apache.thrift.protocol.TProtocol prot) {
            super(prot, prot);
        }

        public Client(org.apache.thrift.protocol.TProtocol iprot, org.apache.thrift.protocol.TProtocol oprot) {
            super(iprot, oprot);
        }

        public String say(String username) throws org.apache.thrift.TException {
            send_say(username);
            return recv_say();
        }
        // 省略.....
    }

    public static class AsyncClient extends org.apache.thrift.async.TAsyncClient implements AsyncIface {
        public static class Factory implements org.apache.thrift.async.TAsyncClientFactory<AsyncClient> {
            private org.apache.thrift.async.TAsyncClientManager clientManager;
            private org.apache.thrift.protocol.TProtocolFactory protocolFactory;

            public Factory(org.apache.thrift.async.TAsyncClientManager clientManager, org.apache.thrift.protocol.TProtocolFactory protocolFactory) {
                this.clientManager = clientManager;
                this.protocolFactory = protocolFactory;
            }

            public AsyncClient getAsyncClient(org.apache.thrift.transport.TNonblockingTransport transport) {
                return new AsyncClient(protocolFactory, clientManager, transport);
            }
        }

        public AsyncClient(org.apache.thrift.protocol.TProtocolFactory protocolFactory, org.apache.thrift.async.TAsyncClientManager clientManager, org.apache.thrift.transport.TNonblockingTransport transport) {
            super(protocolFactory, clientManager, transport);
        }

        public void say(String username, org.apache.thrift.async.AsyncMethodCallback<String> resultHandler) throws org.apache.thrift.TException {
            checkReady();
            say_call method_call = new say_call(username, resultHandler, this, ___protocolFactory, ___transport);
            this.___currentMethod = method_call;
            ___manager.call(method_call);
        }
        // 省略.....
    }
    // 省略.....
}

對於開發人員而言,使用原生的Thrift框架,僅需要關注以下四個核心內部介面/類Iface, AsyncIface, ClientAsyncClient

  • Iface服務端通過實現HelloWorldService.Iface介面,向客戶端的提供具體的同步業務邏輯。
  • AsyncIface服務端通過實現HelloWorldService.Iface介面,向客戶端的提供具體的非同步業務邏輯。
  • Client客戶端通過HelloWorldService.Client的例項物件,以同步的方式訪問服務端提供的服務方法。
  • AsyncClient客戶端通過HelloWorldService.AsyncClient的例項物件,以非同步的方式訪問服務端提供的服務方法。

(二) 新建Maven工程

a). 新建maven工程,引入thrift的依賴,這裡使用的是版本0.10.0

  <dependency>
      <groupId>org.apache.thrift</groupId>
      <artifactId>libthrift</artifactId>
      <version>0.10.0</version>
  </dependency>

b). 將生成類的HelloWorldService.java原始檔拷貝進專案原始檔目錄中,並實現HelloWorldService.Iface的定義的say()方法。

HelloWorldServiceImpl.java

public class HelloWorldServiceImpl implements HelloWorldService.Iface {
    @Override
    public String say(String username) throws TException {
        return "Hello " + username;
    }
}

c). 伺服器端程式編寫:

SimpleServer.java

public class SimpleServer {
    public static void main(String[] args) throws Exception {
        ServerSocket serverSocket = new ServerSocket(ServerConfig.SERVER_PORT);
        TServerSocket serverTransport = new TServerSocket(serverSocket);
        HelloWorldService.Processor processor =
                new HelloWorldService.Processor<HelloWorldService.Iface>(new HelloWorldServiceImpl());

        TBinaryProtocol.Factory protocolFactory = new TBinaryProtocol.Factory();
        TSimpleServer.Args tArgs = new TSimpleServer.Args(serverTransport);
        tArgs.processor(processor);
        tArgs.protocolFactory(protocolFactory);

        // 簡單的單執行緒服務模型 一般用於測試
        TServer tServer = new TSimpleServer(tArgs);
        System.out.println("Running Simple Server");
        tServer.serve();
    }
}

d). 客戶端程式編寫:

SimpleClient.java

public class SimpleClient {
    public static void main(String[] args) {
        TTransport transport = null;
        try {
            transport = new TSocket(ServerConfig.SERVER_IP, ServerConfig.SERVER_PORT, ServerConfig.TIMEOUT);
            TProtocol protocol = new TBinaryProtocol(transport);
            HelloWorldService.Client client = new HelloWorldService.Client(protocol);
            transport.open();

            String result = client.say("Leo");
            System.out.println("Result =: " + result);
        } catch (TException e) {
            e.printStackTrace();
        } finally {
            if (null != transport) {
                transport.close();
            }
        }
    }
}

e). 執行服務端程式,服務端指定埠監聽客戶端連線請求,控制檯輸出啟動日誌:

f). 執行客戶端程式,客戶端通過網路請求HelloWorldServicesay()方法的具體實現,控制檯輸出返回結果:

這裡使用的一個基於單執行緒同步簡單服務模型,一般僅用於入門學習和測試!

總結

本文對Thrift的概念做了相關介紹,體驗了一番thrift程式如何編寫!

歡迎關注技術公眾號: 零壹技術棧

image

本帳號將持續分享後端技術乾貨,包括虛擬機器基礎,多執行緒程式設計,高效能框架,非同步、快取和訊息中介軟體,分散式和微服務,架構學習和進階等學習資料和文章。