Thrift是什么?
Thrift是Facebook于2007年開(kāi)發(fā)的跨語(yǔ)言的rpc服框架艾猜,提供多語(yǔ)言的編譯功能,并提供多種服務(wù)器工作模式炸庞;用戶通過(guò)Thrift的IDL(接口定義語(yǔ)言)來(lái)描述接口函數(shù)及數(shù)據(jù)類型埠居,然后通過(guò)Thrift的編譯環(huán)境生成各種語(yǔ)言類型的接口文件滥壕,用戶可以根據(jù)自己的需要采用不同的語(yǔ)言開(kāi)發(fā)客戶端代碼和服務(wù)器端代碼绎橘。
Thrift為服務(wù)器端程序提供了很多的工作模式称鳞,例如:線程池模型狂票、非阻塞模型等等闺属,可以根據(jù)自己的實(shí)際應(yīng)用場(chǎng)景選擇一種工作模式高效地對(duì)外提供服務(wù)掂器;
(1)支持的傳輸格式
TBinaryProtocol – 二進(jìn)制格式.
TCompactProtocol – 壓縮格式
TJSONProtocol – JSON格式
TSimpleJSONProtocol –提供JSON只寫(xiě)協(xié)議, 生成的文件很容易通過(guò)腳本語(yǔ)言解析国瓮。
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í)際使用了簡(jiǎn)單的ByteArrayOutputStream食寡。
TZlibTransport – 使用zlib進(jìn)行壓縮善榛, 與其他傳輸方式聯(lián)合使用移盆。當(dāng)前無(wú)java實(shí)現(xiàn)咒循。(3)支持的服務(wù)模型
TSimpleServer – 簡(jiǎn)單的單線程服務(wù)模型叙甸,常用于測(cè)試
TThreadPoolServer – 多線程服務(wù)模型蚁署,使用標(biāo)準(zhǔn)的阻塞式IO光戈。
TNonblockingServer – 多線程服務(wù)模型,使用非阻塞式IO(需使用TFramedTransport數(shù)據(jù)傳輸方式)
Thrift的使用
Thrift提供跨語(yǔ)言的服務(wù)框架筷弦,這種跨語(yǔ)言主要體現(xiàn)在它對(duì)多種語(yǔ)言的編譯功能的支持烂琴,用戶只需要使用IDL描述好接口函數(shù)奸绷,只需要一條簡(jiǎn)單的命令号醉,Thrift就能夠把按照IDL格式描述的接口文件翻譯成各種語(yǔ)言版本畔派。其實(shí)线椰,說(shuō)搭建Thrift環(huán)境的時(shí)候呜魄,實(shí)際上最麻煩的就是搭建Thrift的編譯環(huán)境爵嗅,Thrift的編譯和通常的編譯一樣經(jīng)過(guò)詞法分析睹晒、語(yǔ)法分析等等最終生成對(duì)應(yīng)語(yǔ)言的源碼文件伪很,為了能夠支持對(duì)各種語(yǔ)言的編譯锉试,你需要下載各種語(yǔ)言對(duì)應(yīng)的編譯時(shí)使用的包。
編寫(xiě)IDL文件
使用Thrift開(kāi)發(fā)程序贷笛,首先要做的事情就是使用IDL對(duì)接口進(jìn)行描述株扛, 然后再使用Thrift的多語(yǔ)言編譯能力將接口的描述文件編譯成對(duì)應(yīng)語(yǔ)言的版本洞就,本文中將IDL對(duì)接口的描述文件稱為“Thrift文件”
使用IDL對(duì)接口進(jìn)行描述的thrift文件命名一般都是以“.thrift”作為后綴:XXX.thrift旬蟋,可以在該文件的開(kāi)頭為該文件加上命名空間限制咖为,格式為:namespace語(yǔ)言 命名空間的名字;例如:
namespace javacom.test.service
IDL文件中對(duì)所有接口函數(shù)的描述都放在service中架忌,service的名字可以自己指定叹放,該名字也將被用作生成的特定語(yǔ)言接口文件的名字井仰,接口函數(shù)需要對(duì)參數(shù)使用序號(hào)標(biāo)號(hào)俱恶,除最后一個(gè)接口函數(shù)外了罪,要以“泊藕,”結(jié)束對(duì)函數(shù)的描述娃圆。
例如踊餐,下面一個(gè)IDL描述的Thrift文件(該Thrift文件的文件名為:test_service.thrift)的全部?jī)?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就被用作生成的特定語(yǔ)言的文件名,例如我想用該Thrift文件生成一個(gè)java版本的接口文件吧寺,那么生成的java文件名就是:TestThriftService.java幕帆。
- (1) 編寫(xiě)IDL文件時(shí)需要注意的問(wèn)題
[1]函數(shù)的參數(shù)要用數(shù)字依序標(biāo)好失乾,序號(hào)從1開(kāi)始碱茁,形式為:“序號(hào):參數(shù)名”;
[2]每個(gè)函數(shù)的最后要加上“,”纽竣,最后一個(gè)函數(shù)不加聋袋;
[3]在IDL中可以使用/……/添加注釋
- (2) IDL支持的數(shù)據(jù)類型
IDL大小寫(xiě)敏感幽勒,它共支持以下幾種基本的數(shù)據(jù)類型:
[1]string代嗤, 字符串類型干毅,注意是全部小寫(xiě)形式;例如:string aString
[2]i16, 16位整形類型绅喉,例如:i16 aI16Val;
[3]i32徽缚,32位整形類型凿试,對(duì)應(yīng)C/C++/java中的int類型那婉;例如: I32 aIntVal
[4]i64详炬,64位整形呛谜,對(duì)應(yīng)C/C++/java中的long類型隐岛;例如:I64 aLongVal
[5]byte,8位的字符類型逻悠,對(duì)應(yīng)C/C++中的char单旁,java中的byte類型象浑;例如:byte aByteVal
[6]bool, 布爾類型愉豺,對(duì)應(yīng)C/C++中的bool蚪拦,java中的boolean類型驰贷; 例如:bool aBoolVal
[7]double,雙精度浮點(diǎn)類型稿茉,對(duì)應(yīng)C/C++/java中的double類型城须;例如:double aDoubleVal
[8]void糕伐,空類型良瞧,對(duì)應(yīng)C/C++/java中的void類型褥蚯;該類型主要用作函數(shù)的返回值赞庶,例如:void testVoid(),
除上述基本類型外澜薄,ID還支持以下類型:
[1]map肤京,map類型忘分,例如,定義一個(gè)map對(duì)象:map<i32, i32> newmap;
[2]set白修,集合類型肯骇,例如卤恳,定義set<i32>對(duì)象:set<i32> aSet;
[3]list若债,鏈表類型蠢琳,例如傲须,定義一個(gè)list<i32>對(duì)象:list<i32> aList;
- (3) 在Thrift文件中自定義數(shù)據(jù)類型
在IDL中支持兩種自定義類型:枚舉類型和結(jié)構(gòu)體類型泰讽,具體如下:
[1]enum已卸, 枚舉類型
[2]struct累澡,自定義結(jié)構(gòu)體類型愧哟,在IDL中可以自己定義結(jié)構(gòu)體蕊梧,對(duì)應(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í)需要對(duì)每個(gè)結(jié)構(gòu)體成員用序號(hào)標(biāo)識(shí):“序號(hào): ”榄融。
生成Thrift服務(wù)接口文件
搭建Thrift編譯環(huán)境之后愧杯,使用下面命令即可將IDL文件編譯成對(duì)應(yīng)語(yǔ)言的接口文件:
thrift --gen <language> <Thrift filename>
例如:如果使用上面的thrift文件(見(jiàn)上面的代碼2.1):test_service.thrift生成一個(gè)java語(yǔ)言的接口文件,則只需在搭建好thrift編譯環(huán)境的機(jī)子上邑闺,執(zhí)行如下命令即可:
thrift --gen java test_service.thrift
編寫(xiě)服務(wù)器端的java代碼
- 將生成的java接口文件TestThriftService.java拷貝到自己的工程文件中;
- 訪問(wèn)器程序需實(shí)現(xiàn)TestThriftService.Iface接口抵乓,在實(shí)現(xiàn)接口中完成自己要提供的服務(wù):
- 服務(wù)器端啟動(dòng)thrift服務(wù)框架的程序
Thrift對(duì)外提供幾種工作模式:
TSimpleServer灾炭、TNonblockingServer咆贬、TThreadPoolServer帚呼、TThreadedSelectorServer等模式,每種服務(wù)模式的通信方式不一樣沪哺,因此在服務(wù)啟動(dòng)時(shí)使用了那種服務(wù)模式辜妓,客戶端程序也需要采用對(duì)應(yīng)的通信方式。
Thrift支持多種通信協(xié)議格式:TCompactProtocol孽惰、TBinaryProtocol勋功、TJSONProtocol等,因此骚揍,在使用Thrift框架時(shí)疏咐,客戶端程序與服務(wù)器端程序所使用的通信協(xié)議一定要一致浑塞,否則便無(wú)法正常通信。
服務(wù)器端創(chuàng)建并啟動(dòng)Thrift服務(wù)框架的過(guò)程為:
- [1]為自己的服務(wù)實(shí)現(xiàn)類定義一個(gè)對(duì)象歇由,如代碼2.3中的:
TestThriftServiceImplm_myService =newTestThriftServiceImpl();
這里的TestThriftServiceImpl類就是代碼2.2中我們自己定義的服務(wù)器端對(duì)各服務(wù)接口的實(shí)現(xiàn)類糊昙。 - [2]定義一個(gè)TProcess對(duì)象释牺,在根據(jù)Thrift文件生成java源碼接口文件TestThriftService.java中没咙,Thrift已經(jīng)自動(dòng)為我們定義了一個(gè)Processor祭刚;后續(xù)節(jié)中將對(duì)這個(gè)TProcess類的功能進(jìn)行詳細(xì)描述涡驮;如代碼2.3中的:
TProcessor tProcessor = NewTestThriftService.Processor<TestThriftService.Iface>(m_myService); - [3]定義一個(gè)TNonblockingServerSocket對(duì)象暗甥,用于tcp的socket通信,如代碼2.3中的:
TNonblockingServerSocketnioSocket = newTNonblockingServerSocket(m_thriftPort);
在創(chuàng)建server端socket時(shí)需要指明監(jiān)聽(tīng)端口號(hào)遮怜,即上面的變量:m_thriftPort淋袖。 - [4]定義TNonblockingServer所需的參數(shù)對(duì)象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,即以幀的方式對(duì)數(shù)據(jù)進(jìn)行傳輸陌凳。
- [5]定義TNonblockingServer對(duì)象验游,并啟動(dòng)該服務(wù),如代碼2.3中的:
m_server = new TNonblockingServer(tnbArgs);
…
m_server.serve();
編寫(xiě)客戶端代碼
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驹闰,例如通過(guò)m_transport.open()打開(kāi)一個(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)用,即通過(guò)m_transport.open()打開(kāi)一個(gè)socket之后,你可以發(fā)起一個(gè)調(diào)用,在這個(gè)次調(diào)用完成之后,再繼續(xù)調(diào)用其他函數(shù)而不需要再次通過(guò)m_transport.open()打開(kāi)socket;
應(yīng)用技巧
(1) 為調(diào)用加上一個(gè)事務(wù)ID
在分布式服務(wù)開(kāi)發(fā)過(guò)程中,一次事件(事務(wù))的執(zhí)行可能跨越位于不同機(jī)子上多個(gè)服務(wù)程序缘滥,在后續(xù)維護(hù)過(guò)程中跟蹤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開(kāi)發(fā)服務(wù)器程序的時(shí)候,也應(yīng)該為每個(gè)接口函數(shù)提供一個(gè)事務(wù)ID的參數(shù),并且在服務(wù)器程序開(kāi)發(fā)過(guò)程中羡亩,該ID應(yīng)該在內(nèi)部函數(shù)調(diào)用過(guò)程中也進(jìn)行傳遞及志,并且在日志輸出的時(shí)候都加上它迫卢,以便問(wèn)題跟蹤。(2) 封裝返回結(jié)果
Thrift提供的RPC方式的服務(wù),使得調(diào)用方可以像調(diào)用自己的函數(shù)一樣調(diào)用Thrift服務(wù)提供的函數(shù)逛薇;在使用Thrift開(kāi)發(fā)過(guò)程中,盡量不要直接返回需要的數(shù)據(jù),而是將返回結(jié)果進(jìn)行封裝,例如上面的例子中的getStr函數(shù)就是直接返回了結(jié)果string嗦哆,見(jiàn)Thrift文件test_service.thrift中對(duì)該函數(shù)的描述:
stringgetStr(1:string srcStr1, 2:string srcStr2)
在實(shí)際開(kāi)發(fā)過(guò)程中,這是一種很不好的行為嗡官,在返回結(jié)果為null的時(shí)候還可能造成調(diào)用方產(chǎn)生異常,需要對(duì)返回結(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, /*請(qǐng)求結(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對(duì)象耕驰,無(wú)論異常還是正常情況,在出錯(cuò)時(shí)還可以通過(guò)ThriftResult返回出錯(cuò)的類型冲秽。
- (3) 將服務(wù)與數(shù)據(jù)類型分開(kāi)定義
在使用Thrift開(kāi)發(fā)一些中大型項(xiàng)目的時(shí)候,很多情況下都需要自己封裝數(shù)據(jù)結(jié)構(gòu),例如前面將返回結(jié)果進(jìn)行封裝的時(shí)候就定義了自己的數(shù)據(jù)類型ResultStr苟耻,此時(shí),將數(shù)據(jù)結(jié)構(gòu)和服務(wù)分開(kāi)定義到不通的文件中毛秘,可以增加thrift文件的易讀性。例如:
在thrift文件:thrift_datatype.thrift中定義數(shù)據(jù)類型瓶蚂,如:
namespace java com.browan.freepp.thriftdatatype
const string VERSION = "1.0.1"
/**為T(mén)hriftResult添加數(shù)據(jù)不完全和內(nèi)部錯(cuò)誤兩種類型
*/
/****************************************************************************************************
* 定義返回值,
* 枚舉類型ThriftResult,表示返回結(jié)果,成功或失敗哥遮,如果失敗,還可以表示失敗原因
* 每種返回類型都對(duì)應(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, /*請(qǐng)求結(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) 為T(mén)hrift文件添加版本號(hào)
在實(shí)際開(kāi)發(fā)過(guò)程中俏扩,還可以為T(mén)hrift文件加上版本號(hào)澈圈,以方便對(duì)thrift的版本進(jìn)行控制报慕。
工作原理
普通的本地函數(shù)調(diào)用過(guò)程
Thrift的RPC調(diào)用過(guò)程
源碼分析
在thrift生成的服務(wù)接口文件中刊懈,共包含以下幾部分:
(1)異步客戶端類AsyncClient和異步接口AsyncIface属拾,本節(jié)暫不涉及這些異步操作相關(guān)內(nèi)容;
(2)同步客戶端類Client和同步接口Iface菇用,Client類繼承自TServiceClient迎卤,并實(shí)現(xiàn)了同步接口Iface;Iface就是根據(jù)thrift文件中所定義的接口函數(shù)所生成瞎饲;Client類是在開(kāi)發(fā)Thrift的客戶端程序時(shí)使用东且,Client類是Iface的客戶端存根實(shí)現(xiàn)综慎, Iface在開(kāi)發(fā)Thrift服務(wù)器的時(shí)候要使用录择,Thrift的服務(wù)器端程序要實(shí)現(xiàn)接口Iface菱皆。
(3)Processor類吭产,該類主要是開(kāi)發(fā)Thrift服務(wù)器程序的時(shí)候使用医吊,該類內(nèi)部定義了一個(gè)map,它保存了所有函數(shù)名到函數(shù)對(duì)象的映射暑脆,一旦Thrift接到一個(gè)函數(shù)調(diào)用請(qǐng)求鲤孵,就從該map中根據(jù)函數(shù)名字找到該函數(shù)的函數(shù)對(duì)象,然后執(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ù)類和返回值類中有對(duì)數(shù)據(jù)的讀寫(xiě)操作,在參數(shù)類中,將按照協(xié)議類將調(diào)用的函數(shù)名和參數(shù)進(jìn)行封裝讹语,在返回值類中缓待,將按照協(xié)議規(guī)定讀取數(shù)據(jù)。
Thrift調(diào)用過(guò)程中,Thrift客戶端和服務(wù)器之間主要用到傳輸層類腌巾、協(xié)議層類和處理類三個(gè)主要的核心類厉斟,這三個(gè)類的相互協(xié)作共同完成rpc的整個(gè)調(diào)用過(guò)程。在調(diào)用過(guò)程中將按照以下順序進(jìn)行協(xié)同工作:
(1) 將客戶端程序調(diào)用的函數(shù)名和參數(shù)傳遞給協(xié)議層(TProtocol),協(xié)議層將函數(shù)名和參數(shù)按照協(xié)議格式進(jìn)行封裝,然后封裝的結(jié)果交給下層的傳輸層。此處需要注意:要與Thrift服務(wù)器程序所使用的協(xié)議類型一樣,否則Thrift服務(wù)器程序便無(wú)法在其協(xié)議層進(jìn)行數(shù)據(jù)解析;
(2) 傳輸層(TTransport)將協(xié)議層傳遞過(guò)來(lái)的數(shù)據(jù)進(jìn)行處理带污,例如傳輸層的實(shí)現(xiàn)類TFramedTransport就是將數(shù)據(jù)封裝成幀的形式稿静,即“數(shù)據(jù)長(zhǎng)度+數(shù)據(jù)內(nèi)容”倒堕,然后將處理之后的數(shù)據(jù)通過(guò)網(wǎng)絡(luò)發(fā)送給Thrift服務(wù)器望门;此處也需要注意:要與Thrift服務(wù)器程序所采用的傳輸層的實(shí)現(xiàn)類一致万哪,否則Thrift的傳輸層也無(wú)法將數(shù)據(jù)進(jìn)行逆向的處理氓润;
(3) Thrift服務(wù)器通過(guò)傳輸層(TTransport)接收網(wǎng)絡(luò)上傳輸過(guò)來(lái)的調(diào)用請(qǐng)求數(shù)據(jù)斩松,然后將接收到的數(shù)據(jù)進(jìn)行逆向的處理悬赏,例如傳輸層的實(shí)現(xiàn)類TFramedTransport就是將“數(shù)據(jù)長(zhǎng)度+數(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ù)名所對(duì)應(yīng)的函數(shù)對(duì)象;
(6) Thrift服務(wù)端使用傳過(guò)來(lái)的參數(shù)調(diào)用這個(gè)找到的函數(shù)對(duì)象;
(7) Thrift服務(wù)端將函數(shù)對(duì)象執(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ù)献雅;
開(kāi)發(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)用請(qǐng)求和返回執(zhí)行結(jié)果),都是將數(shù)據(jù)按照TMessage進(jìn)行組裝瞒渠,然后發(fā)送良蒸;TMessage包括三部分:消息的名稱、消息的序列號(hào)和消息的類型,消息名稱為字符串類型伍玖,消息的序列號(hào)為32位的整形嫩痰,消息的類型為byte類型,消息的類型共有如下17種窍箍。傳輸類(TTransport)
傳輸類或其各種實(shí)現(xiàn)類串纺,都是對(duì)I/O層的一個(gè)封裝,可更直觀的理解為它封裝了一個(gè)socket椰棘,不同的實(shí)現(xiàn)類有不同的封裝方式纺棺,例如TFramedTransport類,它里面還封裝了一個(gè)讀寫(xiě)buf邪狞,在寫(xiě)入的時(shí)候祷蝌,數(shù)據(jù)都先寫(xiě)到這個(gè)buf里面,等到寫(xiě)完調(diào)用該類的flush函數(shù)的時(shí)候帆卓,它會(huì)將寫(xiě)buf的內(nèi)容巨朦,封裝成幀再發(fā)送出去;
TFramedTransport是對(duì)TTransport的繼承剑令,由于tcp是基于字節(jié)流的方式進(jìn)行傳輸糊啡,因此這種基于幀的方式傳輸就要求在無(wú)頭無(wú)尾的字節(jié)流中每次寫(xiě)入和讀出一個(gè)幀,TFramedTransport是按照下面的方式來(lái)組織幀的:每個(gè)幀都是按照4字節(jié)的幀長(zhǎng)加上幀的內(nèi)容來(lái)組織吁津,幀內(nèi)容就是我們要收發(fā)的數(shù)據(jù)棚蓄,如下:
+---------------+---------------+
| 4字節(jié)的幀長(zhǎng) | 幀的內(nèi)容 |
+---------------+---------------+協(xié)議接口(TProtocol)
提供了一組操作協(xié)議接口,主要用于規(guī)定采用哪種協(xié)議進(jìn)行數(shù)據(jù)的讀寫(xiě),它內(nèi)部包含一個(gè)傳輸類(TTransport)成員對(duì)象梭依,通過(guò)TTransport對(duì)象從輸入輸出流中讀寫(xiě)數(shù)據(jù)挣柬;它規(guī)定了很多讀寫(xiě)方式,例如:
readByte()
readDouble()
readString()
…
每種實(shí)現(xiàn)類都根據(jù)自己所實(shí)現(xiàn)的協(xié)議來(lái)完成TProtocol接口函數(shù)的功能睛挚,例如實(shí)現(xiàn)了TProtocol接口的TBinaryProtocol類,對(duì)于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)聽(tīng)新請(qǐng)求的到來(lái)并完成對(duì)請(qǐng)求的處理,它只是在簡(jiǎn)單的演示時(shí)候使用绳军,它的工作方式如圖:
TSimpleServer的工作模式采用最簡(jiǎn)單的阻塞IO印机,實(shí)現(xiàn)方法簡(jiǎn)潔明了,便于理解门驾,但是一次只能接收和處理一個(gè)socket連接射赛,效率比較低,主要用于演示Thrift的工作過(guò)程奶是,在實(shí)際開(kāi)發(fā)過(guò)程中很少用到它楣责。
TNonblockingServer模式
TNonblockingServer工作模式,該模式也是單線程工作聂沙,但是該模式采用NIO的方式秆麸,所有的socket都被注冊(cè)到selector中,在一個(gè)線程中通過(guò)seletor循環(huán)監(jiān)控所有的socket及汉,每次selector結(jié)束時(shí)沮趣,處理所有的處于就緒狀態(tài)的socket,對(duì)于有數(shù)據(jù)到來(lái)的socket進(jìn)行數(shù)據(jù)讀取操作豁生,對(duì)于有數(shù)據(jù)發(fā)送的socket則進(jìn)行數(shù)據(jù)發(fā)送兔毒,對(duì)于監(jiān)聽(tīng)socket則產(chǎn)生一個(gè)新業(yè)務(wù)socket并將其注冊(cè)到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ù)處理上還是采用單線程順序來(lái)完成芍殖,在業(yè)務(wù)處理比較復(fù)雜豪嗽、耗時(shí)的時(shí)候,例如某些接口函數(shù)需要讀取數(shù)據(jù)庫(kù)執(zhí)行時(shí)間較長(zhǎng),此時(shí)該模式效率也不高龟梦,因?yàn)槎鄠€(gè)調(diào)用請(qǐng)求任務(wù)依然是順序一個(gè)接一個(gè)執(zhí)行隐锭。
THsHaServer模式(半同步半異步)
THsHaServer類是TNonblockingServer類的子類,在5.2節(jié)中的TNonblockingServer模式中计贰,采用一個(gè)線程來(lái)完成對(duì)所有socket的監(jiān)聽(tīng)和業(yè)務(wù)處理钦睡,造成了效率的低下,THsHaServer模式的引入則是部分解決了這些問(wèn)題躁倒。THsHaServer模式中荞怒,引入一個(gè)線程池來(lái)專門(mén)進(jìn)行業(yè)務(wù)處理,如下圖5.3所示秧秉;
- THsHaServer的優(yōu)點(diǎn):
與TNonblockingServer模式相比褐桌,THsHaServer在完成數(shù)據(jù)讀取之后,將業(yè)務(wù)處理過(guò)程交由一個(gè)線程池來(lái)完成象迎,主線程直接返回進(jìn)行下一次循環(huán)操作荧嵌,效率大大提升; - THsHaServer的缺點(diǎn):
由圖5.3可以看出砾淌,主線程需要完成對(duì)所有socket的監(jiān)聽(tīng)以及數(shù)據(jù)讀寫(xiě)的工作啦撮,當(dāng)并發(fā)請(qǐng)求數(shù)較大時(shí),且發(fā)送數(shù)據(jù)量較多時(shí)汪厨,監(jiān)聽(tīng)socket上新連接請(qǐng)求不能被及時(shí)接受逻族。
TThreadPoolServer模式
TThreadPoolServer模式采用阻塞socket方式工作,,主線程負(fù)責(zé)阻塞式監(jiān)聽(tīng)“監(jiān)聽(tīng)socket”中是否有新socket到來(lái)骄崩,業(yè)務(wù)處理交由一個(gè)線程池來(lái)處理聘鳞,如下圖5.4所示:
TThreadPoolServer模式優(yōu)點(diǎn):
線程池模式中,數(shù)據(jù)讀取和業(yè)務(wù)處理都交由線程池完成要拂,主線程只負(fù)責(zé)監(jiān)聽(tīng)新連接抠璃,因此在并發(fā)量較大時(shí)新連接也能夠被及時(shí)接受。線程池模式比較適合服務(wù)器端能預(yù)知最多有多少個(gè)客戶端并發(fā)的情況脱惰,這時(shí)每個(gè)請(qǐng)求都能被業(yè)務(wù)線程池及時(shí)處理搏嗡,性能也非常高。TThreadPoolServer模式缺點(diǎn):
線程池模式的處理能力受限于線程池的工作能力拉一,當(dāng)并發(fā)請(qǐng)求數(shù)大于線程池中的線程數(shù)時(shí)采盒,新請(qǐng)求也只能排隊(duì)等待。
TThreadedSelectorServer
TThreadedSelectorServer模式是目前Thrift提供的最高級(jí)的模式蔚润,它內(nèi)部有如果幾個(gè)部分構(gòu)成:
(1) 一個(gè)AcceptThread線程對(duì)象磅氨,專門(mén)用于處理監(jiān)聽(tīng)socket上的新連接;
(2) 若干個(gè)SelectorThread對(duì)象專門(mén)用于處理業(yè)務(wù)socket的網(wǎng)絡(luò)I/O操作嫡纠,所有網(wǎng)絡(luò)數(shù)據(jù)的讀寫(xiě)均是有這些線程來(lái)完成烦租;
(3) 一個(gè)負(fù)載均衡器SelectorThreadLoadBalancer對(duì)象延赌,主要用于AcceptThread線程接收到一個(gè)新socket連接請(qǐng)求時(shí),決定將這個(gè)新連接請(qǐng)求分配給哪個(gè)SelectorThread線程叉橱。
(4) 一個(gè)ExecutorService類型的工作線程池挫以,在SelectorThread線程中,監(jiān)聽(tīng)到有業(yè)務(wù)socket中有調(diào)用請(qǐng)求過(guò)來(lái)窃祝,則將請(qǐng)求讀取之后掐松,交個(gè)ExecutorService線程池中的線程完成此次調(diào)用的具體執(zhí)行;
TThreadedSelectorServer模式中有一個(gè)專門(mén)的線程AcceptThread用于處理新連接請(qǐng)求粪小,因此能夠及時(shí)響應(yīng)大量并發(fā)連接請(qǐng)求甩栈;另外它將網(wǎng)絡(luò)I/O操作分散到多個(gè)SelectorThread線程中來(lái)完成,因此能夠快速對(duì)網(wǎng)絡(luò)I/O進(jìn)行讀寫(xiě)操作糕再,能夠很好地應(yīng)對(duì)網(wǎng)絡(luò)I/O較多的情況;TThreadedSelectorServer對(duì)于大部分應(yīng)用場(chǎng)景性能都不會(huì)差玉转,因此突想,如果實(shí)在不知道選擇哪種工作模式,使用TThreadedSelectorServer就可以究抓。
Ref:
http://blog.csdn.net/houjixin/article/details/42778335
http://dongxicheng.org/search-engine/thrift-framework-intro/