思考问题
HDFS的IO操作总结
Hadoop工程下与I/O相关的包如下:
- org.apache.hadoop.io
- org.apache.hadoop.io.compress
- org.apache.hadoop.io.file.tfile
- org.apache.hadoop.io.serializer
- org.apache.hadoop.io.serializer.avro
除了org.apache.hadoop.io.serializer.avro是用于Avro提供数据序列化操作外,其余都是用户Hadoop的I/O操作。
数据的完整性
校验和方式是检查数据完整性的重要方式。一般会通过对比新旧校验和来确定数据情况,若两者不同则说明数据已经损坏。
HDFS会对写入的所有数据计算校验和,并在读取数据时验证校验和。
常用的错误检测码是CRC-32(循环冗余校验)。任何大小的数据输入均计算得到一个32位的整数校验和。
hadoop采用HDFS作为默认的文件系统,需要考虑两方面的数据完整性:
- 对本地文件I/O的检查
在Hadoop中,本地文件系统的数据完整性由客户端负责。重点在于存储读取文件时进行校验和的处理。
- 具体做法是:每当hadoop创建文件a时,hadoop就会同时在同一个文件夹下创建隐藏文件.a.crc,这个文件记录了 文件a的校验和。针对数据文件的大小,每512个字节会生成一个32位的校验和(4字节),可以在src/core/core-default.xml中通过修改io.bytes.per.checksum的大小来修改每个校验和所针对的文件的大小。
- 在hadoop中,校验和系统单独为一类 ---org.apache.hadoop.fs.ChecksumFileSystem,当需要校验和机制时,可以很方便的调用它来服务。
- 对HDFS的I/O数据进行检查
一般来说,HDFS会在三种情况下检验校验和: - DataNode接收数据后存储数据前
- DataNode接收数据一般有两种情况:一是客户从客户端上传数据;二是DataNode从其他DataNode上接收数据。当客户端上传数据时,Hadoop会根据预定规则形成一条数据管线。
- 数据按管线流动以完成数据的上传及备份过程,其数据就是先在客户端这个节点上保存数据,备份1在接收数据的同时也会把接收到的数据发送给备份2所在的机器,若过程顺利,三个备份形成的时间相差不多。
- 客户端读取DataNode上的数据时
Ⅰ:在传输数据的最开始阶段,Hadoop会简单地检查数据块的完整性信息;
Ⅱ:依次向各个DataNode传输数据,包括数据头信息、块信息、备份个数、校验和等;
Ⅲ:Hadoop不会在数据每流动到一个DataNode时都检查校验和,它只会在数据流达到最后一个节点时才检查校验和。 - DataNode后台守护进程的定期检查
DataNode会在后台运行DataBlockScanner,这个程序会定期检查此DataNode上的所有数据块。
数据恢复
基本思路是:HDFS存储至少三个相同的数据块,假设数据块1,2,3为相同的三个数据块,
①客户端读取数据块1时,检测到数据块1发生了错误,首先向namenode报告数据块1已经损坏;并抛出异常信息;
②namenode将这个数据块1标记成已损坏,所以namenode不会再分配客户端去读取数据块1,而是分配客户端去读取数据块2或者数据块3;
③为了保证正确数据块1的数量不变,会复制数据块2或3到datanode的数据块4中;
④删除已损坏数据块1,这样就保证复本因子的不变。数据块数量为3.
在hadoop上进行数据读操作时,若发现某数据快失效,读操作涉及的用户,DataNode和NameNode都会尝试来恢复数据块,恢复成功后设置标签,防止其他角色重复恢复。
- 检查已恢复标签
检查一致的数据块恢复标记,若已经恢复,则直接跳过恢复阶段。 - 统计各个备份数据块恢复状态
在这个阶段,DataNode会检查所有出错数据块备份的DataNode,查看这些节点上数据块的恢复情况,将其作为一条记录保存在数据块记录表中。 - 找出所有正确版本数据块中最小长度的版本
DataNode会扫描上一阶段中保存的数据块记录,判断当前副本是否正在恢复;
是:跳过;
否:判断是否配置参数设置了恢复需要保存原副本长度:
是:将恢复长度相同的副本加入待恢复队列;
否:将所有正确的副本加入到待恢复队列。 - 副本同步
若需要保持副本长度,则直接同步长度相同的副本即可,否则以长度最小的副本同步其他副本。
文件格式
- SequenceFile
- SequenceFile是Hadoop API 提供的一种二进制文件,它将数据以<key,value>的形式序列化到文件中。
- 这种二进制文件内部使用Hadoop 的标准的Writable 接口实现序列化和反序列化。它与Hadoop API中的MapFile 是互相兼容的。
- Hive 中的SequenceFile 继承自Hadoop API 的SequenceFile,不过它的key为空,使用value 存放实际的值, 这样是为了避免MR 在运行map 阶段的排序过程。如果你用Java API 编写SequenceFile,并让Hive 读取的话,请确保使用value字段存放数据,否则你需要自定义读取这种SequenceFile 的InputFormat class 和OutputFormat class。
2. RCFile
- RCFile是Hive推出的一种专门面向列的数据格式。 它遵循“先按列划分,再垂直划分”的设计理念。当查询过程中,针对它并不关心的列时,它会在IO上跳过这些列。
- 需要说明的是,RCFile在map阶段从远端拷贝仍然是拷贝整个数据块,并且拷贝到本地目录后RCFile并不是真正直接跳过不需要的列,并跳到需要读取的列, 而是通过扫描每一个row group的头部定义来实现的,但是在整个HDFS Block 级别的头部并没有定义每个列从哪个row group起始到哪个row group结束。所以在读取所有列的情况下,RCFile的性能反而没有SequenceFile高。
3. Avro
- Avro是一种用于支持数据密集型的二进制文件格式。它的文件格式更为紧凑,若要读取大量数据时,Avro能够提供更好的序列化和反序列化性能。
- 并且Avro数据文件天生是带Schema定义的,所以它不需要开发者在API 级别实现自己的Writable对象。最近多个Hadoop 子项目都支持Avro 数据格式,如Pig 、Hive、Flume、Sqoop和Hcatalog。
4. 文本格式
- 除上面提到的3种二进制格式之外,文本格式的数据也是Hadoop中经常碰到的。如TextFile 、XML和JSON。
- 文本格式除了会占用更多磁盘资源外,对它的解析开销一般会比二进制格式高几十倍以上,尤其是XML 和JSON,它们的解析开销比Textfile 还要大,因此强烈不建议在生产系统中使用这些格式进行储存。 如果需要输出这些格式,请在客户端做相应的转换操作。
- 文本格式经常会用于日志收集,数据库导入,Hive默认配置也是使用文本格式,而且常常容易忘了压缩,所以请确保使用了正确的格式。另外文本格式的一个缺点是它不具备类型和模式,比如销售金额、利润这类数值数据或者日期时间类型的数据,如果使用文本格式保存,由于它们本身的字符串类型的长短不一,或者含有负数,导致MR没有办法排序,所以往往需要将它们预处理成含有模式的二进制格式,这又导致了不必要的预处理步骤的开销和储存资源的浪费。
5. 外部格式
- Hadoop实际上支持任意文件格式,只要能够实现对应的RecordWriter和RecordReader即可。其中数据库格式也是会经常储存在Hadoop中,比如Hbase,Mysql,Cassandra,MongoDB。
- 这些格式一般是为了避免大量的数据移动和快速装载的需求而用的。他们的序列化和反序列化都是由这些数据库格式的客户端完成,并且文件的储存位置和数据布局(Data Layout)不由Hadoop控制,他们的文件切分也不是按HDFS的块大小(blocksize)进行切割。
数据压缩
对于任何大容量的分布式存储而言,文件压缩都是必须的,文件压缩带来的好处是:
Ⅰ:减少文件所需的存储空间;
Ⅱ:加快文件在网络上或磁盘上的传输速率;
hadoop关于文件压缩的代码几乎都在package.org.apache.hadoop.io.compress中。
-
hadoop对压缩工具的选择
压缩分割和输入分割
压缩分割和输入分割时很重要的。bzip2支持文件分割,用户可以分开读取每块内容并分别处理之,因此bizip2压缩的文件可分割存储在MapReduce程序中使用压缩
- 在MapReduce中使用压缩非常简答,只需在它进行Job配置时配置好conf就可以了。
- 设置map处理后压缩数据的代码如下:
JobConf conf = new Jobconf();
conf.setBoolean("mapred.compress.map.output",true);
对一般情况,压缩总是好的,无论是对最终结果的压缩还是对map处理后的中间数据进行压缩。
- 压缩小结
- 最快的压缩方法:gzip -1 filename (-1为最快压缩,但空间减少最少,-9为节省最大空间压缩)
- CodeC压缩/解压缩算法类:org.apache.hadoop.io.compress.DefaultCodeC/GzipCodeC/BZip2CodeC/LzopCodeC/Lz4CodeC/SnappCodeC
- CompressionCodeC压缩/解压缩:写入输入流压缩方法-->createOutPutStream在底层数据流中写入CompressionOUtPutStream对象;
相反则是createInputStream获取CompressionInputStream对象; - CompressionCodeFactory推断CompressionCodeC:例如文件名以.gz结尾使用GzipCodeC
- InputStream in = codec.createInputStream(fs.open(new path(args[0])));
- OutputStream out = fs.create(new Path(outputuri))
- CodeCPool:支持反复压缩和解压缩,以分摊创建这些对象的开销;
- Map端压缩: mapred.map.output.compression.codeC 设为org.apache.hadopio.cpmpress.DefaultCodeC
序列化
序列化是将对象转化为字节流以便在网络上传输或写到磁盘进行永久存储的过程,或者说用字节流描述对象的方法。
与序列化相对的是反序列化,反序列化就是将字节流转化为对象的方法。
序列化有两个目的 :
- 进程间通信
- 数据持久性存储
hadoop采用RPC来实现进程间通信 。一般而言,RPC的序列化机制有以下特点:
紧凑:紧凑的格式可以充分利用带宽,加快传输速度;
快速:能减少序列化和反序列化的开销;
可扩展性:可以逐步改变,是客户端与服务器端直接相关 的;
互操作性:支持不同语言编写的客户端与服务器交互数据;
在hadoop中,序列化处于核心地位。因为无论是存储文件还是计算中传输数据,都需要执行序列化过程。hadoop并没有采用java提供的序列化机制(java object serialization),而是自己重新写了一个序列化机制Writeables,它具有紧凑,快速的特点,更方便。
- Writable类
Writable是hadoop的核心,hadoop通过它定义了hadoop中基础的数据类型和操作。一般来说,无论是 上传下载数据还是运行MapReduce程序,无时无刻不需要使用Writable类。
Writeable类中只定义了两种方法:序列化输出数据流和反序列化输入数据流;
或者这样分类:将其状态写到DataOutput二进制流(序列化)和从DataInput二进制流读取状态(发序列化)
- Hadoop的比较器
WritableComparable是Hadoop中的接口类。
在执行MapRedure,我们知道会对key默认的排序输出,就是WritableComparable的功劳。 - writable类中的数据类型
java基本类;例如:boolean,byte,int,float,long,double(6个)
其他类
NullWritable:这是一个占位符,他的序列化长度为0
BytesWritable和ByteWritable:BytesWritable是一个二进制数据数组的封装;ByteWritable是二进制数据封装
Text:hadoop对string类型的重写,使用标准的UTF-8编码。可以理解成Java中String类,但有一定的区别,例如索引、可变性等;
ObjectWritable:一种多类型的封装。
ArrayWritable和TwoArrayWritable:针对数组和二维数组的构建的数据类型。
MapWritable和SortedMapWritable:分别是java.util.Map()和java.util.SortedMap()的实现。
CompressedWritable:保存压缩数据的 数据结构。
GenericWritable:通用的数据封装类型。
VersionedWritable:一个抽象的版本检查类。
实现自己的hadoop数据类型
hadoop可以支持实现自己的数据类型序列化框架Avro
尽管大部分MapReduce程序使用的是Writeable类型的key和value,但不是强制使用。可以使用任何类型,只要能有一种机制对每个类型进行类型和二进制表示来回转换。
序列性框架就是解决这种问题,它是用一个Serialization实现来表示,Avro就是一种序列化框架。
- 针对MapReduce的文件类
对于默写应用,需要特殊的数据结构来存储自己的数据。针对此需求,hadoop提供了一些更高层次的容器。
hadoop定义了一些文件数据结构以适应Mapreduce编程框架的需要,其中SequenceFile和MapFile两种类型非常重要。
Map输出的中间结果就是由他们表示的,其中,MapFile是经过排序并带有索引的SequenceFile.
(1)SequenceFile类
记录的是key/value对的列表,是序列化之后的二进制文件,因此不能直接查看,可通过命令查看文件内容。
hadoop fs -text MySequenceFile
sequence有三种不同类型的结构:
未压缩的key/value对;
记录压缩的key/value对(只有value被压缩);
Block压缩的key/value对;
注:未压缩和只压缩value的SequenceFile数据格式,两种数据格式相同。
(2)MapFile类
MapFile的使用与SequenceFile类似,与SequenceFile生成一个文件不同,这个程序生成一个文件夹。
(3)ArrayFile,SetFile和BloomMapFile
ArrayFile继承自MapFile,保存的是从Integer到value的映射关系。
SetFile继承自MapFile,同JAVA的set类似,仅仅是一个key的集合,而没有任何value。
BloomMapFile在实际使用中发挥的作用和MapFile类似,只是增加了过滤功能。