Thrift基本原理

Thrift是什么?

Thrift是Facebook于2007年開發(fā)的跨語言的rpc服框架,提供多語言的編譯功能,并提供多種服務(wù)器工作模式;用戶通過Thrift的IDL(接口定義語言)來描述接口函數(shù)及數(shù)據(jù)類型,然后通過Thrift的編譯環(huán)境生成各種語言類型的接口文件,用戶可以根據(jù)自己的需要采用不同的語言開發(fā)客戶端代碼和服務(wù)器端代碼。

Thrift為服務(wù)器端程序提供了很多的工作模式,例如:線程池模型、非阻塞模型等等,可以根據(jù)自己的實(shí)際應(yīng)用場景選擇一種工作模式高效地對外提供服務(wù);

  • (1)支持的傳輸格式
    TBinaryProtocol – 二進(jìn)制格式.
    TCompactProtocol – 壓縮格式
    TJSONProtocol – JSON格式
    TSimpleJSONProtocol –提供JSON只寫協(xié)議, 生成的文件很容易通過腳本語言解析。
    TDebugProtocol – 使用易懂的可讀的文本格式,以便于debug

  • (2) 支持的數(shù)據(jù)傳輸方式
    TSocket -阻塞式socker
    TFramedTransport – 以frame為單位進(jìn)行傳輸,非阻塞式服務(wù)中使用。
    TFileTransport – 以文件形式進(jìn)行傳輸。
    TMemoryTransport – 將內(nèi)存用于I/O. java實(shí)現(xiàn)時(shí)內(nèi)部實(shí)際使用了簡單的ByteArrayOutputStream。
    TZlibTransport – 使用zlib進(jìn)行壓縮, 與其他傳輸方式聯(lián)合使用。當(dāng)前無java實(shí)現(xiàn)。

  • (3)支持的服務(wù)模型
    TSimpleServer – 簡單的單線程服務(wù)模型,常用于測試
    TThreadPoolServer – 多線程服務(wù)模型,使用標(biāo)準(zhǔn)的阻塞式IO。
    TNonblockingServer – 多線程服務(wù)模型,使用非阻塞式IO(需使用TFramedTransport數(shù)據(jù)傳輸方式)

Thrift的使用

Thrift提供跨語言的服務(wù)框架,這種跨語言主要體現(xiàn)在它對多種語言的編譯功能的支持,用戶只需要使用IDL描述好接口函數(shù),只需要一條簡單的命令,Thrift就能夠把按照IDL格式描述的接口文件翻譯成各種語言版本。其實(shí),說搭建Thrift環(huán)境的時(shí)候,實(shí)際上最麻煩的就是搭建Thrift的編譯環(huán)境,Thrift的編譯和通常的編譯一樣經(jīng)過詞法分析、語法分析等等最終生成對應(yīng)語言的源碼文件,為了能夠支持對各種語言的編譯,你需要下載各種語言對應(yīng)的編譯時(shí)使用的包。

編寫IDL文件

使用Thrift開發(fā)程序,首先要做的事情就是使用IDL對接口進(jìn)行描述, 然后再使用Thrift的多語言編譯能力將接口的描述文件編譯成對應(yīng)語言的版本,本文中將IDL對接口的描述文件稱為“Thrift文件”
使用IDL對接口進(jìn)行描述的thrift文件命名一般都是以“.thrift”作為后綴:XXX.thrift,可以在該文件的開頭為該文件加上命名空間限制,格式為:namespace語言 命名空間的名字;例如:
namespace javacom.test.service
IDL文件中對所有接口函數(shù)的描述都放在service中,service的名字可以自己指定,該名字也將被用作生成的特定語言接口文件的名字,接口函數(shù)需要對參數(shù)使用序號(hào)標(biāo)號(hào),除最后一個(gè)接口函數(shù)外,要以“,”結(jié)束對函數(shù)的描述。
例如,下面一個(gè)IDL描述的Thrift文件(該Thrift文件的文件名為:test_service.thrift)的全部內(nèi)容:

namespace java com.test.service   
include "thrift_datatype.thrift"  
service TestThriftService  
{  
    /**  
    *value 中存放兩個(gè)字符串拼接之后的字符串  
    */  
    thrift_datatype.ResultStr getStr(1:string srcStr1, 2:string srcStr2),  
      
    thrift_datatype.ResultInt getInt(1:i32 val)     
}  

這里的TestThriftService就被用作生成的特定語言的文件名,例如我想用該Thrift文件生成一個(gè)java版本的接口文件,那么生成的java文件名就是:TestThriftService.java。

  • (1) 編寫IDL文件時(shí)需要注意的問題

