1. 程式人生 > >Apache Avro 與 Thrift 比較

Apache Avro 與 Thrift 比較

http://www.tbdata.org/archives/1307

Avro和Thrift都是跨語言基於二進位制的高效能的通訊中介軟體. 它們都提供了資料序列化的功能RPC服務. 總體功能上類似,但是哲學不一樣. Thrift出自Facebook用於後臺各個服務間的通訊,Thrift的設計強調統一的程式設計介面的多語言通訊框架(IDL). Avro出自Hadoop之父Doug Cutting, 在Thrift已經相當流行的情況下Avro的推出,其目標不僅是提供一套類似Thrift的通訊中介軟體更是要建立一個新的,標準性的雲端計算的資料交換和儲存的Protocol。 這個和Thrift的理念不同,Thrift認為沒有一個完美的方案可以解決所有問題,因此儘量保持一個Neutral框架,插入不同的實現並互相互動。而Avro偏向實用,排斥多種方案帶來的可能的混亂,主張建立一個統一的標準,並不介意採用特定的優化。Avro的創新之處在於融合了顯式

,declarative的Schema和高效二進位制的資料表達,強調資料的自我描述,克服了以往單純XML或二進位制系統的缺陷。Avro對Schema動態載入功能,是Thrift程式設計介面所不具備的,符合了Hadoop上的Hive/Pig及NOSQL 等既屬於ad hoc,又追求效能的應用需求.

語言繫結

目前階段Thrift比Avro支援的語言更豐富.

Thrift:  C++, C#, Cocoa, Erlang, Haskell, Java, Ocami, Perl, PHP, Python, Ruby, Smalltalk.

Avro:   C, C++, Java, Python, Ruby, PHP.

資料型別

從常見的資料型別的角度來說, Avro和Thrift非常接近,功能上並沒有什麼區別。

Avro Thrift
基本型別

true or false

N/A 8-bit signed integer
N/A I16 16-bit signed integer
int I32 32-bit signed integer
long I64 64-bit signed integer
float N/A 32-bit floating point
double double 64-bit floating point
bytes binary Byte sequence
string string Character sequence
複雜型別
record struct 使用者自定義型別
enum enum
array<T> list<T>
N/A set<T>
map<string,T> map<T1,T2> Avro map的key

必須是string

union union
fixed N/A 固定大小的byte array
e.g. md5(16);
RPC服務
protocol service RPC服務型別
error exception RPC異常型別
namespace namespace 域名

開發流程

從開發者角度來說,Avro和Thrift也相當類似,

1)       同一個服務分別用Avro和Thrift來描述

Avro.idl:

protocol SimpleService {

record Message {

  string topic;

  bytes content;

  long    createdTime;

  string id;

  string ipAddress;

  map<string> props;

}

int publish(string context,array<Message> messages);

}

Thrift.idl:

struct Message {

 1: string topic

 2: binary content

 3: i64    createdTime

 4: string id

 5: string ipAddress

 6: map<string,string> props

}

service SimpleService {

 i32 publish(1:string context,2:list<Message> messages);

}

2)       Avro和Thrift都支援IDL程式碼生成功能

java idl avro.idl idl.avro

java org.apache.avro.specific.SpecificCompiler idl.avro avro-gen

目標目錄生成Message.java和SimpleService.java

thrift -gen java thrift.idl

同樣的,目標目錄生成Message.java和SimpleService.java

3)       客戶端程式碼

Avro client :

URL url = new URL ( “http”, HOST, PORT, “/”);

Transceiver trans = new HttpTransceiver(url);

SimpleService proxy=

= (SimpleService)SpecificRequestor.getClient(SimpleService.class, transceiver);

Thrift client :

TTransport transport = new TFramedTransport(new TSocket(HOST,PORT));

TProtocol protocol = new TCompactProtocol(transport);

transport.open();

SimpleService.Client client = new SimpleService.Client(protocol);

4)       伺服器端 Avro和Thrift都生成介面需要實現:

Avro server:

public static class ServiceImpl implements SimpleService {

..

}

Responder responder = new SpecificResponder(SimpleService.class, new ServiceImpl());

Server server = new HttpServer(responder, PORT);

Thrift server:

public static class ServerImpl implements SimpleService.Iface {

..

}

TServerTransport serverTransport=new TServerSocket(PORT);

TServer server=new TSimpleServer(processor,serverTransport,new TFramedTransport.Factory(), new TCompactProtocol.Factory());

server.serve();

Schema處理

Avro和Thrift處理Schema方法截然不同。

Thrift是一個面向程式設計的系統, 完全依賴於IDL->Binding Language的程式碼生成。 Schema也“隱藏”在生成的程式碼中了,完全靜態。為了讓系統識別處理一個新的資料來源,必須走編輯IDL,程式碼生成,編譯載入的流程。

與此對照,雖然Avro也支援基於IDL的Schema描述,但Avro內部Schema還是顯式的,存在於JSON格式的檔案當中,Avro可以把IDL格式的Schema轉化成JSON格式的。

Avro支援2種方式。Avro-specific方式和Thrift的方式相似,依賴程式碼生成產生特定的類,並內嵌JSON Schema. Avro-generic方式支援Schema的動態載入,用通用的結構(map)代表資料物件,不需要編譯載入直接就可以處理新的資料來源。

Serialization

