Thrift之服務模型和序列化機制

一、Thrift介紹

Thrift是一個軟件框架,用來進行可擴展且跨語言的服務的開發。它結合了功能強大的軟件堆棧和代碼生成引擎。其允許你定義一個簡單的定義文件中的數據類型和服務接口。以作為輸入文件,編譯器生成代碼用來方便地生成RPC客戶端和服務器通信的無縫跨編程語言。

二、Thrift基礎架構

Thrift是一個客戶端和服務端的架構體系,數據通過socket傳輸;

具有自己內部定義的傳輸協議規範(TProtocol)和傳輸數據標準(TTransports);

通過IDL腳本對傳輸數據的數據結構(struct) 和傳輸數據的業務邏輯(service)根據不同的運行環境快速的構建相應的代碼;

通過自己內部的序列化機制對傳輸的數據進行簡化和壓縮提高高併發、 大型系統中數據交互的性能。

  • Thrift 支持的數據類型

基本類型:
  bool: 布爾值
  byte: 8位有符號整數
  i16: 16位有符號整數
  i32: 32位有符號整數
  i64: 64位有符號整數
  double: 64位浮點數
  string: UTF-8編碼的字符串
  binary: 二進制串
結構體類型:
  struct: 定義的結構體對象
容器類型:


  list: 有序元素列表
  set: 無序無重複元素集合
  map: 有序的key/value集合
異常類型:
  exception: 異常類型
服務類型:
  service: 具體對應服務的類

  • 協議

Thrift可以讓你選擇客戶端與服務端之間傳輸通信協議的類別,在傳輸協議上總體上劃分為文本(text)和二進制(binary)傳輸協議, 為節約帶寬,提供傳輸效率,一般情況下使用二進制類型的傳輸協議為多數,但有時會還是會使用基於文本類型的協議,這需要根據項目/產品中的實際需求:
1、TBinaryProtocol – 二進制編碼格式進行數據傳輸。
2、TCompactProtocol – 這種協議非常有效的,使用Variable-Length Quantity (VLQ) 編碼對數據進行壓縮。
3、TJSONProtocol – 使用JSON的數據編碼協議進行數據傳輸。
4、TSimpleJSONProtocol – 這種節約只提供JSON只寫的協議,適用於通過腳本語言解析
5、TDebugProtocol – 在開發的過程中幫助開發人員調試用的,以文本的形式展現方便閱讀。

  • 傳輸層

1、TSocket- 使用堵塞式I/O進行傳輸,也是最常見的模式。
2、TFramedTransport- 使用非阻塞方式,按塊的大小,進行傳輸,類似於Java中的NIO。
3、TFileTransport- 顧名思義按照文件的方式進程傳輸,雖然這種方式不提供Java的實現,但是實現起來非常簡單。
4、TMemoryTransport- 使用內存I/O,就好比Java中的ByteArrayOutputStream實現。

5、TZlibTransport- 使用執行zlib壓縮,不提供Java的實現。


三、Thrift網絡服務模型

Thrif 提供網絡模型:單線程、多線程、事件驅動。從另一個角度劃分為:阻塞服務模型、非阻塞服務模型。

  • 阻塞服務

TSimpleServer

TThreadPoolServer

  • 非阻塞服務模型

TNonblockingServer

THsHaServer

TThreadedSelectorServer

1、TSimpleServer

TSimpleServer實現是非常的簡單,循環監聽新請求的到來並完成對請求的處理,是個單線程阻塞模型。由於是一次只能接收和處理一個socket連接,效率比較低,在實際開發過程中很少用到它。

2、TThreadPoolServer

ThreadPoolServer為解決了TSimpleServer不支持併發和多連接的問題, 引入了線程池。但仍然是多線程阻塞模式即實現的模型是One Thread Per Connection。

線程池採用能線程數可伸縮的模式,線程池中的隊列採用同步隊列(SynchronousQueue)。

ThreadPoolServer拆分了監聽線程(accept)和處理客戶端連接的工作線程(worker), 監聽線程每接到一個客戶端, 就投給線程池去處理。

