5.1 Hadoop数据序列化

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 IntWritable
00000064
smallInt serialized value using VIntWritable
64
mediumInt serialized value using IntWritable
00100000
mediumInt serialized value using VIntWritable
8d100000
bigInt serialized value using LongWritable
000000011194e7a0
mediumInt serialized value using VIntWritable
8b011194e7a0

  不管存放的数值大小,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 serializer
aced0005737200116a6176612e6c616e672e496e746567657212e2a0a4f781873802000149000576616c7565787200106a6176612e6c616e672e4e756d62657286ac951d0b94e08b020000787000000064
mediumInt serialized value using Java serializer
aced0005737200116a6176612e6c616e672e496e746567657212e2a0a4f781873802000149000576616c7565787200106a6176612e6c616e672e4e756d62657286ac951d0b94e08b020000787000100000
bigInt serialized value using Java serializer
aced00057372000e6a6176612e6c616e672e4c6f6e673b8be490cc8f23df0200014a000576616c7565787200106a6176612e6c616e672e4e756d62657286ac951d0b94e08b0200007870000000011194e7a0

  显然,这个序列化后的值大于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作业可以于任何序列化框架集成。这使得有很多序列化框架可供选择。每个序列化框架都带来了性能提升,有的是以简洁见长,有的是以序列化和反序列化的速度见长,或两者兼备。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值