5.1 Hadoop数据序列化

来源:互联网 发布:在a标签传随机参数js 编辑:程序博客网 时间:2024/06/06 12:41

5.1 Hadoop数据序列化

  尽管我们看到的数据是结构化的形式,但数据的原始形式是序列化的比特或比特流。数据以这种原始形式通过网络传输,然后保存在RAM或其他持久性存储媒体中。序列化过程就是把结构化的数据转换为原始形式。反序列化过程则相反,是把数据从原始比特流形式重建为结构形式。

  Hadoop中不同的组件使用远程调用(Remote Procedure Call,RPC)进行交互。在发送调用函数名和参数给被调方之前,调用方进程会把它们序列化为字节流。被调方反序列化这个字节流,解释函数类型,根据所提供的参数执行函数,最后序列化执行结果,并放回调用方。当然,整个工作流需要快速的序列化和反序列化。网络带宽十分珍贵,所以需要序列化函数名和函数参数,从而尽可能减小负载。不同的组件会以不同的方式变化,所以整个序列化-反序列化过程需要向后兼容和可扩展。运行在不同机器上的组件进程会有不同的配置,并且所利用的平台组件也不同,因此序列化-反序列化库需要具备交互操作性能。序列化和反序列化的这些特性不限于网络数据,还适用于存储,包括易失存储和持久存储。

5.1.1 Writable与WritableComparable

  Hadoop序列化和反序列化都使用Writable接口。这个接口有两个方法,void write(DataOutput out)和void readFields(DataInput in)。write方法序列化对象为字节流。readFields方法则是反序列化方法,即读取输入的字节流并将其转换为对象。

  继承Writable接口的是WritableComparable接口。可以说这个接口是Writable接口和Comparable接口的组合。接口的实现类不但可以方便地进行序列化和反序列化,而且还能对值进行比较。用Hadoop数据类型实现这个接口,可以非常方便的排序和分组数据对象。

  Hadoop提供了许多唾手可得的WritableComparable包装类。每个WritableComparable包装类对应包装一个Java原生类型。例如,IntWritable包装类包装了int数据点(data point),BooleanWritable包装类包装了boolean类型。
  Hadoop有VIntWritable和VLongWritable类,它们长度可变,但相对于固定长度的IntWritable和LongWritable类型。处于-112到127之间的值会使用可变长度数值类型编码为单个字节。然而,更大的数值用另一种方式编码,即首字节代表标记以及紧跟其后的字节数。当数值分布的方差很高时,平均而言,可变长度的Writable能节省存储空间。数值越小,所需存储空间就越小。

演示程序MasteringHadoopSerialization.java

