此文已由作者肖凡授权网易云社区发布。
欢迎访问,了解更多网易技术产品运营经验。
最近在学习hadoop,发现hadoop的序列化过程和jdk的序列化有很大的区别,下面就来说说这两者的区别都有哪些。
1、先简单回顾下JAVA的序列化
JDK的序列化只要实现serializable接口OK了,但是有时需要加上序列化版本ID serialVersionUID ,这是为了:在某些场合,希望类的不同版本对序列化兼容,因此需要确保类的不同版本具有相同的serialVersionUID;而在另外一些场合,不希望类的不同版本对序列化兼容。
java的序列化算法的过程主要如下:
1) 将对象实例相关的类元数据输出。
2) 递归地输出类的超类描述直到不再有超类。
3) 类元数据完了以后,开始从最顶层的超类开始输出对象实例的实际数据值。
4) 从上至下递归输出实例的数据
java的序列化很强大,对于复杂的情形,JAVA序列化机制也能应付自如,所以反序列化就so easy。 但是,Java的序列化机制的缺点也是很明显的,就是计算量开销大,且序列化的结果体积大太,有时能达到对象大小的数倍乃至十倍。它的引用机制也会导致大文件不能分割的问题,比如在一个很大的文件中反序列化某个对象时,需要访问文件中前面的某一个元数据,这将导致整个文件不能被切割,故而不能通过MapReduce来处理。同时,Java序列化会不断的创建新的对象,对于MapReduce应用来说,这将会带来大量的系统开销。这些缺点使得Java的序列化机制对Hadoop来说是不合适的。于是Hadoop设计了自己的序列化机制。
2、hadoop的序列化过程
对于处理大规模数据的Hadoop平台,其序列化机制需要具有如下特征:
1、紧凑:由于带宽是集群中信息传递的最宝贵的资源,所以我们必须想法设法缩小传递信息的大小。
2、快速:在进程间通信时会大量使用序列化机制,因此必须尽量减少序列化和反序列化的开销。
3、对象可重用:JDK的反序列化会不断地创建对象,这肯定会造成一定的系统开销,但是在hadoop的反序列化中,能重复的利用一个对象的readField方法来重新产生不同的对象。 为了支持以上这些特性,hadoo引入了Writeable的接口,作为所有可序列化对象必须实现的接口。Writable机制紧凑、快速,和serializable接口不同,Writable不是一个说明性的接口,它包含两个方法:
public interface Writable { /** * 输出(序列化)对象到流中 * * @param out DataOuput 流,序列化的结果保存在流中 * @throws IOException */ void write(DataOutput out) throws IOException; /** * 从流中读取(反序列化)对象 * 为了效率,请尽可能复用现有的对象 * @param in DataInput流,从该流中读取数据 * @throws IOException */ void readFields(DataInput in) throws IOException;}
Writable.write()方法用于将对象状态写入二进制的DataOutput中,反序列化的过程由readFields()从DataInput流中读取状态完成。下面是一个例子:
public class MyWritable implements Writable { private Text id; private Text name; public MyWritable(Text id, Text name) { super(); this.id = id; this.name = name; } public synchronized Text getId() { return id; } public synchronized void setId(Text id) { this.id = id; } public synchronized Text getName() { return name; } public synchronized void setName(Text name) { this.name = name; } @Override public void write(DataOutput out) throws IOException { id.write(out); name.write(out); } @Override public void readFields(DataInput in) throws IOException { id.readFields(in); name.readFields(in); }}
从上面的例子可以看出,write()和readFields()这两个方法的实现都很简单:MyWritable有两个成员变量,write()方法简单地把这两个成员变量写入流中,而readFields()则从流中依次读入这些数据。目前Java基本类型对应的Writable封装如下表所示:
Java基本类型 | Writable | 序列化后的长度 |
boolean | BooleanWritable | 1 |
byte | ByteWritable | 1 |
int | IntWritable VIntWritable | 4 1~5 |
float | FloatWritable | 4 |
long | LongWritable VLongWritable | 8 1~9 |
double | DoubleWritable | 8 |
从上表可以看出,对整形(int和long)进行编码的时候,有固定长度格式(IntWritable和LongWritable)和可变长度格式(VIntWritable和VLongWritable)两种选择。固定长度格式的整型,序列化后的数据时定长的,而可变长度格式则使用一种比较灵活的编码方式,对于数值比较小的整型,它们往往比较节省空间,这对于hadoop尤为重要。
3.Hadoop序列化框架
我们知道,大部分的MapReduce程序都使用Writable键-值对作为输入和输出,但这并不是Hadoop的API指定的,其它序列化机制也能够和Hadoop配合使用,目前除了前面提到的JAVA序列化机制和Hadoop使用的Writable机制,还流行其它的序列化框架,如Hadoop Avro、Apache Thrift和Google Protocol Buffer,有兴趣的同学可以去了解下。
Hadoop提供了一个简单的序列化框架API,用于集成各种序列化实现,该框架由Serialization接口实现。通过Serialization可以获得Serializer和Deserializer,分别用来将一个对象转换为一个字节流和将一个字节流转化为一个对象,相关代码如下:
public interface Serialization { /** * 客户端用于判断序列化实现是否支持该类对象 */ boolean accept(Class c); /** * 获得用于序列化对象的Serializer实现 */ Serializer getSerializer(Class c); /** * 获得用于反序列化对象的Deserializer实现 */ Deserializer getDeserializer(Class c);}
如果需要使用Serializer来执行序列化,一般通过open()方法打开Serializer,open()方法传入一个流对象,然后就可以使用serialize()方法序列化对象到流中,最后序列化结束后,通过close()方法关闭Serializer,相关代码如下:
public interface Serializer { /** * 为输出对象做准备 */ void open(OutputStream out) throws IOException; /** * 将对象序列化到底层的流中 */ void serialize(T t) throws IOException; /** * 序列化结束,清理 */ void close() throws IOException;}
Hadoop目前支持两个Serialization实现,分别是支持Writable机制的WritableSerialization和支持Java序列化的JavaSerialization。通过JavaSerialization可以再MapReduce程序中使用标准的Java类型,但是这种序列化不如Hadoop的序列化机制有效,非特殊情况不要轻易尝试。
网易云,0成本体验20+款云产品!
更多网易技术、产品、运营经验分享请。
相关文章:
【推荐】 【推荐】 【推荐】