對於序列化Avro制定了一個協議,而Thrift的設計目標是一個框架,它沒有強制規定序列化的格式。

Avro規定一個標準的序列化的格式,即無論是檔案儲存還是網路傳輸,資料的Schema(in JASON)都出現在資料的前面。資料本身並不包含任何Metadata(Tag). 在檔案儲存的時候,schema出現在檔案頭中。在網路傳輸的時候Schema出現在初始的握手階段.這樣的好處一是使資料self describe,提高了資料的透明度和可操作性,二是減少了資料本身的資訊量提高儲存效率,可謂一舉二得了

Avro的這種協議提供了很多優化的機會:

  • 對資料作Projection,通過掃描schema只對感興趣的部分作反序列化。
  • 支援schema的versioning和mapping ,不同的版本的Reader和Writer可以通過查詢schema相互交換資料(schema的aliases支援mapping),這比thrift採用的給每個域編號的方法優越多了

Avro的Schema允許定義資料的排序Order並在序列化的時候遵循這個順序。這樣話不需要反序列化就可以直接對資料進行排序,在Hadoop裡很管用.

另外一個Avro的特性是採用block連結串列結構,突破了用單一整型表示大小的限制。比如Array或Map由一系列Block組成,每個Block包含計數器和對應的元素,計數器為0標識結束。

Thrift提供了多種序列化的實現:

TCompactProtocol: 最高效的二進位制序列化協議,但並不是所有的繫結語言都支援。

TBinaryProtocol: 預設簡單二進位制序列化協議.

與Avro不同,Thrift的資料儲存的時候是每個Field前面都是帶Tag的,這個Tag用於標識這個域的型別和順序ID(IDL中定義,用於Versioning)。在同一批資料裡面,這些Tag的資訊是完全相同的,當資料條數大的時候這顯然就浪費了。

RPC服務

Avro提供了

HttpServer : 預設,基於Jetty核心的服務.

NettyServer: 新的基於Netty的服務.

Thrift提供了:

TThreadPolServer: 多執行緒服務

TNonBlockingServer: 單執行緒 non blocking的服務

THsHaServer: 多執行緒 non blocking的服務

Benchmarking

測試環境:2臺4核 Intel  Xeon 2.66GHz, 8G memory, Linux, 分別做客戶端,伺服器。

Object definition:

record Message {

string topic;

bytes payload;

long createdTime;

string id;

string ipAddress;

map<string,string > props;

}

Actual instance:

msg.createdTime : System.nanoTime();

msg.ipAddress : “127.0.0.1″;

msg.topic : “pv”;

msg.payload : byte[100]

msg.id : UUID.randomUUID().toString();

msg.props : new HashMap<String,String>();

msg.props.put(“author”, “tjerry”);

msg.props.put(“date”, new Date().toString());

msg.props.put(“status”, “new”);

Serialization size

Avro的序列化產生的結果最小

Serialization speed

Thrift-binary因為序列化方式簡單反而看上去速度最快.

Deserialization speed

這裡 Thrift的速度很快, 因與它內部實現採用zero-copy的改進有關.不過在RPC綜合測試裡這一優勢

似乎並未體現出來.

原始輸出:

Starting

,   Object create,       Serialize,  /w Same Object,     Deserialize, and Check Media,   and Check All,      Total Time, Serialized Size

avro-generic        ,      8751.30500,     10938.00000,      1696.50000,     16825.00000,     16825.00000,     16825.00000,     27763.00000,        221

avro-specific       ,      8566.88000,     10534.50000,      1242.50000,     18157.00000,     18157.00000,     18157.00000,     28691.50000,        221

thrift-compact      ,      6784.61500,     11665.00000,      4214.00000,      1799.00000,      1799.00000,      1799.00000,     13464.00000,        227

thrift-binary       ,      6721.19500,     12386.50000,      4478.00000,      1692.00000,      1692.00000,      1692.00000,     14078.50000,        273

RPC測試用例:

客戶端向伺服器傳送一組固定長度的message,為了能夠同時測試序列和反序列,伺服器收到後將原message返回給客戶端.

array<Message> publish(string context, array<Message> messages);

測試使用了Avro Netty Server和 Thrift HaHa Server因為他們都是基於非同步IO的並且適用於高併發的環境。

結果

從這個測試來看,再未到達網路瓶頸前,Avro Netty比Thrift HsHa服務提供了更高的吞吐率和更快的響應,另外 avro佔用的記憶體高些。

通過進一步實驗,發現不存在絕對的Avro和Thrift服務哪一個更快,決定於給出的test case,或者說與程式的用法有關,比如當前測試用例是Batch模式,大量傳送fine grained的物件(接近後臺tt,hadoop的用法),這個情況下Avro有優勢. 但是對於每次只傳一個物件的chatty客戶端,情況就出現逆轉變成Thrift更高效了.還有當資料結構裡blob比例變大的情況下,Avro和Thrift的差別也在減小.

Conclusion

  • Thrift適用於程式對程式靜態的資料交換,要求schema預知並相對固定。
  • Avro在Thrift基礎上增加了對schema動態的支援且效能上不輸於Thrift。
  • Avro顯式schema設計使它更適用於搭建資料交換及儲存的通用工具和平臺,特別是在後臺。
  • 目前Thrift的優勢在於更多的語言支援和相對成熟。