[1]函數(shù)的參數(shù)要用數(shù)字依序標(biāo)好,序號(hào)從1開始,形式為:“序號(hào):參數(shù)名”;
[2]每個(gè)函數(shù)的最后要加上“,”,最后一個(gè)函數(shù)不加;
[3]在IDL中可以使用/……/添加注釋

  • (2) IDL支持的數(shù)據(jù)類型
    IDL大小寫敏感,它共支持以下幾種基本的數(shù)據(jù)類型:

[1]string, 字符串類型,注意是全部小寫形式;例如:string aString
[2]i16, 16位整形類型,例如:i16 aI16Val;
[3]i32,32位整形類型,對應(yīng)C/C++/java中的int類型;例如: I32 aIntVal
[4]i64,64位整形,對應(yīng)C/C++/java中的long類型;例如:I64 aLongVal
[5]byte,8位的字符類型,對應(yīng)C/C++中的char,java中的byte類型;例如:byte aByteVal
[6]bool, 布爾類型,對應(yīng)C/C++中的bool,java中的boolean類型; 例如:bool aBoolVal
[7]double,雙精度浮點(diǎn)類型,對應(yīng)C/C++/java中的double類型;例如:double aDoubleVal
[8]void,空類型,對應(yīng)C/C++/java中的void類型;該類型主要用作函數(shù)的返回值,例如:void testVoid(),

除上述基本類型外,ID還支持以下類型:

[1]map,map類型,例如,定義一個(gè)map對象:map<i32, i32> newmap;
[2]set,集合類型,例如,定義set<i32>對象:set<i32> aSet;
[3]list,鏈表類型,例如,定義一個(gè)list<i32>對象:list<i32> aList;

  • (3) 在Thrift文件中自定義數(shù)據(jù)類型
    在IDL中支持兩種自定義類型:枚舉類型和結(jié)構(gòu)體類型,具體如下:

[1]enum, 枚舉類型
[2]struct,自定義結(jié)構(gòu)體類型,在IDL中可以自己定義結(jié)構(gòu)體,對應(yīng)C中的struct,c++中的struct和class,java中的class。例如:

struct TestV1 {  
       1: i32 begin_in_both,  
       3: string old_string,  
       12: i32 end_in_both  
}  

注意,在struct定義結(jié)構(gòu)體時(shí)需要對每個(gè)結(jié)構(gòu)體成員用序號(hào)標(biāo)識(shí):“序號(hào): ”。

生成Thrift服務(wù)接口文件

搭建Thrift編譯環(huán)境之后,使用下面命令即可將IDL文件編譯成對應(yīng)語言的接口文件:
thrift --gen <language> <Thrift filename>
例如:如果使用上面的thrift文件(見上面的代碼2.1):test_service.thrift生成一個(gè)java語言的接口文件,則只需在搭建好thrift編譯環(huán)境的機(jī)子上,執(zhí)行如下命令即可:
thrift --gen java test_service.thrift

編寫服務(wù)器端的java代碼

  • 將生成的java接口文件TestThriftService.java拷貝到自己的工程文件中;
  • 訪問器程序需實(shí)現(xiàn)TestThriftService.Iface接口,在實(shí)現(xiàn)接口中完成自己要提供的服務(wù):
  • 服務(wù)器端啟動(dòng)thrift服務(wù)框架的程序

Thrift對外提供幾種工作模式:

  • TSimpleServer、TNonblockingServer、TThreadPoolServer、TThreadedSelectorServer等模式,每種服務(wù)模式的通信方式不一樣,因此在服務(wù)啟動(dòng)時(shí)使用了那種服務(wù)模式,客戶端程序也需要采用對應(yīng)的通信方式。

  • Thrift支持多種通信協(xié)議格式:TCompactProtocol、TBinaryProtocol、TJSONProtocol等,因此,在使用Thrift框架時(shí),客戶端程序與服務(wù)器端程序所使用的通信協(xié)議一定要一致,否則便無法正常通信。

