Apache Avro 與 Thrift 比較

http://www.alidata.org/archives/1307
java

Avro和Thrift都是跨語言,基於二進制的高性能的通信中間件. 它們都提供了數據序列化的功能和RPC服務. 整體功能上相似,可是哲學不同. Thrift出自Facebook用於後臺各個服務間的通信,Thrift的設計強調統一的編程接口的多語言通信框架. 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,又追求性能的應用需求.apache

語言綁定

目前階段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也至關相似,app

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綜合測試裏這一優點

彷佛並未體現出來.

序列化測試數據採集利用了http://code.google.com/p/thrift-protobuf-compare/所提供的框架,

原始輸出:

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的優點在於更多的語言支持和相對成熟。
相關文章
相關標籤/搜索