Apache Thrift系列詳解(一) - 概述與入門

前言

Thrift是一個輕量級跨語言遠程服務調用框架,最初由Facebook開發,後面進入Apache開源項目。它經過自身的IDL中間語言, 並藉助代碼生成引擎生成各類主流語言的RPC服務端/客戶端模板代碼。java

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

正文

Thrift的技術棧

Thrift軟件棧的定義很是的清晰, 使得各個組件可以鬆散的耦合, 針對不一樣的應用場景, 選擇不一樣是方式去搭建服務。編程

Thrift軟件棧分層從下向上分別爲:傳輸層(Transport Layer)、協議層(Protocol Layer)、處理層(Processor Layer)和服務層(Server Layer)。後端

  • 傳輸層(Transport Layer):傳輸層負責直接從網絡中讀取寫入數據,它定義了具體的網絡傳輸協議;好比說TCP/IP傳輸等。緩存

  • 協議層(Protocol Layer):協議層定義了數據傳輸格式,負責網絡傳輸數據的序列化反序列化;好比說JSONXML二進制數據等。bash

  • 處理層(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.apache.org/,一個多小時就能夠理解Thrift IDL文件的語法使用。

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

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文件

a). 下載0.10.0Thrift IDL編譯器,下載地址:http://thrift.apache.org/docs/install。 經過編譯生成器生成.java接口的類文件。

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程序如何編寫!

相關連接

  1. Apache Thrift系列詳解(一) - 概述與入門

  2. Apache Thrift系列詳解(二) - 網絡服務模型

  3. Apache Thrift系列詳解(三) - 序列化機制


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

image

本賬號將持續分享後端技術乾貨,包括虛擬機基礎,多線程編程,高性能框架,異步、緩存和消息中間件,分佈式和微服務,架構學習和進階等學習資料和文章。

相關文章
相關標籤/搜索