Thrift協議的服務模型

爆米花9958發表於2017-03-08
一、Thrift介紹                                                                  
    Thrift是一個軟體框架,用來進行可擴充套件且跨語言的服務的開發。它結合了功能強大的軟體堆疊和程式碼生成引擎。其允許你定義一個簡單的定義檔案中的資料型別和服務介面。以作為輸入檔案,編譯器生成程式碼用來方便地生成RPC客戶端和伺服器通訊的無縫跨程式語言。
二、Thrift基礎架構                                                           
  • Thrift 支援的資料型別
1、基本型別
  bool: 布林值
  byte: 8位有符號整數
  i16: 16位有符號整數
  i32: 32位有符號整數
  i64: 64位有符號整數
  double: 64位浮點數
  string: UTF-8編碼的字串
  binary: 二進位制串
2、結構體型別
  struct: 定義的結構體物件
3、容器型別
Thrift容器與型別密切相關,它與當前流行程式語言提供的容器型別相對應,採用java泛型風格表示。Thrift提供了3種容器型別:
  list<t1>: 一系列t1型別的元素組成的有序表,元素可以重複
  set: <t1>:一系列t1型別的元素組成的無序表,元素唯一
  map<t1,t2>:key/value對(key的型別是t1且唯一,value型別是t2)
容器中的元素型別可以是除了service意外的任何合法thrift型別(包括結構體和異常)
4、異常型別:
  exception: 異常型別
5、服務型別:
  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 提供網路模型:單執行緒、多執行緒、事件驅動。從另一個角度劃分為:阻塞服務模型、非阻塞服務模型。
  • 阻塞服務
  1. TSimpleServer:簡單的單執行緒服務模型,主要用於測試
  2. TThreadPoolServer:多執行緒服務模型,使用標準的阻塞式IO,預先建立一組執行緒處理請求
  • 非阻塞服務模型
  1. TNonblockingServer:多執行緒服務模型,使用非阻塞式IO(需使用TFramedTransport資料傳輸方式),只有一個執行緒來處理消
  2. THsHaServer:半同步半非同步的服務模型,一個單獨的執行緒用來處理網路I/O,一個worker執行緒池用來進行訊息的處理
  3. TThreadedSelectorServer:允許你用多個執行緒來處理網路I/O。它維護了兩個執行緒池,一個用來處理網路I/O,另一個用來進行請求的處理
     1、TSimpleServer
      TSimpleServer實現是非常的簡單,迴圈監聽新請求的到來並完成對請求的處理,是個單執行緒阻塞模型。由於是一次只能接收和處理一個socket連線,效率比較低,在實際開發過程中很少用到它。
/** * 註冊服務端 *  單執行緒服務模型,使用標準的阻塞式IO,只有一個執行緒處理請求 */
public static void startSimpleServer(AdditionService.Processor<AdditionServiceHandler> processor) {
    try {
        TServerTransport serverTransport = new TServerSocket(9090);// 設定服務埠
        // 單執行緒服務模型
        TSimpleServer.Args tArgs = new TSimpleServer.Args(serverTransport);
        tArgs.processor(processor);
        // 客戶端協議要一致
        tArgs.protocolFactory(new TBinaryProtocol.Factory());
        TServer server = new TSimpleServer(tArgs);
        System.out.println("Starting the simple server...");
        server.serve();
    } catch (Exception e) {
        e.printStackTrace();
    }
}
     2、TThreadPoolServer
       ThreadPoolServer為解決了TSimpleServer不支援併發和多連線的問題, 引入了執行緒池。但仍然是多執行緒阻塞模式即實現的模型是One Thread Per Connection。
       執行緒池採用能執行緒數可伸縮的模式,執行緒池中的佇列採用同步佇列(SynchronousQueue)。
       ThreadPoolServer拆分了監聽執行緒(accept)和處理客戶端連線的工作執行緒(worker), 監聽執行緒每接到一個客戶端, 就投給執行緒池去處理。
/**
 * 註冊服務端
 *     執行緒池服務模型,使用標準的阻塞式IO,預先建立一組執行緒處理請求
 */
