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的創新之處在於融合了顯式
語言繫結
目前階段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的優勢在於更多的語言支援和相對成熟。