<code>import org.apache.thrift.TException;
import org.apache.thrift.TProcessor;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.server.TServer;
import org.apache.thrift.server.TThreadPoolServer;
import org.apache.thrift.transport.TServerSocket;

/**
 * 註冊服務端
 *     線程池服務模型,使用標準的阻塞式IO,預先創建一組線程處理請求
 */
public class ThriftTThreadPoolServer {
    // 註冊端口
    public static final int SERVER_PORT = 8080;

    public static void main(String[] args) throws TException {
        TProcessor tprocessor = new HelloWorld.Processor(new HelloWorldImpl());
        // 阻塞IO
        TServerSocket serverTransport = new TServerSocket(SERVER_PORT);
        // 多線程服務模型
        TThreadPoolServer.Args tArgs = new TThreadPoolServer.Args(serverTransport);
        tArgs.processor(tprocessor);
        // 客戶端協議要一致
        tArgs.protocolFactory(new TBinaryProtocol.Factory());
         // 線程池服務模型,使用標準的阻塞式IO,預先創建一組線程處理請求。
        TServer server = new TThreadPoolServer(tArgs);
        System.out.println("Hello TThreadPoolServer....");
        server.serve(); // 啟動服務
    }
}/<code>
<code>import org.apache.thrift.TException;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.protocol.TProtocol;
import org.apache.thrift.transport.TSocket;
import org.apache.thrift.transport.TTransport;

/**
 * 客戶端調用
 * 阻塞
 */
public class BlockClient {
    public static final String SERVER_IP = "127.0.0.1";
    public static final int SERVER_PORT = 8080;
    public static final int TIMEOUT = 30000;

    public static void main(String[] args) throws TException {
        // 設置傳輸通道
        TTransport transport = new TSocket(SERVER_IP, SERVER_PORT, TIMEOUT);
        // 協議要和服務端一致
        // 使用二進制協議 
        TProtocol protocol = new TBinaryProtocol(transport);
        // 創建Client
        HelloWorld.Client client = new HelloWorld.Client(protocol);
        transport.open();
        String result = client.sayHello("thrift");
        System.out.println("result : " + result);
        // 關閉資源
        transport.close();
    }
}/<code> 


TThreadPoolServer模式優點:

線程池模式中,數據讀取和業務處理都交由線程池完成,主線程只負責監聽新連接,因此在併發量較大時新連接也能夠被及時接受。線程池模式比較適合服務器端能預知最多有多少個客戶端併發的情況,這時每個請求都能被業務線程池及時處理,性能也非常高。

TThreadPoolServer模式缺點:

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

3、TNonblockingServer

TNonblockingServer採用單線程非阻塞(NIO)的模式, 藉助Channel/Selector機制, 採用IO事件模型來處理。所有的socket都被註冊到selector中,在一個線程中通過seletor循環監控所有的socket,每次selector結束時,處理所有的處於就緒狀態的socket,對於有數據到來的socket進行數據讀取操作,對於有數據發送的socket則進行數據發送,對於監聽socket則產生一個新業務socket並將其註冊到selector中。

<code>private void select() {
  try {
    selector.select();  // wait for io events.
    // process the io events we received
    Iterator selectedKeys = selector.selectedKeys().iterator();
    while (!stopped_ && selectedKeys.hasNext()) {
      SelectionKey key = selectedKeys.next();
      selectedKeys.remove();
      if (key.isAcceptable()) {
        handleAccept(); // deal with accept
      } else if (key.isReadable()) {
        handleRead(key);    // deal with reads
      } else if (key.isWritable()) {
        handleWrite(key); // deal with writes
      }
    }
  } catch (IOException e) {
  }
}/<code>

select代碼裡對accept/read/write等IO事件進行監控和處理, 唯一可惜的這個單線程處理. 當遇到handler裡有阻塞的操作時, 會導致整個服務被阻塞住。