服務(wù)器端創(chuàng)建并啟動(dòng)Thrift服務(wù)框架的過程為:

  • [1]為自己的服務(wù)實(shí)現(xiàn)類定義一個(gè)對象,如代碼2.3中的:
    TestThriftServiceImplm_myService =newTestThriftServiceImpl();
    這里的TestThriftServiceImpl類就是代碼2.2中我們自己定義的服務(wù)器端對各服務(wù)接口的實(shí)現(xiàn)類。
  • [2]定義一個(gè)TProcess對象,在根據(jù)Thrift文件生成java源碼接口文件TestThriftService.java中,Thrift已經(jīng)自動(dòng)為我們定義了一個(gè)Processor;后續(xù)節(jié)中將對這個(gè)TProcess類的功能進(jìn)行詳細(xì)描述;如代碼2.3中的:
    TProcessor tProcessor = NewTestThriftService.Processor<TestThriftService.Iface>(m_myService);
  • [3]定義一個(gè)TNonblockingServerSocket對象,用于tcp的socket通信,如代碼2.3中的:
    TNonblockingServerSocketnioSocket = newTNonblockingServerSocket(m_thriftPort);
    在創(chuàng)建server端socket時(shí)需要指明監(jiān)聽端口號(hào),即上面的變量:m_thriftPort。
  • [4]定義TNonblockingServer所需的參數(shù)對象TNonblockingServer.Args;并設(shè)置所需的參數(shù),如:
TNonblockingServer.Args tnbArgs = new TNonblockingServer.Args(nioSocket);  
tnbArgs.processor(tProcessor);  
tnbArgs.transportFactory(new TFramedTransport.Factory());  
tnbArgs.protocolFactory(new TBinaryProtocol.Factory());  

在TNonblockingServer模式下我們使用二進(jìn)制協(xié)議:TBinaryProtocol,通信方式采用TFramedTransport,即以幀的方式對數(shù)據(jù)進(jìn)行傳輸。

  • [5]定義TNonblockingServer對象,并啟動(dòng)該服務(wù),如代碼2.3中的:
m_server = new TNonblockingServer(tnbArgs);
…
m_server.serve();

編寫客戶端代碼

 m_transport = new TSocket(THRIFT_HOST, THRIFT_PORT,2000);  
 TProtocol protocol = new TBinaryProtocol(m_transport);  
 TestThriftService.Client testClient = new TestThriftService.Client(protocol);  
  
 try {  
     m_transport.open();  
       
     String res = testClient.getStr("test1", "test2");  
     System.out.println("res = " + res);  
     m_transport.close();  
} catch (TException e){  
        // TODO Auto-generated catch block  
        e.printStackTrace();  
}  

注意:

  • [1]在同步方式使用客戶端和服務(wù)器的時(shí)候,socket是被一個(gè)函數(shù)調(diào)用獨(dú)占的,不能多個(gè)調(diào)用同時(shí)使用一個(gè)socket,例如通過m_transport.open()打開一個(gè)socket,此時(shí)創(chuàng)建多個(gè)線程同時(shí)進(jìn)行函數(shù)調(diào)用,這時(shí)就會(huì)報(bào)錯(cuò),因?yàn)閟ocket在被一個(gè)調(diào)用占著的時(shí)候不能再使用;
  • [2]可以分時(shí)多次使用同一個(gè)socket進(jìn)行多次函數(shù)調(diào)用,即通過m_transport.open()打開一個(gè)socket之后,你可以發(fā)起一個(gè)調(diào)用,在這個(gè)次調(diào)用完成之后,再繼續(xù)調(diào)用其他函數(shù)而不需要再次通過m_transport.open()打開socket;

應(yīng)用技巧

  • (1) 為調(diào)用加上一個(gè)事務(wù)ID
    在分布式服務(wù)開發(fā)過程中,一次事件(事務(wù))的執(zhí)行可能跨越位于不同機(jī)子上多個(gè)服務(wù)程序,在后續(xù)維護(hù)過程中跟蹤log將變得非常麻煩,因此在系統(tǒng)設(shè)計(jì)的時(shí)候,系統(tǒng)的一個(gè)事務(wù)產(chǎn)生之處應(yīng)該產(chǎn)生一個(gè)系統(tǒng)唯一的事務(wù)ID,該ID在各服務(wù)程序之間進(jìn)行傳遞,讓一次事務(wù)在所有服務(wù)程序輸出的log都以此ID作為標(biāo)識(shí)。
    在使用Thrift開發(fā)服務(wù)器程序的時(shí)候,也應(yīng)該為每個(gè)接口函數(shù)提供一個(gè)事務(wù)ID的參數(shù),并且在服務(wù)器程序開發(fā)過程中,該ID應(yīng)該在內(nèi)部函數(shù)調(diào)用過程中也進(jìn)行傳遞,并且在日志輸出的時(shí)候都加上它,以便問題跟蹤。

  • (2) 封裝返回結(jié)果
    Thrift提供的RPC方式的服務(wù),使得調(diào)用方可以像調(diào)用自己的函數(shù)一樣調(diào)用Thrift服務(wù)提供的函數(shù);在使用Thrift開發(fā)過程中,盡量不要直接返回需要的數(shù)據(jù),而是將返回結(jié)果進(jìn)行封裝,例如上面的例子中的getStr函數(shù)就是直接返回了結(jié)果string,見Thrift文件test_service.thrift中對該函數(shù)的描述:
    stringgetStr(1:string srcStr1, 2:string srcStr2)
    在實(shí)際開發(fā)過程中,這是一種很不好的行為,在返回結(jié)果為null的時(shí)候還可能造成調(diào)用方產(chǎn)生異常,需要對返回結(jié)果進(jìn)行封裝,例如:

/*String類型返回結(jié)果*/  
struct ResultStr  
{  
  1: ThriftResult result,  
  2: string value  
}  

其中ThriftResult是自己定義的枚舉類型的返回結(jié)果,在這里可以根據(jù)自己的需要添加任何自己需要的返回結(jié)果類型:

enum ThriftResult  
{  
  SUCCESS,           /*成功*/  
  SERVER_UNWORKING,  /*服務(wù)器處于非Working狀態(tài)*/  
  NO_CONTENT,        /*請求結(jié)果不存在*/  
  PARAMETER_ERROR,   /*參數(shù)錯(cuò)誤*/  
  EXCEPTION,         /*內(nèi)部出現(xiàn)異常*/  
  INDEX_ERROR,       /*錯(cuò)誤的索引或者下標(biāo)值*/  
  UNKNOWN_ERROR,     /*未知錯(cuò)誤*/  
  DATA_NOT_COMPLETE,     /*數(shù)據(jù)不完全*/  
  INNER_ERROR,   /*內(nèi)部錯(cuò)誤*/  
}  

此時(shí)可以將上述定義的getStr函數(shù)修改為:
ResultStr getStr(1:string srcStr1, 2:string srcStr2)
在此函數(shù)中,任何時(shí)候都會(huì)返回一個(gè)ResultStr對象,無論異常還是正常情況,在出錯(cuò)時(shí)還可以通過ThriftResult返回出錯(cuò)的類型。

  • (3) 將服務(wù)與數(shù)據(jù)類型分開定義
    在使用Thrift開發(fā)一些中大型項(xiàng)目的時(shí)候,很多情況下都需要自己封裝數(shù)據(jù)結(jié)構(gòu),例如前面將返回結(jié)果進(jìn)行封裝的時(shí)候就定義了自己的數(shù)據(jù)類型ResultStr,此時(shí),將數(shù)據(jù)結(jié)構(gòu)和服務(wù)分開定義到不通的文件中,可以增加thrift文件的易讀性。例如:
    在thrift文件:thrift_datatype.thrift中定義數(shù)據(jù)類型,如:
namespace java com.browan.freepp.thriftdatatype  
const string VERSION = "1.0.1"  
/**為ThriftResult添加數(shù)據(jù)不完全和內(nèi)部錯(cuò)誤兩種類型  
*/  
  
/****************************************************************************************************  
* 定義返回值,  
* 枚舉類型ThriftResult,表示返回結(jié)果,成功或失敗,如果失敗,還可以表示失敗原因  
* 每種返回類型都對應(yīng)一個(gè)封裝的結(jié)構(gòu)體,該結(jié)構(gòu)體其命名遵循規(guī)則:"Result" + "具體操作結(jié)果類型",結(jié)構(gòu)體都包含兩部分內(nèi)容:  
* 第一部分為枚舉類型ThriftResult變量result,表示操作結(jié)果,可以 表示成功,或失敗,失敗時(shí)可以給出失敗原因  
* 第二部分的變量名為value,表示返回結(jié)果的內(nèi)容;  
*****************************************************************************************************/  
enum ThriftResult  
{  
  SUCCESS,           /*成功*/  
  SERVER_UNWORKING,  /*服務(wù)器處于非Working狀態(tài)*/  
  NO_CONTENT,        /*請求結(jié)果不存在*/  
  PARAMETER_ERROR,   /*參數(shù)錯(cuò)誤*/  
  EXCEPTION,         /*內(nèi)部出現(xiàn)異常*/  
  INDEX_ERROR,       /*錯(cuò)誤的索引或者下標(biāo)值*/  
  UNKNOWN_ERROR      /*未知錯(cuò)誤*/  
  DATA_NOT_COMPLETE      /*數(shù)據(jù)不完全*/  
  INNER_ERROR    /*內(nèi)部錯(cuò)誤*/  
}  
  
/*bool類型返回結(jié)果*/  
struct ResultBool   
{  
  1: ThriftResult result,  
  2: bool value  
}  
  
/*int類型返回結(jié)果*/  
struct ResultInt  
{  
  1: ThriftResult result,  
  2: i32 value  
}  
  
