Apache Thrift系列詳解(一)
前言
Thrift
是一個輕量級、跨語言的遠端服務呼叫框架,最初由Facebook
開發,後面進入Apache
開源專案。它通過自身的IDL
中間語言, 並藉助程式碼生成引擎生成各種主流語言的RPC
服務端/客戶端模板程式碼。
Thrift
支援多種不同的程式語言,包括C++
、Java
、Python
、PHP
、Ruby
等,本系列主要講述基於Java
語言的Thrift
的配置方式和具體使用。
正文
Thrift的技術棧
Thrift
對軟體棧的定義非常的清晰, 使得各個元件能夠鬆散的耦合, 針對不同的應用場景, 選擇不同是方式去搭建服務。
Thrift
軟體棧分層從下向上分別為:傳輸層(Transport Layer
Protocol Layer
)、處理層(Processor Layer
)和服務層(Server Layer
)。
-
傳輸層(
Transport Layer
):傳輸層負責直接從網路中讀取和寫入資料,它定義了具體的網路傳輸協議;比如說TCP/IP
傳輸等。 -
協議層(
Protocol Layer
):協議層定義了資料傳輸格式,負責網路傳輸資料的序列化和反序列化;比如說JSON
、XML
、二進位制資料等。 -
處理層(
Processor Layer
):處理層是由具體的IDL
(介面描述語言)生成的,封裝了具體的底層網路傳輸和序列化方式,並委託給使用者實現的Handler
進行處理。 -
服務層(
Server Layer
Thrift的特性
(一) 開發速度快
通過編寫RPC
介面Thrift IDL
檔案,利用編譯生成器自動生成服務端骨架(Skeletons
)和客戶端樁(Stubs
)。從而省去開發者自定義和維護介面編解碼、訊息傳輸、伺服器多執行緒模型等基礎工作。
- 服務端:只需要按照服務骨架即介面,編寫好具體的業務處理程式(
Handler
)即實現類即可。 - 客戶端:只需要拷貝
IDL
定義好的客戶端樁和服務物件,然後就像呼叫本地物件的方法一樣呼叫遠端服務。
(二) 介面維護簡單
通過維護Thrift
格式的IDL(介面描述語言)檔案(注意寫好註釋),即可作為給Client
Thrift
協議可靈活支援介面的可擴充套件性。
(三) 學習成本低
因為其來自Google Protobuf
開發團隊,所以其IDL
檔案風格類似Google Protobuf
,且更加易讀易懂;特別是RPC
服務介面的風格就像寫一個面向物件的Class
一樣簡單。
(四) 多語言/跨語言支援
Thrift
支援C++
、 Java
、Python
、PHP
、Ruby
、Erlang
、Perl
、Haskell
、C#
、Cocoa
、JavaScript
、Node.js
、Smalltalk
等多種語言,即可生成上述語言的伺服器端和客戶端程式。
對於我們經常使用的Java
、PHP
、Python
、C++
支援良好,雖然對iOS
環境的Objective-C
(Cocoa
)支援稍遜,但也完全滿足我們的使用要求。
(五) 穩定/廣泛使用
Thrift
在很多開源專案中已經被驗證是穩定和高效的,例如Cassandra
、Hadoop
、HBase
等;國外在Facebook
中有廣泛使用,國內包括百度、美團小米、和餓了麼等公司。
Thrift的資料型別
Thrift 指令碼可定義的資料型別包括以下幾種型別:
- 基本型別: bool: 布林值 byte: 8位有符號整數 i16: 16位有符號整數 i32: 32位有符號整數 i64: 64位有符號整數 double: 64位浮點數 string: UTF-8編碼的字串 binary: 二進位制串
- 結構體型別: struct: 定義的結構體物件
- 容器型別: list: 有序元素列表 set: 無序無重複元素集合 map: 有序的key/value集合
- 異常型別: exception: 異常型別
- 服務型別: service: 具體對應服務的類
Thrift的協議
Thrift
可以讓使用者選擇客戶端與服務端之間傳輸通訊協議的類別,在傳輸協議上總體劃分為文字(text
)和二進位制(binary
)傳輸協議。為節約頻寬,提高傳輸效率,一般情況下使用二進位制型別的傳輸協議為多數,有時還會使用基於文字型別的協議,這需要根據專案/產品中的實際需求。常用協議有以下幾種:
- TBinaryProtocol:二進位制編碼格式進行資料傳輸
- TCompactProtocol:高效率的、密集的二進位制編碼格式進行資料傳輸
- TJSONProtocol: 使用
JSON
文字的資料編碼協議進行資料傳輸 - TSimpleJSONProtocol:只提供
JSON
只寫的協議,適用於通過指令碼語言解析
Thrift的傳輸層
常用的傳輸層有以下幾種:
- TSocket:使用阻塞式
I/O
進行傳輸,是最常見的模式 - TNonblockingTransport:使用非阻塞方式,用於構建非同步客戶端
- TFramedTransport:使用非阻塞方式,按塊的大小進行傳輸,類似於
Java
中的NIO
Thrift的服務端型別
- TSimpleServer:單執行緒伺服器端,使用標準的阻塞式
I/O
- TThreadPoolServer:多執行緒伺服器端,使用標準的阻塞式
I/O
- TNonblockingServer:單執行緒伺服器端,使用非阻塞式
I/O
- THsHaServer:半同步半非同步伺服器端,基於非阻塞式
IO
讀寫和多執行緒工作任務處理 - TThreadedSelectorServer:多執行緒選擇器伺服器端,對
THsHaServer
在非同步IO
模型上進行增強
Thrift入門示例
(一) 編寫Thrift IDL檔案
b). 下載Windows
安裝環境的.exe
檔案,將thrift.exe
的路徑加入環境變數中。在Idea
上安裝Thrift
編輯外掛。
c). 編寫hello.thrift
的IDL
檔案:
service HelloWorldService {
string say(1: string username)
}
d). 使用程式碼生成工具生成程式碼,執行以下命令:
thrift -gen java hello.thrift
e). 由於未指定程式碼生成的目標目錄,生成的類檔案預設存放在gen-java
目錄下。這裡生成一個HelloWorldService.java
類檔案,檔案大小超過數千行,下面擷取一部分核心程式碼。
public class HelloWorldService {
public interface Iface {
public String say(String username) throws org.apache.thrift.TException;
}
public interface AsyncIface {
public void say(String username, org.apache.thrift.async.AsyncMethodCallback<String> resultHandler) throws org.apache.thrift.TException;
}
public static class Client extends org.apache.thrift.TServiceClient implements Iface {
public static class Factory implements org.apache.thrift.TServiceClientFactory<Client> {
public Factory() {
}
public Client getClient(org.apache.thrift.protocol.TProtocol prot) {
return new Client(prot);
}
public Client getClient(org.apache.thrift.protocol.TProtocol iprot, org.apache.thrift.protocol.TProtocol oprot) {
return new Client(iprot, oprot);
}
}
public Client(org.apache.thrift.protocol.TProtocol prot) {
super(prot, prot);
}
public Client(org.apache.thrift.protocol.TProtocol iprot, org.apache.thrift.protocol.TProtocol oprot) {
super(iprot, oprot);
}
public String say(String username) throws org.apache.thrift.TException {
send_say(username);
return recv_say();
}
// 省略.....
}
public static class AsyncClient extends org.apache.thrift.async.TAsyncClient implements AsyncIface {
public static class Factory implements org.apache.thrift.async.TAsyncClientFactory<AsyncClient> {
private org.apache.thrift.async.TAsyncClientManager clientManager;
private org.apache.thrift.protocol.TProtocolFactory protocolFactory;
public Factory(org.apache.thrift.async.TAsyncClientManager clientManager, org.apache.thrift.protocol.TProtocolFactory protocolFactory) {
this.clientManager = clientManager;
this.protocolFactory = protocolFactory;
}
public AsyncClient getAsyncClient(org.apache.thrift.transport.TNonblockingTransport transport) {
return new AsyncClient(protocolFactory, clientManager, transport);
}
}
public AsyncClient(org.apache.thrift.protocol.TProtocolFactory protocolFactory, org.apache.thrift.async.TAsyncClientManager clientManager, org.apache.thrift.transport.TNonblockingTransport transport) {
super(protocolFactory, clientManager, transport);
}
public void say(String username, org.apache.thrift.async.AsyncMethodCallback<String> resultHandler) throws org.apache.thrift.TException {
checkReady();
say_call method_call = new say_call(username, resultHandler, this, ___protocolFactory, ___transport);
this.___currentMethod = method_call;
___manager.call(method_call);
}
// 省略.....
}
// 省略.....
}
對於開發人員而言,使用原生的Thrift
框架,僅需要關注以下四個核心內部介面/類:Iface
, AsyncIface
, Client
和AsyncClient
。
- Iface:服務端通過實現
HelloWorldService.Iface
介面,向客戶端的提供具體的同步業務邏輯。 - AsyncIface:服務端通過實現
HelloWorldService.Iface
介面,向客戶端的提供具體的非同步業務邏輯。 - Client:客戶端通過
HelloWorldService.Client
的例項物件,以同步的方式訪問服務端提供的服務方法。 - AsyncClient:客戶端通過
HelloWorldService.AsyncClient
的例項物件,以非同步的方式訪問服務端提供的服務方法。
(二) 新建Maven工程
a). 新建maven
工程,引入thrift
的依賴,這裡使用的是版本0.10.0
。
<dependency>
<groupId>org.apache.thrift</groupId>
<artifactId>libthrift</artifactId>
<version>0.10.0</version>
</dependency>
b). 將生成類的HelloWorldService.java
原始檔拷貝進專案原始檔目錄中,並實現HelloWorldService.Iface
的定義的say()
方法。
HelloWorldServiceImpl.java
public class HelloWorldServiceImpl implements HelloWorldService.Iface {
@Override
public String say(String username) throws TException {
return "Hello " + username;
}
}
c). 伺服器端程式編寫:
SimpleServer.java
public class SimpleServer {
public static void main(String[] args) throws Exception {
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);
// 簡單的單執行緒服務模型 一般用於測試
TServer tServer = new TSimpleServer(tArgs);
System.out.println("Running Simple Server");
tServer.serve();
}
}
d). 客戶端程式編寫:
SimpleClient.java
public class SimpleClient {
public static void main(String[] args) {
TTransport transport = null;
try {
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);
} catch (TException e) {
e.printStackTrace();
} finally {
if (null != transport) {
transport.close();
}
}
}
}
e). 執行服務端程式,服務端在指定埠監聽客戶端的連線請求,控制檯輸出啟動日誌:
f). 執行客戶端程式,客戶端通過網路請求HelloWorldService
的say()
方法的具體實現,控制檯輸出返回結果:
這裡使用的一個基於單執行緒同步的簡單服務模型,一般僅用於入門學習和測試!
總結
本文對Thrift
的概念做了相關介紹,體驗了一番thrift
程式如何編寫!
歡迎關注技術公眾號: 零壹技術棧
本帳號將持續分享後端技術乾貨,包括虛擬機器基礎,多執行緒程式設計,高效能框架,非同步、快取和訊息中介軟體,分散式和微服務,架構學習和進階等學習資料和文章。