<code>import org.apache.thrift.TException;
import org.apache.thrift.TProcessor;
import org.apache.thrift.protocol.TCompactProtocol;
import org.apache.thrift.server.TNonblockingServer;
import org.apache.thrift.server.TServer;
import org.apache.thrift.transport.TFramedTransport;
import org.apache.thrift.transport.TNonblockingServerSocket;

/**
 * 註冊服務端
 *     使用非阻塞式IO,服務端和客戶端需要指定 TFramedTransport 數據傳輸的方式  
 */
public class ThriftTNonblockingServer {
    // 註冊端口
    public static final int SERVER_PORT = 8080;

    public static void main(String[] args) throws TException {
        // 處理器
        TProcessor tprocessor = new HelloWorld.Processor(new HelloWorldImpl());
        // 傳輸通道 - 非阻塞方式  
        TNonblockingServerSocket serverTransport = new TNonblockingServerSocket(SERVER_PORT);
        // 異步IO,需要使用TFramedTransport,它將分塊緩存讀取。  
        TNonblockingServer.Args tArgs = new TNonblockingServer.Args(serverTransport);
        tArgs.processor(tprocessor);
        tArgs.transportFactory(new TFramedTransport.Factory());
        // 使用高密度二進制協議 
        tArgs.protocolFactory(new TCompactProtocol.Factory());
        // 使用非阻塞式IO,服務端和客戶端需要指定TFramedTransport數據傳輸的方式
        TServer server = new TNonblockingServer(tArgs);
        System.out.println("Hello TNonblockingServer....");
        server.serve(); // 啟動服務
    }
}/<code>
<code>import org.apache.thrift.TException;
import org.apache.thrift.protocol.TCompactProtocol;
import org.apache.thrift.protocol.TProtocol;
import org.apache.thrift.transport.TFramedTransport;
import org.apache.thrift.transport.TSocket;
import org.apache.thrift.transport.TTransport;

/**
 * 客戶端調用
 * 非阻塞
 */
public class NonblockingClient {
    public static final String SERVER_IP = "127.0.0.1";
    public static final int SERVER_PORT = 8080;
    public static final int TIMEOUT = 30000;

    public static void main(String[] args) throws TException {
        // 設置傳輸通道,對於非阻塞服務,需要使用TFramedTransport,它將數據分塊發送  
        TTransport transport = new TFramedTransport(new TSocket(SERVER_IP, SERVER_PORT, TIMEOUT));
        // 協議要和服務端一致
        //HelloTNonblockingServer
        // 使用高密度二進制協議 
        TProtocol protocol = new TCompactProtocol(transport);
        
        // 使用二進制協議 
        //TProtocol protocol = new TBinaryProtocol(transport);
        HelloWorld.Client client = new HelloWorld.Client(protocol);
        transport.open();
        String result = client.sayHello("jack");
        System.out.println("result : " + result);
        // 關閉資源
        transport.close();
    }
}/<code>

TNonblockingServer模式優點:

相比於TSimpleServer效率提升主要體現在IO多路複用上,TNonblockingServer採用非阻塞IO,同時監控多個socket的狀態變化;

TNonblockingServer模式缺點:

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

4、THsHaServer

THsHaServer類是TNonblockingServer類的子類,為解決TNonblockingServer的缺點, THsHa引入了線程池去處理, 其模型把讀寫任務放到線程池去處理即多線程非阻塞模式。HsHa是: Half-sync/Half-async的處理模式, Half-aysnc是在處理IO事件上(accept/read/write io), Half-sync用於handler對rpc的同步處理上。因此可以認為THsHaServer半同步半異步。

THsHaServer的優點:

與TNonblockingServer模式相比,THsHaServer在完成數據讀取之後,將業務處理過程交由一個線程池來完成,主線程直接返回進行下一次循環操作,效率大大提升;

THsHaServer的缺點:

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

5、TThreadedSelectorServer

TThreadedSelectorServer是大家廣泛採用的服務模型,其多線程服務器端使用非堵塞式I/O模型,是對TNonblockingServer的擴充, 其分離了Accept和Read/Write的Selector線程, 同時引入Worker工作線程池。

