前言
Thrift
提供的網(wǎng)絡(luò)服務(wù)模型:單線程、多線程、事件驅(qū)動,從另一個角度劃分為:阻塞服務(wù)模型、非阻塞服務(wù)模型。
阻塞服務(wù)模型:
TSimpleServer
、TThreadPoolServer
。非阻塞服務(wù)模型:
TNonblockingServer
、THsHaServer
和TThreadedSelectorServer
。
TServer
類的層次關(guān)系:
正文
TServer
TServer
定義了靜態(tài)內(nèi)部類Args
,Args
繼承自抽象類AbstractServerArgs
。AbstractServerArgs
采用了建造者模式,向TServer
提供各種工廠:
工廠屬性 | 工廠類型 | 作用 |
---|---|---|
ProcessorFactory | TProcessorFactory | 處理層工廠類,用于具體的TProcessor對象的創(chuàng)建 |
InputTransportFactory | TTransportFactory | 傳輸層輸入工廠類,用于具體的TTransport對象的創(chuàng)建 |
OutputTransportFactory | TTransportFactory | 傳輸層輸出工廠類,用于具體的TTransport對象的創(chuàng)建 |
InputProtocolFactory | TProtocolFactory | 協(xié)議層輸入工廠類,用于具體的TProtocol對象的創(chuàng)建 |
OutputProtocolFactory | TProtocolFactory | 協(xié)議層輸出工廠類,用于具體的TProtocol對象的創(chuàng)建 |
下面是TServer
的部分核心代碼:
public abstract class TServer {
public static class Args extends org.apache.thrift.server.TServer.AbstractServerArgs<org.apache.thrift.server.TServer.Args> {
public Args(TServerTransport transport) {
super(transport);
}
}
public static abstract class AbstractServerArgs<T extends org.apache.thrift.server.TServer.AbstractServerArgs<T>> {
final TServerTransport serverTransport;
TProcessorFactory processorFactory;
TTransportFactory inputTransportFactory = new TTransportFactory();
TTransportFactory outputTransportFactory = new TTransportFactory();
TProtocolFactory inputProtocolFactory = new TBinaryProtocol.Factory();
TProtocolFactory outputProtocolFactory = new TBinaryProtocol.Factory();
public AbstractServerArgs(TServerTransport transport) {
serverTransport = transport;
}
}
protected TProcessorFactory processorFactory_;
protected TServerTransport serverTransport_;
protected TTransportFactory inputTransportFactory_;
protected TTransportFactory outputTransportFactory_;
protected TProtocolFactory inputProtocolFactory_;
protected TProtocolFactory outputProtocolFactory_;
private boolean isServing;
protected TServer(org.apache.thrift.server.TServer.AbstractServerArgs args) {
processorFactory_ = args.processorFactory;
serverTransport_ = args.serverTransport;
inputTransportFactory_ = args.inputTransportFactory;
outputTransportFactory_ = args.outputTransportFactory;
inputProtocolFactory_ = args.inputProtocolFactory;
outputProtocolFactory_ = args.outputProtocolFactory;
}
public abstract void serve();
public void stop() {}
public boolean isServing() {
return isServing;
}
protected void setServing(boolean serving) {
isServing = serving;
}
}
TServer
的三個方法:serve()
、stop()
和isServing()
。serve()
用于啟動服務(wù),stop()
用于關(guān)閉服務(wù),isServing()
用于檢測服務(wù)的起停狀態(tài)。
TServer
的不同實現(xiàn)類的啟動方式不一樣,因此serve()
定義為抽象方法。不是所有的服務(wù)都需要優(yōu)雅的退出, 因此stop()
方法沒有被定義為抽象。
TSimpleServer
TSimpleServer
的工作模式采用最簡單的阻塞IO,實現(xiàn)方法簡潔明了,便于理解,但是一次只能接收和處理一個socket
連接,效率比較低。它主要用于演示Thrift
的工作過程,在實際開發(fā)過程中很少用到它。
(一) 工作流程
(二) 使用入門
服務(wù)端:
ServerSocket serverSocket = new ServerSocket(ServerConfig.SERVER_PORT);
TServerSocket serverTransport = new TServerSocket(serverSocket);
HelloWorldService.Processor processor =
new HelloWorldService.Processor<HelloWorldService.Iface>(new HelloWorldServiceImpl());
TBinaryProtocol.Factory protocolFactory = new TBinaryProtocol.Factory();
TSimpleServer.Args tArgs = new TSimpleServer.Args(serverTransport);
tArgs.processor(processor);
tArgs.protocolFactory(protocolFactory);
// 簡單的單線程服務(wù)模型 一般用于測試
TServer tServer = new TSimpleServer(tArgs);
System.out.println("Running Simple Server");
tServer.serve();
客戶端:
TTransport transport = new TSocket(ServerConfig.SERVER_IP, ServerConfig.SERVER_PORT, ServerConfig.TIMEOUT);
TProtocol protocol = new TBinaryProtocol(transport);
HelloWorldService.Client client = new HelloWorldService.Client(protocol);
transport.open();
String result = client.say("Leo");
System.out.println("Result =: " + result);
transport.close();
(三) 源碼分析
查看上述流程的源代碼,即TSimpleServer.java
中的serve()
方法如下:
serve()
方法的操作:
- 設(shè)置
TServerSocket
的listen()
方法啟動連接監(jiān)聽。 - 以阻塞的方式接受客戶端地連接請求,每進入一個連接即為其創(chuàng)建一個通道
TTransport
對象。 - 為客戶端創(chuàng)建處理器對象、輸入傳輸通道對象、輸出傳輸通道對象、輸入?yún)f(xié)議對象和輸出協(xié)議對象。
- 通過
TServerEventHandler
對象處理具體的業(yè)務(wù)請求。
ThreadPoolServer
TThreadPoolServer
模式采用阻塞socket
方式工作,主線程負責阻塞式監(jiān)聽是否有新socket
到來,具體的業(yè)務(wù)處理交由一個線程池來處理。
(一) 工作流程
(二) 使用入門
服務(wù)端:
ServerSocket serverSocket = new ServerSocket(ServerConfig.SERVER_PORT);
TServerSocket serverTransport = new TServerSocket(serverSocket);
HelloWorldService.Processor<HelloWorldService.Iface> processor =
new HelloWorldService.Processor<>(new HelloWorldServiceImpl());
TBinaryProtocol.Factory protocolFactory = new TBinaryProtocol.Factory();
TThreadPoolServer.Args ttpsArgs = new TThreadPoolServer.Args(serverTransport);
ttpsArgs.processor(processor);
ttpsArgs.protocolFactory(protocolFactory);
// 線程池服務(wù)模型 使用標準的阻塞式IO 預先創(chuàng)建一組線程處理請求
TServer ttpsServer = new TThreadPoolServer(ttpsArgs);
System.out.println("Running ThreadPool Server");
ttpsServer.serve();
客戶端:
TTransport transport = new TSocket(ServerConfig.SERVER_IP, ServerConfig.SERVER_PORT, ServerConfig.TIMEOUT);
TProtocol protocol = new TBinaryProtocol(transport);
HelloWorldService.Client client = new HelloWorldService.Client(protocol);
transport.open();
String result = client.say("ThreadPoolClient");
System.out.println("Result =: " + result);
transport.close();
(三) 源碼分析
ThreadPoolServer
解決了TSimpleServer
不支持并發(fā)和多連接的問題,引入了線程池。實現(xiàn)的模型是One Thread Per Connection
。查看上述流程的源代碼,先查看線程池的代碼片段:
TThreadPoolServer.java
中的serve()
方法如下:
serve()
方法的操作:
- 設(shè)置
TServerSocket
的listen()
方法啟動連接監(jiān)聽。 - 以阻塞的方式接受客戶端的連接請求,每進入一個連接,將通道對象封裝成一個
WorkerProcess
對象(WorkerProcess
實現(xiàn)了Runnabel
接口),并提交到線程池。 -
WorkerProcess
的run()
方法負責業(yè)務(wù)處理,為客戶端創(chuàng)建了處理器對象、輸入傳輸通道對象、輸出傳輸通道對象、輸入?yún)f(xié)議對象和輸出協(xié)議對象。 - 通過
TServerEventHandler
對象處理具體的業(yè)務(wù)請求。
WorkerProcess
的run()
方法:
(四) 優(yōu)缺點
TThreadPoolServer模式的優(yōu)點
拆分了監(jiān)聽線程(Accept Thread
)和處理客戶端連接的工作線程(Worker Thread
),數(shù)據(jù)讀取和業(yè)務(wù)處理都交給線程池處理。因此在并發(fā)量較大時新連接也能夠被及時接受。
線程池模式比較適合服務(wù)器端能預知最多有多少個客戶端并發(fā)的情況,這時每個請求都能被業(yè)務(wù)線程池及時處理,性能也非常高。
TThreadPoolServer模式的缺點
線程池模式的處理能力受限于線程池的工作能力,當并發(fā)請求數(shù)大于線程池中的線程數(shù)時,新請求也只能排隊等待。
TNonblockingServer
TNonblockingServer
模式也是單線程工作,但是采用NIO
的模式,借助Channel/Selector
機制, 采用IO
事件模型來處理。
所有的socket
都被注冊到selector
中,在一個線程中通過seletor
循環(huán)監(jiān)控所有的socket
。
每次selector
循環(huán)結(jié)束時,處理所有的處于就緒狀態(tài)的socket
,對于有數(shù)據(jù)到來的socket
進行數(shù)據(jù)讀取操作,對于有數(shù)據(jù)發(fā)送的socket則進行數(shù)據(jù)發(fā)送操作,對于監(jiān)聽socket
則產(chǎn)生一個新業(yè)務(wù)socket
并將其注冊到selector
上。
注意:TNonblockingServer要求底層的傳輸通道必須使用TFramedTransport。
(一) 工作流程
(二) 使用入門
服務(wù)端:
TProcessor tprocessor = new HelloWorldService.Processor<HelloWorldService.Iface>(new HelloWorldServiceImpl());
TNonblockingServerSocket tnbSocketTransport = new TNonblockingServerSocket(ServerConfig.SERVER_PORT);
TNonblockingServer.Args tnbArgs = new TNonblockingServer.Args(tnbSocketTransport);
tnbArgs.processor(tprocessor);
tnbArgs.transportFactory(new TFramedTransport.Factory());
tnbArgs.protocolFactory(new TCompactProtocol.Factory());
// 使用非阻塞式IO服務(wù)端和客戶端需要指定TFramedTransport數(shù)據(jù)傳輸?shù)姆绞? TServer server = new TNonblockingServer(tnbArgs);
System.out.println("Running Non-blocking Server");
server.serve();
客戶端:
TTransport transport = new TFramedTransport(new TSocket(ServerConfig.SERVER_IP, ServerConfig.SERVER_PORT, ServerConfig.TIMEOUT));
// 協(xié)議要和服務(wù)端一致
TProtocol protocol = new TCompactProtocol(transport);
HelloWorldService.Client client = new HelloWorldService.Client(protocol);
transport.open();
String result = client.say("NonBlockingClient");
System.out.println("Result =: " + result);
transport.close();
(三) 源碼分析
TNonblockingServer
繼承于AbstractNonblockingServer
,這里我們更關(guān)心基于NIO
的selector
部分的關(guān)鍵代碼。
(四) 優(yōu)缺點
TNonblockingServer模式優(yōu)點
相比于TSimpleServer
效率提升主要體現(xiàn)在IO
多路復用上,TNonblockingServer
采用非阻塞IO
,對accept/read/write
等IO
事件進行監(jiān)控和處理,同時監(jiān)控多個socket
的狀態(tài)變化。
TNonblockingServer模式缺點
TNonblockingServer
模式在業(yè)務(wù)處理上還是采用單線程順序來完成。在業(yè)務(wù)處理比較復雜、耗時的時候,例如某些接口函數(shù)需要讀取數(shù)據(jù)庫執(zhí)行時間較長,會導致整個服務(wù)被阻塞住,此時該模式效率也不高,因為多個調(diào)用請求任務(wù)依然是順序一個接一個執(zhí)行。
THsHaServer
鑒于TNonblockingServer
的缺點,THsHaServer
繼承于TNonblockingServer
,引入了線程池提高了任務(wù)處理的并發(fā)能力。THsHaServer
是半同步半異步(Half-Sync/Half-Async
)的處理模式,Half-Aysnc
用于IO
事件處理(Accept/Read/Write
),Half-Sync
用于業(yè)務(wù)handler
對rpc
的同步處理上。
注意:THsHaServer和TNonblockingServer一樣,要求底層的傳輸通道必須使用TFramedTransport。
(一) 工作流程
(二) 使用入門
服務(wù)端:
TNonblockingServerSocket tnbSocketTransport = new TNonblockingServerSocket(ServerConfig.SERVER_PORT);
TProcessor tprocessor = new HelloWorldService.Processor<HelloWorldService.Iface>(new HelloWorldServiceImpl());
// 半同步半異步
THsHaServer.Args thhsArgs = new THsHaServer.Args(tnbSocketTransport);
thhsArgs.processor(tprocessor);
thhsArgs.transportFactory(new TFramedTransport.Factory());
thhsArgs.protocolFactory(new TBinaryProtocol.Factory());
TServer server = new THsHaServer(thhsArgs);
System.out.println("Running HsHa Server");
server.serve();
客戶端:
TTransport transport = new TFramedTransport(new TSocket(ServerConfig.SERVER_IP, ServerConfig.SERVER_PORT, ServerConfig.TIMEOUT));
// 協(xié)議要和服務(wù)端一致
TProtocol protocol = new TBinaryProtocol(transport);
HelloWorldService.Client client = new HelloWorldService.Client(protocol);
transport.open();
String result = client.say("HsHaClient");
System.out.println("Result =: " + result);
transport.close();
(三) 源碼分析
THsHaServer
繼承于TNonblockingServer
,新增了線程池并發(fā)處理工作任務(wù)的功能,查看線程池的相關(guān)代碼:
任務(wù)線程池的創(chuàng)建過程:
下文的TThreadedSelectorServer囊括了THsHaServer的大部分特性,源碼分析可參考TThreadedSelectorServer。
(四) 優(yōu)缺點
THsHaServer的優(yōu)點
THsHaServer
與TNonblockingServer
模式相比,THsHaServer
在完成數(shù)據(jù)讀取之后,將業(yè)務(wù)處理過程交由一個線程池來完成,主線程直接返回進行下一次循環(huán)操作,效率大大提升。
THsHaServer的缺點
主線程仍然需要完成所有socket
的監(jiān)聽接收、數(shù)據(jù)讀取和數(shù)據(jù)寫入操作。當并發(fā)請求數(shù)較大時,且發(fā)送數(shù)據(jù)量較多時,監(jiān)聽socket
上新連接請求不能被及時接受。
TThreadedSelectorServer
TThreadedSelectorServer
是對THsHaServer
的一種擴充,它將selector
中的讀寫IO
事件(read/write
)從主線程中分離出來。同時引入worker
工作線程池,它也是種Half-Sync/Half-Async
的服務(wù)模型。
TThreadedSelectorServer
模式是目前Thrift
提供的最高級的線程服務(wù)模型,它內(nèi)部有如果幾個部分構(gòu)成:
-
一個
AcceptThread
線程對象,專門用于處理監(jiān)聽socket
上的新連接。 -
若干個
SelectorThread
對象專門用于處理業(yè)務(wù)socket
的網(wǎng)絡(luò)I/O
讀寫操作,所有網(wǎng)絡(luò)數(shù)據(jù)的讀寫均是有這些線程來完成。 - 一個負載均衡器
SelectorThreadLoadBalancer
對象,主要用于AcceptThread
線程接收到一個新socket
連接請求時,決定將這個新連接請求分配給哪個SelectorThread
線程。 - 一個
ExecutorService
類型的工作線程池,在SelectorThread
線程中,監(jiān)聽到有業(yè)務(wù)socket
中有調(diào)用請求過來,則將請求數(shù)據(jù)讀取之后,交給ExecutorService
線程池中的線程完成此次調(diào)用的具體執(zhí)行。主要用于處理每個rpc
請求的handler
回調(diào)處理(這部分是同步的)。
(一) 工作流程
(二) 使用入門
服務(wù)端:
TNonblockingServerSocket serverSocket = new TNonblockingServerSocket(ServerConfig.SERVER_PORT);
TProcessor processor = new HelloWorldService.Processor<HelloWorldService.Iface>(new HelloWorldServiceImpl());
// 多線程半同步半異步
TThreadedSelectorServer.Args ttssArgs = new TThreadedSelectorServer.Args(serverSocket);
ttssArgs.processor(processor);
ttssArgs.protocolFactory(new TBinaryProtocol.Factory());
// 使用非阻塞式IO時 服務(wù)端和客戶端都需要指定數(shù)據(jù)傳輸方式為TFramedTransport
ttssArgs.transportFactory(new TFramedTransport.Factory());
// 多線程半同步半異步的服務(wù)模型
TThreadedSelectorServer server = new TThreadedSelectorServer(ttssArgs);
System.out.println("Running ThreadedSelector Server");
server.serve();
客戶端:
for (int i = 0; i < 10; i++) {
new Thread("Thread " + i) {
@Override
public void run() {
// 設(shè)置傳輸通道 對于非阻塞服務(wù) 需要使用TFramedTransport(用于將數(shù)據(jù)分塊發(fā)送)
for (int j = 0; j < 10; j++) {
TTransport transport = null;
try {
transport = new TFramedTransport(new TSocket(ServerConfig.SERVER_IP, ServerConfig.SERVER_PORT, ServerConfig.TIMEOUT));
TProtocol protocol = new TBinaryProtocol(transport);
HelloWorldService.Client client = new HelloWorldService.Client(protocol);
transport.open();
String result = client.say("ThreadedSelector Client");
System.out.println("Result =: " + result);
transport.close();
} catch (Exception e) {
e.printStackTrace();
} finally {
// 關(guān)閉傳輸通道
transport.close();
}
}
}
}.start();
}
(三) 核心代碼
以上工作流程的三個組件AcceptThread
、SelectorThread
和ExecutorService
在源碼中的定義如下:
TThreadedSelectorServer
模式中有一個專門的線程AcceptThread
用于處理新連接請求,因此能夠及時響應大量并發(fā)連接請求;另外它將網(wǎng)絡(luò)I/O操作分散到多個SelectorThread
線程中來完成,因此能夠快速對網(wǎng)絡(luò)I/O
進行讀寫操作,能夠很好地應對網(wǎng)絡(luò)I/O
較多的情況。
TThreadedSelectorServer
默認參數(shù)定義如下:
- 負責網(wǎng)絡(luò)IO讀寫的selector默認線程數(shù)(selectorThreads):2
- 負責業(yè)務(wù)處理的默認工作線程數(shù)(workerThreads):5
- 工作線程池單個線程的任務(wù)隊列大小(acceptQueueSizePerThread):4
創(chuàng)建、初始化并啟動AcceptThread
和SelectorThreads
,同時啟動selector
線程的負載均衡器(selectorThreads
)。
AcceptThread源碼
AcceptThread
繼承于Thread
,可以看出包含三個重要的屬性:非阻塞式傳輸通道(TNonblockingServerTransport
)、NIO
選擇器(acceptSelector
)和選擇器線程負載均衡器(threadChooser
)。
查看AcceptThread
的run()
方法,可以看出accept
線程一旦啟動,就會不停地調(diào)用select()
方法:
查看select()
方法,acceptSelector
選擇器等待IO
事件的到來,拿到SelectionKey
即檢查是不是accept
事件。如果是,通過handleAccept()
方法接收一個新來的連接;否則,如果是IO
讀寫事件,AcceptThread
不作任何處理,交由SelectorThread
完成。
在handleAccept()
方法中,先通過doAccept()
去拿連接通道,然后Selector
線程負載均衡器選擇一個Selector
線程,完成接下來的IO
讀寫事件。
[圖片上傳失敗...(image-314d7a-1536936397051)]
接下來繼續(xù)查看doAddAccept()
方法的實現(xiàn),毫無懸念,它進一步調(diào)用了SelectorThread
的addAcceptedConnection()
方法,把非阻塞傳輸通道對象傳遞給選擇器線程做進一步的IO
讀寫操作。
SelectorThreadLoadBalancer源碼
SelectorThreadLoadBalancer
如何創(chuàng)建?
SelectorThreadLoadBalancer
是一個基于輪詢算法的Selector
線程選擇器,通過線程迭代器為新進來的連接順序分配SelectorThread
。
SelectorThread源碼
SelectorThread
和AcceptThread
一樣,是TThreadedSelectorServer
的一個成員內(nèi)部類,每個SelectorThread
線程對象內(nèi)部都有一個阻塞式的隊列,用于存放該線程被接收的連接通道。
[站外圖片上傳中...(image-ce3409-1536936397051)]
阻塞隊列的大小可由構(gòu)造函數(shù)指定:
上面看到,在AcceptThread
的doAddAccept()
方法中調(diào)用了SelectorThread
的addAcceptedConnection()
方法。
這個方法做了兩件事:
- 將被此
SelectorThread
線程接收的連接通道放入阻塞隊列中。 - 通過
wakeup()
方法喚醒SelectorThread
中的NIO
選擇器selector
。
既然SelectorThread
也是繼承于Thread
,查看其run()
方法的實現(xiàn):
SelectorThread
方法的select()
監(jiān)聽IO
事件,僅僅用于處理數(shù)據(jù)讀取和數(shù)據(jù)寫入。如果連接有數(shù)據(jù)可讀,讀取并以frame
的方式緩存;如果需要向連接中寫入數(shù)據(jù),緩存并發(fā)送客戶端的數(shù)據(jù)。且在數(shù)據(jù)讀寫處理完成后,需要向NIO
的selector
清空和注銷自身的SelectionKey
。
-
數(shù)據(jù)寫操作完成以后,整個
rpc
調(diào)用過程也就結(jié)束了,handleWrite()
方法如下:
-
數(shù)據(jù)讀操作完成以后,
Thrift
會利用已讀數(shù)據(jù)執(zhí)行目標方法,handleRead()
方法如下:
handleRead
方法在執(zhí)行read()
方法,將數(shù)據(jù)讀取完成后,會調(diào)用requestInvoke()
方法調(diào)用目標方法完成具體業(yè)務(wù)處理。requestInvoke()
方法將請求數(shù)據(jù)封裝為一個Runnable
對象,提交到工作任務(wù)線程池(ExecutorService
)進行處理。
select()
方法完成后,線程繼續(xù)運行processAcceptedConnections()
方法處理下一個連接的IO
事件。
這里比較核心的幾個操作:
- 嘗試從
SelectorThread
的阻塞隊列acceptedQueue
中獲取一個連接的傳輸通道。如果獲取成功,調(diào)用registerAccepted()
方法;否則,進入下一次循環(huán)。 -
registerAccepted()
方法將傳輸通道底層的連接注冊到NIO
的選擇器selector
上面,獲取到一個SelectionKey
。 - 創(chuàng)建一個
FrameBuffer
對象,并綁定到獲取的SelectionKey
上面,用于數(shù)據(jù)傳輸時的中間讀寫緩存。
總結(jié)
本文對Thrift
的各種線程服務(wù)模型進行了介紹,包括2種阻塞式服務(wù)模型:TSimpleServer
、TThreadPoolServer
,3種非阻塞式服務(wù)模型:TNonblockingServer
、THsHaServer
和TThreadedSelectorServer
。對各種服務(wù)模型的具體用法、工作流程、原理和源碼實現(xiàn)進行了一定程度的分析。
鑒于篇幅較長,請各位看官請慢慢批閱!
歡迎關(guān)注技術(shù)公眾號: 零壹技術(shù)棧
本帳號將持續(xù)分享后端技術(shù)干貨,包括虛擬機基礎(chǔ),多線程編程,高性能框架,異步、緩存和消息中間件,分布式和微服務(wù),架構(gòu)學習和進階等學習資料和文章。