/*String類型返回結(jié)果*/  
struct ResultStr  
{  
  1: ThriftResult result,  
  2: string value  
}  
  
/*long類型返回結(jié)果*/  
struct ResultLong  
{  
  1: ThriftResult result,  
  2: i64 value  
}  
  
  
  
/*double類型返回結(jié)果*/  
struct ResultDouble  
{  
  1: ThriftResult result,  
  2: double value  
}  
  
/*list<string>類型返回結(jié)果*/  
struct ResultListStr   
{  
  1: ThriftResult result,  
  2: list<string> value  
}  
  
/*Set<string>類型返回結(jié)果*/  
struct ResultSetStr   
{  
  1: ThriftResult result,  
  2: set<string> value  
}  
  
/*map<string,string>類型返回結(jié)果*/  
struct ResultMapStrStr   
{  
  1: ThriftResult result,  
  2: map<string,string> value  
}  

在另外一個(gè)文件test_service.thrift中定義服務(wù)接口函數(shù),如下所示:

namespace java com.test.service  
  
include "thrift_datatype.thrift"  
  
service TestThriftService  
{  
  
    /**  
    *value 中存放兩個(gè)字符串拼接之后的字符串  
    */  
    thrift_datatype.ResultStr getStr(1:string srcStr1, 2:string srcStr2),  
      
    thrift_datatype.ResultInt getInt(1:i32 val)  
      
}  

  • (4) 為Thrift文件添加版本號(hào)
    在實(shí)際開發(fā)過程中,還可以為Thrift文件加上版本號(hào),以方便對thrift的版本進(jìn)行控制。

工作原理

普通的本地函數(shù)調(diào)用過程

Thrift的RPC調(diào)用過程

源碼分析

