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/