IT技术博客大学习 共学习 共进步

Apache Avro 与 Thrift 比较

淘宝数据平台与产品部官方博客 tbdata.org 2010-12-28 00:26:29 浏览 5,445 次

    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,又追求性能的应用需求.

语言绑定

    目前阶段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 list
N/A set
map map 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 props;

    }

    int publish(string context,array messages);

    }

    Thrift.idl:

    struct Message {

    1: string topic

    2: binary content

    3: i64 createdTime

    4: string id

    5: string ipAddress

    6: map props

    }

    service SimpleService {

    i32 publish(1:string context,2:list 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 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();

    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 publish(string context, array 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的优势在于更多的语言支持和相对成熟。

建议继续学习

  1. Linux大棚版Thrift入门教程 (阅读 24,383)
  2. 中间件和稳定性平台 (阅读 7,046)
  3. HBase Thrift 接口使用注意事项 (阅读 6,603)
  4. hadoop rpc机制 && 将avro引入hadoop rpc机制初探 (阅读 6,084)
  5. Thrift简析 (阅读 5,764)
  6. php实现的thrift socket server (阅读 4,725)
  7. 国内互联网公司数据库访问层调查 (阅读 4,145)
  8. 2012年数据库技术大会 百度和淘宝介绍的中间件对比 (阅读 3,924)
  9. Thrift Message deserialize 方法的一个缺点及改进 (阅读 3,844)
  10. 在你的应用中添加 JSONP 的支持 (阅读 2,985)