在thrift生成的服務(wù)接口文件中,共包含以下幾部分:
(1)異步客戶端類AsyncClient和異步接口AsyncIface,本節(jié)暫不涉及這些異步操作相關(guān)內(nèi)容;
(2)同步客戶端類Client和同步接口Iface,Client類繼承自TServiceClient,并實(shí)現(xiàn)了同步接口Iface;Iface就是根據(jù)thrift文件中所定義的接口函數(shù)所生成;Client類是在開發(fā)Thrift的客戶端程序時(shí)使用,Client類是Iface的客戶端存根實(shí)現(xiàn), Iface在開發(fā)Thrift服務(wù)器的時(shí)候要使用,Thrift的服務(wù)器端程序要實(shí)現(xiàn)接口Iface。
(3)Processor類,該類主要是開發(fā)Thrift服務(wù)器程序的時(shí)候使用,該類內(nèi)部定義了一個(gè)map,它保存了所有函數(shù)名到函數(shù)對象的映射,一旦Thrift接到一個(gè)函數(shù)調(diào)用請求,就從該map中根據(jù)函數(shù)名字找到該函數(shù)的函數(shù)對象,然后執(zhí)行它;
(4)參數(shù)類,為每個(gè)接口函數(shù)定義一個(gè)參數(shù)類,例如:為接口getInt產(chǎn)生一個(gè)參數(shù)類:getInt_args,一般情況下,接口函數(shù)參數(shù)類的命名方式為:接口函數(shù)名_args;
(5)返回值類,每個(gè)接口函數(shù)定義了一個(gè)返回值類,例如:為接口getInt產(chǎn)生一個(gè)返回值類:getInt_result,一般情況下,接口函數(shù)返回值類的命名方式為:接口函數(shù)名_result;
參數(shù)類和返回值類中有對數(shù)據(jù)的讀寫操作,在參數(shù)類中,將按照協(xié)議類將調(diào)用的函數(shù)名和參數(shù)進(jìn)行封裝,在返回值類中,將按照協(xié)議規(guī)定讀取數(shù)據(jù)。
Thrift調(diào)用過程中,Thrift客戶端和服務(wù)器之間主要用到傳輸層類、協(xié)議層類和處理類三個(gè)主要的核心類,這三個(gè)類的相互協(xié)作共同完成rpc的整個(gè)調(diào)用過程。在調(diào)用過程中將按照以下順序進(jìn)行協(xié)同工作:
(1) 將客戶端程序調(diào)用的函數(shù)名和參數(shù)傳遞給協(xié)議層(TProtocol),協(xié)議層將函數(shù)名和參數(shù)按照協(xié)議格式進(jìn)行封裝,然后封裝的結(jié)果交給下層的傳輸層。此處需要注意:要與Thrift服務(wù)器程序所使用的協(xié)議類型一樣,否則Thrift服務(wù)器程序便無法在其協(xié)議層進(jìn)行數(shù)據(jù)解析;
(2) 傳輸層(TTransport)將協(xié)議層傳遞過來的數(shù)據(jù)進(jìn)行處理,例如傳輸層的實(shí)現(xiàn)類TFramedTransport就是將數(shù)據(jù)封裝成幀的形式,即“數(shù)據(jù)長度+數(shù)據(jù)內(nèi)容”,然后將處理之后的數(shù)據(jù)通過網(wǎng)絡(luò)發(fā)送給Thrift服務(wù)器;此處也需要注意:要與Thrift服務(wù)器程序所采用的傳輸層的實(shí)現(xiàn)類一致,否則Thrift的傳輸層也無法將數(shù)據(jù)進(jìn)行逆向的處理;
(3) Thrift服務(wù)器通過傳輸層(TTransport)接收網(wǎng)絡(luò)上傳輸過來的調(diào)用請求數(shù)據(jù),然后將接收到的數(shù)據(jù)進(jìn)行逆向的處理,例如傳輸層的實(shí)現(xiàn)類TFramedTransport就是將“數(shù)據(jù)長度+數(shù)據(jù)內(nèi)容”形式的網(wǎng)絡(luò)數(shù)據(jù),轉(zhuǎn)成只有數(shù)據(jù)內(nèi)容的形式,然后再交付給Thrift服務(wù)器的協(xié)議類(TProtocol);
(4) Thrift服務(wù)端的協(xié)議類(TProtocol)將傳輸層處理之后的數(shù)據(jù)按照協(xié)議進(jìn)行解封裝,并將解封裝之后的數(shù)據(jù)交個(gè)Processor類進(jìn)行處理;
(5) Thrift服務(wù)端的Processor類根據(jù)協(xié)議層(TProtocol)解析的結(jié)果,按照函數(shù)名找到函數(shù)名所對應(yīng)的函數(shù)對象;
(6) Thrift服務(wù)端使用傳過來的參數(shù)調(diào)用這個(gè)找到的函數(shù)對象;
(7) Thrift服務(wù)端將函數(shù)對象執(zhí)行的結(jié)果交給協(xié)議層;
(8) Thrift服務(wù)器端的協(xié)議層將函數(shù)的執(zhí)行結(jié)果進(jìn)行協(xié)議封裝;
(9) Thrift服務(wù)器端的傳輸層將協(xié)議層封裝的結(jié)果進(jìn)行處理,例如封裝成幀,然后發(fā)送給Thrift客戶端程序;
(10) Thrift客戶端程序的傳輸層將收到的網(wǎng)絡(luò)結(jié)果進(jìn)行逆向處理,得到實(shí)際的協(xié)議數(shù)據(jù);
(11) Thrift客戶端的協(xié)議層將數(shù)據(jù)按照協(xié)議格式進(jìn)行解封裝,然后得到具體的函數(shù)執(zhí)行結(jié)果,并將其交付給調(diào)用函數(shù);

開發(fā)thrift客戶端和服務(wù)器端程序時(shí)需要用到三個(gè)類:傳輸類(TTransport)、協(xié)議接口(TProtocol)和處理類(Processor);在Thrift生成代碼的內(nèi)部,還需要將待傳輸?shù)膬?nèi)容封裝成消息類TMessage。

  • TMessage
    Thrift在客戶端和服務(wù)器端傳遞數(shù)據(jù)的時(shí)候(包括發(fā)送調(diào)用請求和返回執(zhí)行結(jié)果),都是將數(shù)據(jù)按照TMessage進(jìn)行組裝,然后發(fā)送;TMessage包括三部分:消息的名稱、消息的序列號(hào)和消息的類型,消息名稱為字符串類型,消息的序列號(hào)為32位的整形,消息的類型為byte類型,消息的類型共有如下17種。

  • 傳輸類(TTransport)
    傳輸類或其各種實(shí)現(xiàn)類,都是對I/O層的一個(gè)封裝,可更直觀的理解為它封裝了一個(gè)socket,不同的實(shí)現(xiàn)類有不同的封裝方式,例如TFramedTransport類,它里面還封裝了一個(gè)讀寫buf,在寫入的時(shí)候,數(shù)據(jù)都先寫到這個(gè)buf里面,等到寫完調(diào)用該類的flush函數(shù)的時(shí)候,它會(huì)將寫buf的內(nèi)容,封裝成幀再發(fā)送出去;
    TFramedTransport是對TTransport的繼承,由于tcp是基于字節(jié)流的方式進(jìn)行傳輸,因此這種基于幀的方式傳輸就要求在無頭無尾的字節(jié)流中每次寫入和讀出一個(gè)幀,TFramedTransport是按照下面的方式來組織幀的:每個(gè)幀都是按照4字節(jié)的幀長加上幀的內(nèi)容來組織,幀內(nèi)容就是我們要收發(fā)的數(shù)據(jù),如下:
    +---------------+---------------+
    | 4字節(jié)的幀長 | 幀的內(nèi)容 |
    +---------------+---------------+

  • 協(xié)議接口(TProtocol)
    提供了一組操作協(xié)議接口,主要用于規(guī)定采用哪種協(xié)議進(jìn)行數(shù)據(jù)的讀寫,它內(nèi)部包含一個(gè)傳輸類(TTransport)成員對象,通過TTransport對象從輸入輸出流中讀寫數(shù)據(jù);它規(guī)定了很多讀寫方式,例如:
    readByte()
    readDouble()
    readString()

    每種實(shí)現(xiàn)類都根據(jù)自己所實(shí)現(xiàn)的協(xié)議來完成TProtocol接口函數(shù)的功能,例如實(shí)現(xiàn)了TProtocol接口的TBinaryProtocol類,對于readDouble()函數(shù)就是按照二進(jìn)制的方式讀取出一個(gè)Double類型的數(shù)據(jù)。

Thrift服務(wù)器端幾種工作模式

Thrift為服務(wù)器端提供了多種工作模式,本文中將涉及以下5中工作模式:TSimpleServer、TNonblockingServer、THsHaServer、TThreadPoolServer、TThreadedSelectorServer,這5中工作模式的詳細(xì)工作原理如下:

TSimpleServer模式

TSimpleServer的工作模式只有一個(gè)工作線程,循環(huán)監(jiān)聽新請求的到來并完成對請求的處理,它只是在簡單的演示時(shí)候使用,它的工作方式如圖:

TSimpleServer的工作模式采用最簡單的阻塞IO,實(shí)現(xiàn)方法簡潔明了,便于理解,但是一次只能接收和處理一個(gè)socket連接,效率比較低,主要用于演示Thrift的工作過程,在實(shí)際開發(fā)過程中很少用到它。

TNonblockingServer模式

TNonblockingServer工作模式,該模式也是單線程工作,但是該模式采用NIO的方式,所有的socket都被注冊到selector中,在一個(gè)線程中通過seletor循環(huán)監(jiān)控所有的socket,每次selector結(jié)束時(shí),處理所有的處于就緒狀態(tài)的socket,對于有數(shù)據(jù)到來的socket進(jìn)行數(shù)據(jù)讀取操作,對于有數(shù)據(jù)發(fā)送的socket則進(jìn)行數(shù)據(jù)發(fā)送,對于監(jiān)聽socket則產(chǎn)生一個(gè)新業(yè)務(wù)socket并將其注冊到selector中,如下圖5.2所示:

  • TNonblockingServer模式優(yōu)點(diǎn):
    相比于TSimpleServer效率提升主要體現(xiàn)在IO多路復(fù)用上,TNonblockingServer采用非阻塞IO,同時(shí)監(jiān)控多個(gè)socket的狀態(tài)變化;
  • TNonblockingServer模式缺點(diǎn):
    TNonblockingServer模式在業(yè)務(wù)處理上還是采用單線程順序來完成,在業(yè)務(wù)處理比較復(fù)雜、耗時(shí)的時(shí)候,例如某些接口函數(shù)需要讀取數(shù)據(jù)庫執(zhí)行時(shí)間較長,此時(shí)該模式效率也不高,因?yàn)槎鄠€(gè)調(diào)用請求任務(wù)依然是順序一個(gè)接一個(gè)執(zhí)行。

THsHaServer模式(半同步半異步)

THsHaServer類是TNonblockingServer類的子類,在5.2節(jié)中的TNonblockingServer模式中,采用一個(gè)線程來完成對所有socket的監(jiān)聽和業(yè)務(wù)處理,造成了效率的低下,THsHaServer模式的引入則是部分解決了這些問題。THsHaServer模式中,引入一個(gè)線程池來專門進(jìn)行業(yè)務(wù)處理,如下圖5.3所示;

  • THsHaServer的優(yōu)點(diǎn):
    與TNonblockingServer模式相比,THsHaServer在完成數(shù)據(jù)讀取之后,將業(yè)務(wù)處理過程交由一個(gè)線程池來完成,主線程直接返回進(jìn)行下一次循環(huán)操作,效率大大提升;
  • THsHaServer的缺點(diǎn):
    由圖5.3可以看出,主線程需要完成對所有socket的監(jiān)聽以及數(shù)據(jù)讀寫的工作,當(dāng)并發(fā)請求數(shù)較大時(shí),且發(fā)送數(shù)據(jù)量較多時(shí),監(jiān)聽socket上新連接請求不能被及時(shí)接受。