(1)一個AcceptThread線程對象,專門用於處理監聽socket上的新連接;

(2)若干個SelectorThread對象專門用於處理業務socket的網絡I/O操作,所有網絡數據的讀寫均是有這些線程來完成;

(3)一個負載均衡器SelectorThreadLoadBalancer對象,主要用於AcceptThread線程接收到一個新socket連接請求時,決定將這個新連接請求分配給哪個SelectorThread線程。

(4)一個ExecutorService類型的工作線程池,在SelectorThread線程中,監聽到有業務socket中有調用請求過來,則將請求讀取之後,交個ExecutorService線程池中的線程完成此次調用的具體執行


Thrift之服務模型和序列化機制


MainReactor就是Accept線程, 用於監聽客戶端連接, SubReactor採用IO事件線程(多個), 主要負責對所有客戶端的IO讀寫事件進行處理. 而Worker工作線程主要用於處理每個rpc請求的handler回調處理(這部分是同步的)。因此其也是Half-Sync/Half-Async(半異步-半同步)的 。

TThreadedSelectorServer模式對於大部分應用場景性能都不會差,因為其有一個專門的線程AcceptThread用於處理新連接請求,因此能夠及時響應大量併發連接請求;另外它將網絡I/O操作分散到多個SelectorThread線程中來完成,因此能夠快速對網絡I/O進行讀寫操作,能夠很好地應對網絡I/O較多的情況。

<code>import org.apache.thrift.TException;
import org.apache.thrift.TProcessor;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.server.TServer;
import org.apache.thrift.server.TThreadedSelectorServer;
import org.apache.thrift.transport.TFramedTransport;
import org.apache.thrift.transport.TNonblockingServerSocket;

/**
 * TFramedTransport 數據傳輸的方式
 */
public class ThriftTThreadedSelectorServer {
    // 註冊端口
    public static final int SERVER_PORT = 8080;

    public static void main(String[] args) throws TException {        
        TProcessor tprocessor = new HelloWorld.Processor(new HelloWorldImpl());
        
        TThreadedSelectorServer.Args tArgs = new TThreadedSelectorServer.Args(serverTransport);
        tArgs.processor(tprocessor);
        tArgs.transportFactory(new TFramedTransport.Factory());
        // 二進制協議
        tArgs.protocolFactory(new TBinaryProtocol.Factory());
        
        TServer server = new TThreadedSelectorServer(tArgs);
        System.out.println("Hello TThreadedSelectorServer....");
        server.serve(); // 啟動服務
        
    }

}/<code>


四、Thrift序列化機制

Thrift提供了可擴展序列化機制, 不但兼容性好而且壓縮率高。

thrift 數據格式描述

thrift的向後兼容性(Version)藉助屬性標識(數字編號id + 屬性類型type)來實現, 可以

理解為在序列化後(屬性數據存儲由 field_name:field_value => id+type:field_value)

我們定義IDL文件形如

<code>namespace java stu.thrift;
 
struct User {
  1: required string name
  2: required string address
}/<code>

是不是和我們使用序列化的數據xml/json有了很大的差別,那麼我們來比較是常見的數據傳輸格式

數據傳輸格式類型優點缺點Xml文本

1、良好的可讀性

2、序列化的數據包含完整的結構

3、調整不同屬性的順序對序列化/反序列化不影響

1、數據傳輸量大

2、不支持二進制數據類型

Json文本

1、良好的可讀性

2、調整不同屬性的順序對序列化/反序列化不影響

1、丟棄了類型信息, 比如"price":100, 對price類型是int/double解析有二義性

2、不支持二進制數據類型

Thrift二進制高效

1、不宜讀

2、向後兼容有一定的約定限制,採用id遞增的方式標識並以optional修飾來添加

Google Protobuf二進制高效

1、不宜讀

2、向後兼容有一定的約定限制


分享到:


相關文章: