thrift是一個軟件框架, 用來進行可擴展且跨語言的服務的開發. 它結合了功能強大的軟件堆棧和代碼生成引擎, 以構建在 C++, Java, Go,Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, JavaScript, Node.js, Smalltalk, and OCaml 這些編程語言間無縫結合的、高效的服務.
官網地址: thrift.apache.orgjava
Thrift 的安裝比較簡單, 在 Mac 下能夠直接使用 brew 快速安裝.git
brew install thrift
Window 或 Linux 能夠經過官網 下載, 這裏就再也不多說了.github
當下載安裝完畢後, 咱們就會獲得一個名爲 thrift
(Window 下是 thrift.exe) 的工具, 經過它就能夠生成各個語言的 thrift 代碼.apache
Thrift 腳本可定義的數據類型包括如下幾種類型:編程
bool: 布爾值, true 或 false, 對應 Java 的 boolean服務器
byte: 8 位有符號整數, 對應 Java 的 byte網絡
i16: 16 位有符號整數, 對應 Java 的 short數據結構
i32: 32 位有符號整數, 對應 Java 的 int多線程
i64: 64 位有符號整數, 對應 Java 的 long併發
double: 64 位浮點數, 對應 Java 的 double
string: 未知編碼文本或二進制字符串, 對應 Java 的 String
定義公共的對象, 相似於 C 語言中的結構體定義, 在 Java 中是一個 JavaBean
和 C/C++ 中的 union 相似.
list: 對應 Java 的 ArrayList
set: 對應 Java 的 HashSet
map: 對應 Java 的 HashMap
對應 Java 的 Exception
對應服務的類.
service 類型能夠被繼承, 例如:
service PeopleDirectory { oneway void log(1: string message), void reloadDatabase() } service EmployeeDirectory extends PeopleDirectory { Employee findEmployee(1: i32employee_id) throws (1: MyError error), bool createEmployee(1: Employee new_employee) }
注意到, 在定義 PeopleDirectory
服務的 log 方法時, 咱們使用到了 oneway
關鍵字, 這個關鍵字的做用是告訴 thrift, 咱們不關心函數的返回值, 不須要等待函數執行完畢就能夠直接返回.oneway
關鍵字一般用於修飾無返回值(void)的函數, 可是它和直接的無返回值的函數仍是有區別的, 例如上面的 log 函數和 reloadDatabase 函數, 當客戶端經過 thrift 進行遠程調用服務端的 log 函數時, 不須要等待服務端的 log 函數執行結束就能夠直接返回; 可是當客戶端調用 reloadDatabase 方法時, 雖然這個方法也是無返回值的, 但客戶端必需要阻塞等待, 直到服務端通知客戶端此調用已結束後, 客戶端的遠程調用才能夠返回.
和 Java 中的 enum
類型同樣, 例如:
enum Fruit { Apple, Banana, }
下面是一個在 IDL 文件中使用各類類型的例子:
enum ResponseStatus { OK = 0, ERROR = 1, } struct ListResponse { 1: required ResponseStatus status, 2: optional list<i32> ids, 3: optional list<double> scores, 10: optional string strategy, 11: optional string globalId, 12: optional map<string, string> extraInfo, } service Hello { string helloString(1:string para) i32 helloInt(1:i32 para) bool helloBoolean(1:bool para) void helloVoid() string helloNull() }
所謂 IDL, 即 接口描述語言
, 在使用 thrift 前, 須要提供一個 .thrift
後綴的文件, 其內容是使用 IDL 描述的服務接口信息.
例如以下的一個接口描述:
namespace java com.xys.thrift service HelloWorldService { string sayHello(string name); }
這裏咱們定義了一個名爲 HelloWorldService 的接口, 它有一個方法, 即 sayHello
. 當經過 thrift --gen java test.thrift
來生成 thrift 接口服務時, 會產生一個 HelloWorldService.java
的文件, 在此文件中會定義一個 HelloWorldService.Iface
接口, 咱們在服務器端實現此接口便可.
實現服務處理接口 impl
建立 Processor
建立 Transport
建立 Protocol
建立 Server
啓動 Server
例如:
public class HelloServer { public static final int SERVER_PORT = 8080; public static void main(String[] args) throws Exception { HelloServer server = new HelloServer(); server.startServer(); } public void startServer() throws Exception { // 建立 TProcessor TProcessor tprocessor = new HelloWorldService.Processor<HelloWorldService.Iface>(new HelloWorldImpl()); // 建立 TServerTransport, TServerSocket 繼承於 TServerTransport TServerSocket serverTransport = new TServerSocket(SERVER_PORT); // 建立 TProtocol TProtocolFactory protocolFactory = new TBinaryProtocol.Factory(); TServer.Args tArgs = new TServer.Args(serverTransport); tArgs.processor(tprocessor); tArgs.protocolFactory(protocolFactory); // 建立 TServer TServer server = new TSimpleServer(tArgs); // 啓動 Server server.serve(); } }
建立 Transport
建立 Protocol
基於 Potocol 建立 Client
打開 Transport
調用服務相應的方法.
public class HelloClient { public static final String SERVER_IP = "localhost"; public static final int SERVER_PORT = 8080; public static final int TIMEOUT = 30000; public static void main(String[] args) throws Exception { HelloClient client = new HelloClient(); client.startClient("XYS"); } public void startClient(String userName) throws Exception { // 建立 TTransport TTransport transport = new TSocket(SERVER_IP, SERVER_PORT, TIMEOUT); // 建立 TProtocol TProtocol protocol = new TBinaryProtocol(transport); // 建立客戶端. HelloWorldService.Client client = new HelloWorldService.Client(protocol); // 打開 TTransport transport.open(); // 調用服務方法 String result = client.sayHello(userName); System.out.println("Result: " + result); transport.close(); } }
如上圖所示, thrift 的網絡棧包含了 transport 層, protocol 層, processor 層和 Server/Client 層.
Transport 層提供了從網絡中讀取數據或將數據寫入網絡的抽象.
Transport 層和 Protocol 層相互獨立, 咱們能夠根據本身須要選擇不一樣的 Transport 層, 而對上層的邏輯不形成任何影響.
Thrift 的 Java 實現中, 咱們使用接口 TTransport
來描述傳輸層對象, 這個接口提供的經常使用方法有:
open close read write flush
而在服務器端, 咱們一般會使用 TServerTransport
來監聽客戶端的請求, 並生成相對應的 Transport 對象, 這個接口提供的經常使用方法有:
open listen accept close
爲了使用上的方便, Thrift 提供了以下幾個經常使用 Transport:
TSocket: 這個 transport 使用阻塞 socket 來收發數據.
TFramedTransport: 以幀的形式發送數據, 每幀前面是一個長度. 當服務方使用 non-blocking IO 時(即服務器端使用的是 TNonblockingServerSocket), 那麼就必須使用 TFramedTransport.
TMemoryTransport: 使用內存 I/O. Java 實現中在內部使用了 ByteArrayOutputStream
TZlibTransport: 使用 Zlib 壓縮傳輸的數據. 在 Java 中未實現.
這一層的做用是內存中的數據結構轉換爲可經過 Transport 傳輸的數據流或者反操做, 即咱們所謂的 序列化
和 反序列化
.
經常使用的協議有:
TBinaryProtocol, 二進制格式
TCompactProtocol, 壓縮格式
TJSONProtocol, JSON 格式
TSimpleJSONProtocol, 提供 JSON 只寫協議, 生成的文件很容易經過腳本語言解析.
TDebugProtocoal, 使用人類可讀的 Text 格式, 幫助調試
注意, 客戶端和服務器的協議要同樣.
Processor 層對象由 Thrift 根據用戶的 IDL 文件所生成, 咱們一般不能隨意指定.
這一層主要有兩個功能:
從 Protocol 層讀取數據, 而後轉交給對應的 handler 處理
將 handler 處理的結構發送 Prootcol 層.
Thrift 提供的 Server 層實現有:
TNonblockingServer: 這個是一個基於多線程, 非阻塞 IO 的 Server 層實現, 它專門用於處理大量的併發請求的
THsHaServer: 辦同步/半異步服務器模型, 基於 TNonblockingServer
實現.
TThreadPoolServer: 基於多線程, 阻塞 IO 的 Server 層實現, 它所消耗的系統資源比 TNonblockingServer 高, 不過能夠提供更高的吞吐量.
TSimpleServer: 這個實現主要是用於測試目的. 它只有一個線程, 而且是阻塞 IO, 所以在同一時間只能處理一個鏈接.
下面的例子在個人 Github 上有源碼, 直接 clone 便可.
<dependency> <groupId>org.apache.thrift</groupId> <artifactId>libthrift</artifactId> <version>0.10.0</version> </dependency>
thrift 版本: 0.10.0
注意
, jar 包的版本須要和 thrift 版本一致, 否則可能會有一些編譯錯誤
test.thrift
namespace java com.xys.thrift service HelloWorldService { string sayHello(string name); }
cd src/main/resources/ thrift --gen java test.thrift mv gen-java/com/xys/thrift/HelloWorldService.java ../java/com/xys/thrift
當執行 thrift --gen java test.thrift
命令後, 會在當前目錄下生成一個 gen-java
目錄, 其中會以包路徑格式存放着生成的服務器端 thrift 代碼, 咱們將其拷貝到工程對應的目錄下便可.
public class HelloWorldImpl implements HelloWorldService.Iface { public HelloWorldImpl() { } @Override public String sayHello(String name) throws TException { return "Hello, " + name; } }
下面咱們分別根據服務器端的幾種不一樣類型, 來分別實現它們, 並對比這些模型的異同點.
TSimpleServer 是一個簡單的服務器端模型, 它只有一個線程, 而且使用的是阻塞 IO 模型, 所以通常用於測試環境中.
public class SimpleHelloServer { public static final int SERVER_PORT = 8080; public static void main(String[] args) throws Exception { SimpleHelloServer server = new SimpleHelloServer(); server.startServer(); } public void startServer() throws Exception { TProcessor tprocessor = new HelloWorldService.Processor<HelloWorldService.Iface>( new HelloWorldImpl()); TServerSocket serverTransport = new TServerSocket(SERVER_PORT); TSimpleServer.Args tArgs = new TSimpleServer.Args(serverTransport); tArgs.processor(tprocessor); tArgs.protocolFactory(new TBinaryProtocol.Factory()); TServer server = new TSimpleServer(tArgs); server.serve(); } }
咱們在服務器端的代碼中, 沒有顯示地指定 Transport 的類型, 這個是由於 TSimpleServer.Args
在構造時, 會指定一個默認的 TransportFactory, 當有新的客戶端鏈接時, 就會生成一個 TSocket 的 Transport 實例. 因爲這一點, 咱們在客戶端實現時, 也就須要指定客戶端的 Transport 爲 TSocket 才行.
public class SimpleHelloClient { public static final String SERVER_IP = "localhost"; public static final int SERVER_PORT = 8080; public static final int TIMEOUT = 30000; public void startClient(String userName) throws Exception { TTransport transport = null; transport = new TSocket(SERVER_IP, SERVER_PORT, TIMEOUT); // 協議要和服務端一致 TProtocol protocol = new TBinaryProtocol(transport); HelloWorldService.Client client = new HelloWorldService.Client( protocol); transport.open(); String result = client.sayHello(userName); System.out.println("Result: " + result); transport.close(); } public static void main(String[] args) throws Exception { SimpleHelloClient client = new SimpleHelloClient(); client.startClient("XYS"); } }
TThreadPoolServer 是一個基於線程池和傳統的阻塞 IO 模型實現, 每一個線程對應着一個鏈接.
public class ThreadPoolHelloServer { public static final int SERVER_PORT = 8080; public static void main(String[] args) throws Exception { ThreadPoolHelloServer server = new ThreadPoolHelloServer(); server.startServer(); } public void startServer() throws Exception { TProcessor tprocessor = new HelloWorldService.Processor<HelloWorldService.Iface>( new HelloWorldImpl()); TServerSocket serverTransport = new TServerSocket(SERVER_PORT); TThreadPoolServer.Args tArgs = new TThreadPoolServer.Args(serverTransport); tArgs.processor(tprocessor); tArgs.protocolFactory(new TBinaryProtocol.Factory()); TServer server = new TThreadPoolServer(tArgs); server.serve(); } }
TThreadPoolServer 的服務器端實現和 TSimpleServer 的沒有很大區別, 只不過是在對應的地方把 TSimpleServer
改成 TThreadPoolServer
便可.
一樣地, 咱們在 TThreadPoolServer
服務器端的代碼中, 和 TSimpleServer
同樣, 沒有顯示地指定 Transport 的類型, 這裏的緣由和 TSimpleServer 的同樣, 就再也不贅述了.
代碼實現和 SimpleHelloClient
同樣.
TNonblockingServer 是基於線程池的, 而且使用了 Java 提供的 NIO 機制實現非阻塞 IO, 這個模型能夠併發處理大量的客戶端鏈接.注意
, 當使用 TNonblockingServer 模型是, 服務器端和客戶端的 Transport 層須要指定爲 TFramedTransport
或 TFastFramedTransport
.
public class NonblockingHelloServer { public static final int SERVER_PORT = 8080; public static void main(String[] args) throws Exception { NonblockingHelloServer server = new NonblockingHelloServer(); server.startServer(); } public void startServer() throws Exception { TProcessor tprocessor = new HelloWorldService.Processor<HelloWorldService.Iface>( new HelloWorldImpl()); TNonblockingServerSocket serverTransport = new TNonblockingServerSocket(SERVER_PORT); TNonblockingServer.Args tArgs = new TNonblockingServer.Args(serverTransport); tArgs.processor(tprocessor); tArgs.protocolFactory(new TBinaryProtocol.Factory()); // 下面這個設置 TransportFactory 的語句能夠去掉 tArgs.transportFactory(new TFramedTransport.Factory()); TServer server = new TNonblockingServer(tArgs); server.serve(); } }
前面咱們提到, 在 TThreadPoolServer
和 TSimpleServer
的服務器端代碼實現中, 咱們並無顯示地爲服務器端設置 Transport, 由於 TSimpleServer.Args
和 TThreadPoolServer.Args
設置了默認的 TransportFactory, 其最終生成的 Transport 是一個 TSocket 實例.
那麼在 TNonblockingServer
中又會是怎樣的狀況呢?
經過查看代碼咱們能夠發現, TNonblockingServer.Args
構造時, 會調用父類 AbstractNonblockingServerArgs
的構造器, 其源碼以下:
public AbstractNonblockingServerArgs(TNonblockingServerTransport transport) { super(transport); this.transportFactory(new TFramedTransport.Factory()); }
能夠看到, TNonblockingServer.Args
也會設置一個默認的 TransportFactory, 它的類型是 TFramedTransport#Factory
, 所以最終 TNonblockingServer 所使用的 Transport 實際上是 TFramedTransport 類型的, 這也就是爲何客戶端也必須使用 TFramedTransport(或TFastFramedTransport) 類型的 Transport 的緣由了.
分析到這裏, 回過頭來看代碼實現, 咱們就發現其實代碼中 tArgs.transportFactory(new TFramedTransport.Factory())
這一句是多餘的, 不過爲了強調一下, 我仍是保留了.
public class NonblockingHelloClient { public static final String SERVER_IP = "localhost"; public static final int SERVER_PORT = 8080; public static final int TIMEOUT = 30000; public void startClient(String userName) throws Exception { TTransport transport = null; // 客戶端使用 TFastFramedTransport 也是能夠的. transport = new TFramedTransport(new TSocket(SERVER_IP, SERVER_PORT, TIMEOUT)); // 協議要和服務端一致 TProtocol protocol = new TBinaryProtocol(transport); HelloWorldService.Client client = new HelloWorldService.Client( protocol); transport.open(); String result = client.sayHello(userName); System.out.println("Result: " + result); transport.close(); } public static void main(String[] args) throws Exception { NonblockingHelloClient client = new NonblockingHelloClient(); client.startClient("XYS"); } }
在 TNonblockingServer 服務器模型下, 除了使痛不式的客戶端調用方式, 咱們還能夠在客戶端中使用異步調用的方式, 具體代碼以下:
public class NonblockingAsyncHelloClient { public static final String SERVER_IP = "localhost"; public static final int SERVER_PORT = 8080; public static final int TIMEOUT = 30000; public void startClient(String userName) throws Exception { TAsyncClientManager clientManager = new TAsyncClientManager(); TNonblockingTransport transport = new TNonblockingSocket(SERVER_IP, SERVER_PORT, TIMEOUT); // 協議要和服務端一致 TProtocolFactory protocolFactory = new TBinaryProtocol.Factory(); HelloWorldService.AsyncClient client = new HelloWorldService.AsyncClient( protocolFactory, clientManager, transport); client.sayHello(userName, new AsyncHandler()); Thread.sleep(500); } class AsyncHandler implements AsyncMethodCallback<String> { @Override public void onComplete(String response) { System.out.println("Got result: " + response); } @Override public void onError(Exception exception) { System.out.println("Got error: " + exception.getMessage()); } } public static void main(String[] args) throws Exception { NonblockingAsyncHelloClient client = new NonblockingAsyncHelloClient(); client.startClient("XYS"); } }
能夠看到, 使用異步的客戶端調用方式實現起來就比較複雜了. 和 NonblockingHelloClient 對比, 咱們能夠看到有幾點不一樣:
異步客戶端中須要定義一個 TAsyncClientManager 實例, 而同步客戶端模式下不須要.
異步客戶端 Transport 層使用的是 TNonblockingSocket, 而同步客戶端使用的是 TFramedTransport
異步客戶端的 Procotol 層對象須要使用 TProtocolFactory 來生成, 而同步客戶端須要用戶手動生成.
異步客戶端的 Client 是 HelloWorldService.AsyncClient
, 而同步客戶的 Client 是 HelloWorldService.Client
最後也是最關鍵的不一樣點, 異步客戶端須要提供一個異步處理 Handler, 用於處理服務器的回覆.
咱們再來看一下 AsyncHandler 這個類. 這個類是用於異步回調用的, 當咱們正常收到了服務器的迴應後, Thrift 就會自動回調咱們的 onComplete
方法, 所以咱們在這裏就能夠設置咱們的後續處理邏輯.
當 Thrift 遠程調用服務器端出現異常時(例如服務器未啓動), 那麼就會回調到 onError
方法, 咱們在這個方法中就能夠作相應的錯誤處理.
即 Half-Sync/Half-Async
, 半同步/半異步服務器模型, 底層的實現其實仍是依賴於 TNonblockingServer
, 所以它所須要的 Transport 也是 TFramedTransport
.
public class HsHaHelloServer { public static final int SERVER_PORT = 8080; public static void main(String[] args) throws Exception { HsHaHelloServer server = new HsHaHelloServer(); server.startServer(); } public void startServer() throws Exception { TProcessor tprocessor = new HelloWorldService.Processor<HelloWorldService.Iface>( new HelloWorldImpl()); TNonblockingServerSocket serverTransport = new TNonblockingServerSocket(SERVER_PORT); THsHaServer.Args tArgs = new THsHaServer.Args(serverTransport); tArgs.processor(tprocessor); tArgs.protocolFactory(new TBinaryProtocol.Factory()); TServer server = new THsHaServer(tArgs); server.serve(); } }
和 NonblockingHelloClient 一致.
Learning-Apache-Thrift