TThreadPoolServer模式

TThreadPoolServer模式采用阻塞socket方式工作,,主線程負(fù)責(zé)阻塞式監(jiān)聽“監(jiān)聽socket”中是否有新socket到來,業(yè)務(wù)處理交由一個(gè)線程池來處理,如下圖5.4所示:

  • TThreadPoolServer模式優(yōu)點(diǎn):
    線程池模式中,數(shù)據(jù)讀取和業(yè)務(wù)處理都交由線程池完成,主線程只負(fù)責(zé)監(jiān)聽新連接,因此在并發(fā)量較大時(shí)新連接也能夠被及時(shí)接受。線程池模式比較適合服務(wù)器端能預(yù)知最多有多少個(gè)客戶端并發(fā)的情況,這時(shí)每個(gè)請求都能被業(yè)務(wù)線程池及時(shí)處理,性能也非常高。

  • TThreadPoolServer模式缺點(diǎn):
    線程池模式的處理能力受限于線程池的工作能力,當(dāng)并發(fā)請求數(shù)大于線程池中的線程數(shù)時(shí),新請求也只能排隊(duì)等待。

TThreadedSelectorServer

TThreadedSelectorServer模式是目前Thrift提供的最高級(jí)的模式,它內(nèi)部有如果幾個(gè)部分構(gòu)成:
(1) 一個(gè)AcceptThread線程對象,專門用于處理監(jiān)聽socket上的新連接;
(2) 若干個(gè)SelectorThread對象專門用于處理業(yè)務(wù)socket的網(wǎng)絡(luò)I/O操作,所有網(wǎng)絡(luò)數(shù)據(jù)的讀寫均是有這些線程來完成;
(3) 一個(gè)負(fù)載均衡器SelectorThreadLoadBalancer對象,主要用于AcceptThread線程接收到一個(gè)新socket連接請求時(shí),決定將這個(gè)新連接請求分配給哪個(gè)SelectorThread線程。
(4) 一個(gè)ExecutorService類型的工作線程池,在SelectorThread線程中,監(jiān)聽到有業(yè)務(wù)socket中有調(diào)用請求過來,則將請求讀取之后,交個(gè)ExecutorService線程池中的線程完成此次調(diào)用的具體執(zhí)行;

TThreadedSelectorServer模式中有一個(gè)專門的線程AcceptThread用于處理新連接請求,因此能夠及時(shí)響應(yīng)大量并發(fā)連接請求;另外它將網(wǎng)絡(luò)I/O操作分散到多個(gè)SelectorThread線程中來完成,因此能夠快速對網(wǎng)絡(luò)I/O進(jìn)行讀寫操作,能夠很好地應(yīng)對網(wǎng)絡(luò)I/O較多的情況;TThreadedSelectorServer對于大部分應(yīng)用場景性能都不會(huì)差,因此,如果實(shí)在不知道選擇哪種工作模式,使用TThreadedSelectorServer就可以。

Ref:
http://blog.csdn.net/houjixin/article/details/42778335
http://dongxicheng.org/search-engine/thrift-framework-intro/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,363評論 6 532
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,497評論 3 416
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,305評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,962評論 1 311
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,727評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,193評論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,257評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,411評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,945評論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,777評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,978評論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,519評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,216評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,642評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,878評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,657評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,960評論 2 373

推薦閱讀更多精彩內(nèi)容

  • 轉(zhuǎn)自:http://blog.csdn.net/kesonyk/article/details/50924489 ...
    晴天哥_王志閱讀 24,884評論 2 38
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 134,782評論 18 139
  • 從三月份找實(shí)習(xí)到現(xiàn)在,面了一些公司,掛了不少,但最終還是拿到小米、百度、阿里、京東、新浪、CVTE、樂視家的研發(fā)崗...
    時(shí)芥藍(lán)閱讀 42,319評論 11 349
  • 成熟原本就是一個(gè)不斷失去的過程 樸樹是,我們也是 我們來到這人世間 磕磕碰碰受了傷、燙了疤,長出繭 心尖上厚厚一層...
    石蕊花開閱讀 182評論 0 0
  • 不知道戀舊的人是不是都喜歡收集以前的東西,哪怕是什么價(jià)值都沒有的廢紙一張,也當(dāng)做生命當(dāng)中不可缺少的寶一樣! 人果然...
    亂世小白菜閱讀 365評論 0 1