1. 程式人生 > >Apache Thrift 介紹

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沒有實現,而是留給使用者去實現。

參考資源