public static void startMultipleServer(AdditionService.Processor<AdditionServiceHandler> processor) {
    try {
        TServerTransport serverTransport = new TServerSocket(9090);// 設定服務埠
        //執行緒池服務模型,使用標準的阻塞式IO,預先建立一組執行緒處理請求。
        TThreadPoolServer.Args tArgs = new TThreadPoolServer.Args(serverTransport);
        tArgs.processor(processor);
        // 客戶端協議要一致
        tArgs.protocolFactory(new TBinaryProtocol.Factory());
        TServer server = new TThreadPoolServer(tArgs);
        System.out.println("Hello TThreadPoolServer....");
        server.serve(); // 啟動服務

    } catch (Exception e) {
        e.printStackTrace();
    }
}

這兩個服務端可以處理同樣的客戶端:
/**
 * 客戶端呼叫
 * 阻塞
 * Created by Administrator on 2017/1/12.
 */
public class AdditionClient {
    public static void main(String[] args) {
        try {
            TTransport transport;
            // 設定傳輸通道
            transport = new TSocket("localhost", 9090);//使用堵塞式I/O進行傳輸
            transport.open();
            // 協議要和服務端一致
            // 使用二進位制協議
            TProtocol protocol = new TBinaryProtocol(transport);
            AdditionService.Client client = new AdditionService.Client(protocol);
            System.out.println(client.add(100, 200));
            transport.close();
        } catch (TTransportException e) {
            e.printStackTrace();
        } catch (TException x) {
            x.printStackTrace();
        }
    }

}
  TThreadPoolServer模式優點:
       執行緒池模式中,資料讀取和業務處理都交由執行緒池完成,主執行緒只負責監聽新連線,因此在併發量較大時新連線也能夠被及時接受。執行緒池模式比較適合伺服器端能預知最多有多少個客戶端併發的情況,這時每個請求都能被業務執行緒池及時處理,效能也非常高。
    TThreadPoolServer模式缺點:
       執行緒池模式的處理能力受限於執行緒池的工作能力,當併發請求數大於執行緒池中的執行緒數時,新請求也只能排隊等待。
3、TNonblockingServer
       TNonblockingServer採用單執行緒非阻塞(NIO)的模式, 藉助Channel/Selector機制, 採用IO事件模型來處理。所有的socket都被註冊到selector中,在一個執行緒中通過seletor迴圈監控所有的socket,每次selector結束時,處理所有的處於就緒狀態的socket,對於有資料到來的socket進行資料讀取操作,對於有資料傳送的socket則進行資料傳送,對於監聽socket則產生一個新業務socket並將其註冊到selector中。
/**
 * TNonblockingServer採用單執行緒非阻塞(NIO)的模式
 * @param processor
 */
public static void nonBlockingServer(AdditionService.Processor<AdditionServiceHandler> processor) {
    try {
        // 傳輸通道 - 非阻塞方式
        TNonblockingServerSocket serverTransport = new TNonblockingServerSocket(9090);// 設定服務埠
        // 非同步IO,需要使用TFramedTransport,它將分塊快取讀取。
        TNonblockingServer.Args tArgs = new TNonblockingServer.Args(serverTransport);
        tArgs.processor(processor);
        tArgs.transportFactory(new TFramedTransport.Factory());
        // 使用高密度二進位制協議
        tArgs.protocolFactory(new TCompactProtocol.Factory());
        // 使用非阻塞式IO,服務端和客戶端需要指定TFramedTransport資料傳輸的方式
        TServer server = new TNonblockingServer(tArgs);
        System.out.println("Starting the simple server...");
        server.serve();// 啟動服務
    } catch (Exception e) {
        e.printStackTrace();
    }
}
檢視TNonblockingServer的原始碼,我們可以看到select()方法:
private void select() {
    try {
        this.selector.select();
        Iterator e = this.selector.selectedKeys().iterator();

        while(!TNonblockingServer.this.stopped_ && e.hasNext()) {
            SelectionKey key = (SelectionKey)e.next();
            e.remove();
            if(!key.isValid()) {
                this.cleanupSelectionKey(key);
            } else if(key.isAcceptable()) {
                this.handleAccept();
            } else if(key.isReadable()) {
                this.handleRead(key);
            } else if(key.isWritable()) {
                this.handleWrite(key);
            } else {
                TNonblockingServer.this.LOGGER.warn("Unexpected state in select! " + key.interestOps());
            }
        }
    } catch (IOException var3) {
        TNonblockingServer.this.LOGGER.warn("Got an IOException while selecting!", var3);
    }

}
select程式碼裡對accept/read/write等IO事件進行監控和處理, 但由於這是單執行緒處理,所以當遇到handler裡有阻塞的操作時, 會導致整個服務被阻塞住。
 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
 *  THsHaServer類是TNonblockingServer類的子類
 * @param processor
 */
public static void startTHsHaServer(AdditionService.Processor<AdditionServiceHandler> processor) {
    try {
        // 傳輸通道 - 非阻塞方式
        TNonblockingServerSocket serverTransport = new TNonblockingServerSocket(9090);// 設定服務埠
        // 非同步IO,需要使用TFramedTransport,它將分塊快取讀取。
        THsHaServer.Args tArgs = new THsHaServer.Args(serverTransport);
        tArgs.processor(processor);
        tArgs.transportFactory(new TFramedTransport.Factory());
        // 使用高密度二進位制協議
        tArgs.protocolFactory(new TCompactProtocol.Factory());
        // 使用非阻塞式IO,服務端和客戶端需要指定TFramedTransport資料傳輸的方式
        TServer server = new THsHaServer(tArgs);
        System.out.println("Starting the simple server...");
        server.serve();// 啟動服務
    } catch (Exception e) {
        e.printStackTrace();
    }
}
     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執行緒池中的執行緒完成此次呼叫的具體執行

MainReactor就是Accept執行緒, 用於監聽客戶端連線, SubReactor採用IO事件執行緒(多個), 主要負責對所有客戶端的IO讀寫事件進行處理. 而Worker工作執行緒主要用於處理每個rpc請求的handler回撥處理(這部分是同步的)。因此其也是Half-Sync/Half-Async(半非同步-半同步)的 。
    TThreadedSelectorServer模式對於大部分應用場景效能都不會差,因為其有一個專門的執行緒AcceptThread用於處理新連線請求,因此能夠及時響應大量併發連線請求;另外它將網路I/O操作分散到多個SelectorThread執行緒中來完成,因此能夠快速對網路I/O進行讀寫操作,能夠很好地應對網路I/O較多的情況。
/**
 *  TThreadedSelectorServer
 *  是多執行緒伺服器端使用非堵塞式I/O模型
 * @param processor
 */
    public static void startTThreadedSelectorServer(AdditionService.Processor<AdditionServiceHandler> processor) {
        try {
            // 傳輸通道 - 非阻塞方式
            TNonblockingServerSocket serverTransport = new TNonblockingServerSocket(9090);// 設定服務埠
            // 非同步IO,需要使用TFramedTransport,它將分塊快取讀取。
            TThreadedSelectorServer.Args tArgs = new TThreadedSelectorServer.Args(serverTransport);
            tArgs.processor(processor);
            tArgs.transportFactory(new TFramedTransport.Factory());
            // 使用高密度二進位制協議
            tArgs.protocolFactory(new TCompactProtocol.Factory());
            // 使用非阻塞式IO,服務端和客戶端需要指定TFramedTransport資料傳輸的方式
            TServer server = new TThreadedSelectorServer(tArgs);
            System.out.println("Starting the simple server...");
            server.serve();// 啟動服務
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
上面三種非阻塞服務模型可以處理這樣的客戶端:
public static void main(String[] args) {
    try {
        // 設定傳輸通道,對於非阻塞服務,需要使用TFramedTransport,它將資料分塊傳送
        TSocket socket = new TSocket("localhost", 9090);//使用堵塞式I/O進行傳輸
        //使用非阻塞方式,按塊的大小,進行傳輸,類似於Java中的NIO
        TTransport transport = new TFramedTransport(socket);
        // 協議要和服務端一致
        // 使用高密度二進位制協議
        TProtocol protocol = new TCompactProtocol(transport);
        AdditionService.Client client = new AdditionService.Client(protocol);
        transport.open();
        System.out.println(client.add(100, 100));

        transport.close();
    } catch (TTransportException e) {
        e.printStackTrace();
    } catch (TException x) {
        x.printStackTrace();
    }
}

相關文章