Apache Thrift 介紹
Thrift源於大名鼎鼎的facebook之手,在2007年facebook提交Apache基金會將Thrift作為一個開源專案,對於當時的facebook來說創造thrift是為了解決facebook系統中各系統間大資料量的傳 輸通訊以及系統之間語言環境不同需要跨平臺的特性。所以thrift可以支援多種程式語言,例如: C++, C#, Cocoa, Erlang, Haskell, Java, Ocami, Perl, PHP, Python, Ruby, Smalltalk. 在多種不同的語言之間通訊thrift可以作為二進位制的高效能的通訊中介軟體,支援資料(物件)序列化和多種型別的RPC服務。Thrift適用於程式對程 序靜態的資料交換,需要先確定好他的資料結構,他是完全靜態化的,當資料結構發生變化時,必須重新編輯IDL檔案,程式碼生成,再編譯載入的流程,跟其他IDL工具相比較可以視為是Thrift的弱項,Thrift適用於搭建大型資料交換及儲存的通用工具,對於大型系統中的內部資料傳輸相對於JSON和xml無論在效能、傳輸大小上有明顯的優勢。
Thrift如何使用
本文基於thrift最新版本0.6.1。
首先呢,Thrift使用了ThriftIDL來定義伺服器和客戶端的介面。例如,這是本文使用的一個thrift(calculator.thrift)檔案。
namespace java calculator /* Thrift的註釋與C++或Java類似 */ /* 在這裡我們定義了加減乘除的一個列舉型別 */ enum Operation { ADD, SUBTRACT, MULTIPLY, DIVIDE } /* Thrift支援自定義的異常 */ exception InvalidOperation { 1: i32 errorCode, 2: string message } /* 定義一下我們的Service介面 */ service Calculator { i32 calculate(1:Operation operation, 2:i32 num1, 3:i32 num2) throws (1:InvalidOperation e), }接下來,我們需要把這個thrift檔案編譯成Java檔案。
我們需要的編譯工作可以到http://thrift.apache.org/download/下載:Thrift compiler for Windows (thrift-0.6.1.exe) 。千萬不要下載“Snapshot Releases”的,這個不一定能用,我就下錯了,結果無法使用,呵呵。
接下來我們執行命令
thrift-0.6.1.exe -o src --gen java calculator.thrift
可以看到在src目錄下生成了如下的檔案
gen-java\calculator\Calculator.java gen-java\calculator\InvalidOperation.java gen-java\calculator\Operation.java把這幾個檔案加入到你的專案中吧。
下面,我們需要thrift的Java庫。
thrift沒有提供完整的下載,不過既然是搞Java的,起碼要會maven或ivy吧。
到這裡我們已經準備好了thrift。
我們看一下thrift的架構
thrift最大的功勞就在於他幫助我們實現了傳輸層(TTransport)和協議層(TProtocol),我們只需要選擇需要一個適合自己的實現就ok啦。
例如,我們客戶端可以這麼寫
package calculator;
import org.apache.thrift.TException;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.protocol.TProtocol;
import org.apache.thrift.transport.TSocket;
import org.apache.thrift.transport.TTransport;
import org.apache.thrift.transport.TTransportException;
public class Client {
public static void main(String[] args) {
// 傳輸層
// * TSocket- 使用堵塞式I/O進行傳輸,也是最常見的模式。
// * TFramedTransport- 使用非阻塞方式,按塊的大小,進行傳輸,類似於Java中的NIO。
// * TFileTransport- 顧名思義按照檔案的方式程序傳輸,雖然這種方式不提供Java的實現,但是實現起來非常簡單。
// * TMemoryTransport- 使用記憶體I/O,就好比Java中的ByteArrayOutputStream實現。
// * TZlibTransport- 使用執行zlib壓縮,不提供Java的實現。
TTransport transport = new TSocket("localhost", 9090);
// 協議層
// * TBinaryProtocol – 二進位制編碼格式進行資料傳輸。
// * TCompactProtocol – 這種協議非常有效的,使用Variable-Length Quantity
// (VLQ)編碼對資料進行壓縮。
// * TJSONProtocol – 使用JSON的資料編碼協議進行資料傳輸。
// * TSimpleJSONProtocol – 這種節約只提供JSON只寫的協議,適用於通過指令碼語言解析
TProtocol protocol = new TBinaryProtocol(transport);
Calculator.Client client = new Calculator.Client(protocol);
try {
transport.open();
System.out.println(client.calculate(Operation.ADD, 1, 2));
System.out.println(client.calculate(Operation.SUBTRACT, 3, 5));
System.out.println(client.calculate(Operation.MULTIPLY, 4, 2));
System.out.println(client.calculate(Operation.DIVIDE, 1, 0));
} catch (TTransportException e) {
e.printStackTrace();
} catch (InvalidOperation e) {
e.printStackTrace();
} catch (TException e) {
e.printStackTrace();
} finally {
// 最後一定要呼叫close來釋放資源
transport.close();
}
}
}
這個客戶端很簡單,設定一個傳輸層(TTransport)和協議層(TProtocol)的實現類TSocket和TBinaryProtocol,就可以使用thrift幫我們生成的類alculator.Client(這個完全不用我們寫一行程式碼的,自動生成的類)來呼叫伺服器上的真正的Calculator物件了,用起來跟直接呼叫函式差不多,把網路傳輸細節都隱藏起來了。
下面我們看一下伺服器的實現。
package calculator;
import org.apache.thrift.TException;
import org.apache.thrift.server.TServer;
import org.apache.thrift.server.TSimpleServer;
import org.apache.thrift.transport.TServerSocket;
import org.apache.thrift.transport.TServerTransport;
import org.apache.thrift.transport.TTransportException;
public class Server {
/*
* 在伺服器這邊,由這個類來真正處理客戶端的請求。
*/
public static class CalculatorHandler implements Calculator.Iface {
@Override
public int calculate(Operation operation, int num1, int num2)
throws InvalidOperation, TException {
switch (operation) {
case ADD:
return num1 + num2;
case SUBTRACT:
return num1 - num2;
case MULTIPLY:
return num1 * num2;
case DIVIDE:
if (num2 == 0) {
throw new InvalidOperation(1, "divide by zero");
}
return num1 / num2;
default:
throw new InvalidOperation(0, "impossible code");
}
}
}
public static void main(String[] args) {
// 既然是RPC,那麼我們在伺服器需要有一個真正的類來處理客戶端的請求
Calculator.Processor processor = new Calculator.Processor(
new CalculatorHandler());
try {
TServerTransport serverTransport = new TServerSocket(9090);
// 服務端型別
// * TSimpleServer - 單執行緒伺服器端使用標準的堵塞式I/O。
// * TThreadPoolServer - 多執行緒伺服器端使用標準的堵塞式I/O。
// * TNonblockingServer – 多執行緒伺服器端使用非堵塞式I/O,並且實現了Java中的NIO通道。
TServer server = new TSimpleServer(
new TServer.Args(serverTransport).processor(processor));
server.serve();
// 跟Client的寫法比起來,是不是覺得少了TProtocol?
// 因為TServer.Args預設設定的TProtocol是TBinaryProtocol,因此就不顯示指定了。
} catch (TTransportException e) {
e.printStackTrace();
}
}
}
伺服器的實現也一樣,設定一個傳輸層(TTransport)和協議層(TProtocol)的實現類TSocket和TBinaryProtocol,然後直接呼叫TServer的serve函式就ok了。之後伺服器就掛起等待客戶端的請求了。
當然,我們還需要在伺服器這邊寫一個真正的CalculatorHandler例項。這個例項在伺服器這邊進行真正的計算,然後有TServer自動地把結果傳遞給client。
Thrift比起protobuf的優點
protobuf也提供了跨語言的資料傳輸解決方案,但是沒有thrift的server和client的功能。使用protobuf實現server和client需要自己編寫網路傳輸的程式碼(缺少thrift的TTransport和TProtocol這兩層的實現)。另外,protobuf只支援c++,python,java,語言數量比較少。Thrift支援C++, C#, Erlang, Haskell, Java, Objective C/Cocoa, OCaml, Perl, PHP, Python, Ruby, and Squeak等各種聽過或沒聽過的語言^_^。當然protobuf優勢在於傳輸的資料比thrift小一些,當然差距不是很大。另外,事實上protobuf完全可以實現server/client功能,從protobuf的service功能可以看出來,不知道為什麼google沒有實現,而是留給使用者去實現。