hadoop深入研究:(十三)——序列化框架

hadoop深入研究:(十三)——序列化框架


Mapreduce之序列化框架(转自http://blog.csdn.net/lastsweetop/article/details/9376495)

框架简介

大部分的MapReduce程序都使用Writable键–值对作为输入和输出,但这并不是Hadoop强制使用的,其他序列化机制也能和Hadoop配合,并应用于MapReduce中。

目前,除了前面介绍过的Java序列化机制和Hadoop使用的Writable机制,还流行其他序列化框架,如Hadoop Avro、Apache Thrift和Google Protocol Buffer。

MapReduce仅仅可以支持Writable做key,value吗?答案是否定的。事实上,可以使用任何类型:只要能有一种机制能对每个类型进行类型与二进制表示的来回转换。为此Hadoop提供了一个针对序列化做替换的框架来支持,他们在org.apache.hadoop.io.serializer包中,Writable可以作为MapReduce支持的类型也是因为实现了这个框架,类不多,我们从几个接口说起。

Hadoop提供了一个简单的序列化框架API,用于集成各种序列化实现,该框架由Serialization实现(在org.apache.hadoop.io.serializer包中)。

Serialization是一个接口,使用抽象工厂的设计模式.定义了一组接口,判断是否支持输入的类,根据输入的类给出序列化接口和反序列化接口

复制代码

/**
 * 
 * 包装一个序列化/反序列化对 (抽象工厂类)*/public interface Serialization<T> {  
  /**
   * 允许客户端进行测试:给的序列化是否支持给定的类   */
  boolean accept(Class<?> c);  
  /**
   * 获得用于序列化对象的Serializer实现   */
  Serializer<T> getSerializer(Class<T> c);  
  /**
   * 获得用于反序列化对象的Deserializer实现*/
  Deserializer<T> getDeserializer(Class<T> c);
}

复制代码

参考了:Hadoop的简单序列化框架 - cjt1991 - 博客园 - http://www.cnblogs.com/java-cjt/p/4443852.html

Serializer 定义了一组接口,打开流,序列化,关闭流

如果需要使用Serializer来执行序列化,一般需要通过Open方法来打开Serializer,open()方法传入一个底层的流对象,然后就可以使用serializer()方法序列化对象对底层的流中。最后序列化结束时,通过close()方法关闭Serializer。

public interface Serializer <T>  {  
    void open(java.io.OutputStream outputStream) throws java.io.IOException;  
  
    void serialize(T t) throws java.io.IOException;  
  
    void close() throws java.io.IOException;  
}  

Deserializer

定义了一组接口,打开流,反序列化,关闭流

public interface Deserializer <T>  {  
    void open(java.io.InputStream inputStream) throws java.io.IOException;  
    T deserialize(T t) throws java.io.IOException;  
    void close() throws java.io.IOException;  
}  

 copy

WritableSerialization

如果你想自己定义一个类似Writable这样的框架,那么你首先需要的就是实现上面三个接口,那么我们先来看下Writable是如何实现的。

public class WritableSerialization extends Configured   
  implements Serialization<Writable> {  
    
  static class WritableDeserializer extends Configured   
    implements Deserializer<Writable> {  
  
    private Class<?> writableClass;  
    private DataInputStream dataIn;  
      
    public WritableDeserializer(Configuration conf, Class<?> c) {  
      setConf(conf);  
      this.writableClass = c;  
    }  
      
    public void open(InputStream in) {  
      if (in instanceof DataInputStream) {  
        dataIn = (DataInputStream) in;  
      } else {  
        dataIn = new DataInputStream(in);  
      }  
    }  
      
    public Writable deserialize(Writable w) throws IOException {  
      Writable writable;  
      if (w == null) {  
        writable   
          = (Writable) ReflectionUtils.newInstance(writableClass, getConf());  
      } else {  
        writable = w;  
      }  
      writable.readFields(dataIn);  
      return writable;  
    }  
  
    public void close() throws IOException {  
      dataIn.close();  
    }  
      
  }  
    
  static class WritableSerializer implements Serializer<Writable> {  
  
    private DataOutputStream dataOut;  
      
    public void open(OutputStream out) {  
      if (out instanceof DataOutputStream) {  
        dataOut = (DataOutputStream) out;  
      } else {  
        dataOut = new DataOutputStream(out);  
      }  
    }  
  
    public void serialize(Writable w) throws IOException {  
      w.write(dataOut);  
    }  
  
    public void close() throws IOException {  
      dataOut.close();  
    }  
  
  }  
  
  public boolean accept(Class<?> c) {  
    return Writable.class.isAssignableFrom(c);  
  }  
  
  public Deserializer<Writable> getDeserializer(Class<Writable> c) {  
    return new WritableDeserializer(getConf(), c);  
  }  
  
  public Serializer<Writable> getSerializer(Class<Writable> c) {  
    return new WritableSerializer();  
  }  
  
}


两个内部静态类分别实现Serializer和Deserializer接口,然后getSerializer和getDeserializer分别实例化WritableSerializer和WritableDeserializer,

accept方法仅仅是判断输入类是否是Writable的子类。

通过"io.serializations"指定以实现Serialization,各个类之间通过逗号隔开,默认的SerializationWritableSerialization和Avro中Serialization

这也就是说默认情况下,只有Writable和Avro里的对象可以在MapReduce中使用。

那么你可能有疑问了,hadoop是如何知道一个类该交给哪个Serialization呢,答案也在这个包中,请看

SerializationFactory

先看他的构造器

[java] view plain copy

public SerializationFactory(Configuration conf) {  
    super(conf);  
    for (String serializerName : conf.getStrings("io.serializations",   new String[]{"org.apache.hadoop.io.serializer.WritableSerialization"})) {  
      add(conf, serializerName);  
    }  
  }  


可知他是从"io.serializations"属性指定的实现了Serialization的类,然后再看他是如何知道选哪个Serialization

[java] view plain copy

public <T> Serialization<T> getSerialization(Class<T> c) {  
   for (Serialization serialization : serializations) {  
     if (serialization.accept(c)) {  
       return (Serialization<T>) serialization;  
     }  
   }  
   return null;  
 }  


好吧,就是这么简单,判断一下是否是对应的子类而已。


这个包里还实现了JavaSerialization,其实就是Java对象的序列化,很多人觉得,这个好简单的,我只要实现java中的序列化接口就可以了,

不用那么费事搞什么Writable和Avro,但是,千万别这么想,非常不推荐使用java对象的序列化,并且详尽的解释为什么不推荐:

Hadoop目前支持两个Serialization实现分别是支持Writable机制的WritableSerialization和支持Java序列化的JavaSerialization。通过JavaSerialization可以在MapReduce程序中方便的使用java类型,如int或String,但Java的ObjectSerialization不如Hadoop的序列化机制有效,非特殊情况不要尝试

 为什么不使用java序列化

1.Java序列化不够灵活,为了更好的控制序列化的整个流程所以使用Writable

2.java序列化不符合序列化的标准,没有做一定的压缩,java序列化首先写类名,然后再是整个类的数据,而且成员对象在序列化中只存引用,成员对象的可以出现的位置很随机,既可以在序列化的对象前,也可以在其后面,这样就对随机访问造成影响,一旦出错,整个后面的序列化就会全部错误,但是
Writable完美的弥补了这一点,因为Writable中每一条纪录间是相互独立的
3.Java序列化每次序列化都要重新创建对象,内存消耗大,而Writable是可以重用的。

 Hadoop序列化机制的特征

        紧凑:由于带宽是Hadoop集群中最稀缺的资源,一个紧凑的序列化机制可以充分利用数据中心的带宽。

        快速:在进程间通信(包括MapReduce过程中涉及的数据交互)时会大量使用序列化机制;因此,必须尽量减少序列化和反序列化的开销。

        可扩展:随着系统的发展,系统间通信的协议会升级,类的定义会发生变化 ,序列化机制需要支持这些升级和变化。

        可操作:可以支持不同开发语言的通信:如C++和Java间的通信。这样的通信,可以通过文件 (需要精心设计文件的格式)或者后者介绍的IPC机制实现。

 

Java序列化在Hadoop中不适应原因:占用存储空间大,在反序列化中不断创建大量新对象,而Hadoop反序列化可以重用对象,在已有对象上进行反序列化操作。来源: http://blog.sina.com.cn/s/blog_7ed002b30101j6pa.html


序列化IDL

为了和其他语言交互,必须定义序列化的IDL,原先定义的IDL在org.apache.hadoop.record包里,但是后来一直没用起来就淘汰掉了,现在比较常用的就是Avro,后面我们会重点着墨讲解。

Apache的Thrift和Google的Protocol Buffer也是比较流行的序列化框架,但是在Hadoop里使用是有限的,只用于RPC和数据交互,不过有一个开源项目elephant-bird可以把他们使用在MapReduce上。


总结:

A 默认序列化框架是 Writable接口, 缺点: 缺乏语言的可移植性

B 不使用java Serialization, 缺点: 不够精简, 用起来非常纠结, 无法做到 精简,快速, 可扩展, 支持互操作

C Apache Thrift 一般用来作为二进制 数据的永久存储格式, Mapreduce格式对该类的支持有限

D Google Protocol框架 一般用来做二进制数据的永久存储格式,Mapreduce格式对该类的支持有限

E: Avro 更加有生命力, 与编程语言无关, 非常使用hadoop的大规模数据处理。

Avro模式议案使用JSON来写, 数据通常采用二进制格式来编码

和其他序列化类库想比, Avro的性能更好。

=======================================================================================

hadoop权威指南 第三版 page 127

原文:hadoop序列化框架 - qiezikuaichuan的专栏 - CSDN博客 - http://blog.csdn.net/qiezikuaichuan/article/details/48676181


参考:

hadoop深入研究:(十三)——序列化框架 - 独自登高楼 望断天涯路 - CSDN博客 - http://blog.csdn.net/lastsweetop/article/details/9376495#t5


 

通俗 Python 设计模式——工厂模式 - 知乎专栏 - https://zhuanlan.zhihu.com/p/23803353

抽象工厂模式和工厂模式的区别? - 知乎 - https://www.zhihu.com/question/20367734

抽象工厂模式 - cbf4life - 博客园 - http://www.cnblogs.com/cbf4life/archive/2009/12/23/1630612.html

《JAVA与模式》之抽象工厂模式 - java_my_life - 博客园 - http://www.cnblogs.com/java-my-life/archive/2012/03/28/2418836.html

设计模式系列 - 抽象工厂模式 - 后端 - 掘金 - https://juejin.im/entry/58ddc44a8d6d8100613ee7d0

Java 的 23 种设计模式全解析 - 后端 - 掘金 - https://juejin.im/entry/58faca0a1b69e600588cd952
java-设计模式之-抽象工厂模式 | 戒修-沉迷技术的小沙弥 - https://leokongwq.github.io/2016/11/26/java-DesignPatterns-abstractFactory.html


原文地址:https://www.cnblogs.com/liango/p/7144013.html