package MasteringHadoop;import org.apache.hadoop.io.*;import org.apache.hadoop.util.StringUtils;import java.io.ByteArrayOutputStream;import java.io.DataOutputStream;import java.io.IOException;import java.io.ObjectOutputStream;public class MasteringHadoopSerialization{    public static String serializeToByteString(Writable writable) throws IOException {        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();        DataOutputStream dataOutputStream = new DataOutputStream(outputStream);        //使用你write方法把Writable对象序列化为字节流        writable.write(dataOutputStream);        dataOutputStream.close();        byte[] byteArray = outputStream.toByteArray();        //StringUtils工具类的方法把字节数组转换为十六进制字符串        return StringUtils.byteToHexString(byteArray);    }    public static String javaSerializeToByteString(Object o) throws IOException{        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();        ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);        objectOutputStream.writeObject(o);        objectOutputStream.close();        byte[] byteArray = outputStream.toByteArray();        return StringUtils.byteToHexString(byteArray);    }    public static void main(String[] args) throws IOException{        IntWritable intWritable = new IntWritable();        VIntWritable vIntWritable = new VIntWritable();        LongWritable longWritable = new LongWritable();        VLongWritable vLongWritable = new VLongWritable();        int smallInt = 100;        int mediumInt = 1048576;        long bigInt = 4589938592L;        System.out.println("smallInt serialized value using IntWritable");        intWritable.set(smallInt);        System.out.println(serializeToByteString(intWritable));        System.out.println("smallInt serialized value using VIntWritable");        vIntWritable.set(smallInt);        System.out.println(serializeToByteString(vIntWritable));        System.out.println("mediumInt serialized value using IntWritable");        intWritable.set(mediumInt);        System.out.println(serializeToByteString(intWritable));        System.out.println("mediumInt serialized value using VIntWritable");        vIntWritable.set(mediumInt);        System.out.println(serializeToByteString(vIntWritable));        System.out.println("bigInt serialized value using LongWritable");        longWritable.set(bigInt);        System.out.println(serializeToByteString(longWritable));        System.out.println("mediumInt serialized value using VIntWritable");        vLongWritable.set(bigInt);        System.out.println(serializeToByteString(vLongWritable));        System.out.println("smallInt serialized value using Java serializer");        System.out.println(javaSerializeToByteString(new Integer(smallInt)));        System.out.println("mediumInt serialized value using Java serializer");        System.out.println(javaSerializeToByteString(new Integer(mediumInt)));        System.out.println("bigInt serialized value using Java serializer");        System.out.println(javaSerializeToByteString(new Long(bigInt)));    }}

运行部分结果:

smallInt serialized value using IntWritable00000064smallInt serialized value using VIntWritable64mediumInt serialized value using IntWritable00100000mediumInt serialized value using VIntWritable8d100000bigInt serialized value using LongWritable000000011194e7a0mediumInt serialized value using VIntWritable8b011194e7a0

  不管存放的数值大小,IntWritable类始终使用4个字节的固定长度来表示一个整形。而VIntWritable类更聪明,字节数取决于数值的大小。对于数值100,VIntWritable只使用1个字节。LongWritable和VLongWritable在序列化值的时候有类似区别。Text是Writable版的String类型。它代表UTF-8字符集合。相比Java的String类,Hadoop中的Text类时可变的。

5.1.2 Hadoop与Java序列化的区别

  在此有一个疑问,为什么Hadoop用Writable接口来序列化而不使用Java序列化?让我们使用上面演示程序中的javaSerializeToByteString()方法。在该方法中Java提供ObjectOutputStream类来把对象序列化为字节流。ObjectOutputStream类型有writeObject方法。
程序部分输出结果:

smallInt serialized value using Java serializeraced0005737200116a6176612e6c616e672e496e746567657212e2a0a4f781873802000149000576616c7565787200106a6176612e6c616e672e4e756d62657286ac951d0b94e08b020000787000000064mediumInt serialized value using Java serializeraced0005737200116a6176612e6c616e672e496e746567657212e2a0a4f781873802000149000576616c7565787200106a6176612e6c616e672e4e756d62657286ac951d0b94e08b020000787000100000bigInt serialized value using Java serializeraced00057372000e6a6176612e6c616e672e4c6f6e673b8be490cc8f23df0200014a000576616c7565787200106a6176612e6c616e672e4e756d62657286ac951d0b94e08b0200007870000000011194e7a0

  显然,这个序列化后的值大于Writable序列化后的值。Hadoop是在磁盘或网络上进行序列化和反序列化的,所以简洁有效极为重要。而Java序列化因为要标识对象,所以消耗了更多的字节。

  Java不假设序列化的值类型,这是Java序列化低效的根本原因。这就需要在每个序列化结果中标记类相关的元数据。然而,Writable类则从字节流中读取字段,并假设字节流的类型。更简洁的序列化结果极大地提高了性能。但是这样增加了Hadoop新手的学习难度。另一个缺点是Writable类只适用于Java编程语言。

  编写自定义的Writable类很乏味,因为开发人员不得不考虑网络传输中的类格式。Hadoop中临时引入了Record IO,这个功能使用了记录定义语言(record definition language),同时提供了编译器能把记录定义转换为Writable类。最终,这个功能被废弃,Avro取而代之。

  在Hadoop 0.17之前,任何MapReduce程序中,Map和Reduce任务的键和值都基于Writable类。然而,在之后的版本,Hadoop中的MapReduce作业可以于任何序列化框架集成。这使得有很多序列化框架可供选择。每个序列化框架都带来了性能提升,有的是以简洁见长,有的是以序列化和反序列化的速度见长,或两者兼备。

原创粉丝点击