总结HDFS的I/O操作

1、数据完整性

IO操作过程中难免会出现数据丢失或脏数据,数据传输得量越大出错得几率越高。校验错误最常用得办法就是传输前计算一个校验和,传输后计算一个校验和,两个校验和如果不相同就说明数据存在错误,比较常用得错误校验码是CRC32.

hdfs数据完整性

hdfs写入的时候计算出校验和,然后每次读的时候再计算校验和。要注意的一点是,hdfs每固定长度就会计算一次校验和,这个值由io.bytes.per.checksum指定,默认是512字节。因为CRC32是32位即4个字节,这样校验和占用的空间就会少于原数据的1%。1%这个数字在hadoop中会经常看到。以后有时间会整理一份hadoop和1%不得不说的故事。

datanode在存储收到的数据前会校验数据的校验和,比如收到客户端的数据或者其他副本传过来的数据。想一下前面的文章hadoop深入研究:(三)——hdfs数据流中客户端写入数据到hdfs时的数据流,在管道的最后一个datanode会去检查这个校验和,如果发现错误,就会抛出ChecksumException到客户端。

客户端从datanode读数据的时候一样要检查校验和,而且每个datanode还保存了检查校验和的日志,客户端的每一次校验都会记录到日志中。

除了读写操作会检查校验和以外,datanode还跑着一个后台进程(DataBlockScanner)来定期校验存在在它上面的block,因为除了读写过程中会产生数据错误以外,硬件本身也会产生数据错误,比如说位衰减(bit rot)。

如果客户端发现有block坏掉呢,会怎么恢复这个坏的块,主要分几步:

1.客户端在抛出ChecksumException之前会把坏的block和block所在的datanode报告给namenode

2.namenode把这个block标记为已损坏,这样namenode就不会把客户端指向这个block,也不会复制这个block到其他的datanode。

3.namenode会把一个好的block复制到另外一个datanode

4.namenode把坏的block删除掉

如果出于一些原因在操作的时候不想让hdfs检查校验码,在调用FileSystem的open方法前调用setVerityCheckSum方法,并设为为false即可,命令行下可以使用-ignoreCrc参数。

实现

LocalFileSystem继承自ChecksumFileSystem,已经实现了checksum功能,,checksum的信息存储在与文件名同名的crc文件中,发现错误的文件放在bad_files文件夹中。如果你确认顶层系统已经实现了checksum功能,那么你就没必要使用LocalFileSystem,改为用RowLocalFileSystem。可以通过更改fs.file.impl=org.apache.hadoop.fs.RawLoacalFileSystem全局指定,也可以通过代码直接实例化。

Configuration conf=...FileSystem fs=new RawLocalFileSystem();fs.initialize(null, conf);

如果其他的FileSystem想拥有checksum功能的话,只需要用ChecksumFileSystem包装一层即可:

FileSystem rawFs=...FileSystem checksummedFs=new ChecksumFileSystem(fs){} ;

2、文件格式

1. SequenceFile

SequenceFile的文件结构如下:

Sequence.png-272.5kB

根据是否压缩,以及采用记录压缩还是块压缩,存储格式有所不同:

  • 不压缩: 
    按照记录长度、Key长度、Value程度、Key值、Value值依次存储。长度是指字节数。采用指定的Serialization进行序列化。

  • Record压缩: 
    只有value被压缩,压缩的codec保存在Header中。

  • Block压缩: 
    多条记录被压缩在一起,可以利用记录之间的相似性,更节省空间。Block前后都加入了同步标识。Block的最小值由io.seqfile.compress.blocksize属性设置。 
    block-compression.png-217.8kB

2. MapFile

MapFile是SequenceFile的变种,在SequenceFile中加入索引并排序后就是MapFile。索引作为一个单独的文件存储,一般每个128个记录存储一个索引。索引可以被载入内存,用于快速查找。存放数据的文件根据Key定义的顺序排列。 
MapFile的记录必须按照顺序写入,否则抛出IOException。

MapFile的衍生类型:

  • SetFile:特殊的MapFile,用于存储一序列Writable类型的Key。Key按照顺序写入。
  • ArrayFile:Key为整数,代表在数组中的位置,value为Writable类型。
  • BloomMapFile:针对MapFile的get()方法,使用动态Bloom过滤器进行优化。过滤器保存在内存中,只有带key值存在的时候,才会调用常规的get()方法,真正进行读操作。

Hadoop体系下面向列的文件包括RCFile,ORCFile,Parquet的。Avro的面向列版本为Trevni。

3. RCFile

Hive的Record Columnar File,这种类型的文件先将数据按行划分成Row Group,在Row Group内部,再将数据按列划分存储。其结构如下:

rcfile.png-51.9kB

相比较于单纯地面向行和面向列:

QQ截图20160801192417.png-55.6kB

QQ截图20160801192358.png-47.5kB

更详细的介绍参考RCFile论文

4. ORCFile

RCFile(Optimized Record Columnar File)提供了一种比RCFile更加高效的文件格式。其内部将数据划分为默认大小为250M的Stripe。每个Stripe包括索引、数据和Footer。索引存储每一列的最大最小值,以及列中每一行的位置。

OrcFileLayout.png-124.9kB

hive中,如下命令用于使用ORCFile:

CREATE TABLE ... STORED AAS ORC
ALTER TABLE ... SET FILEFORMAT ORC
SET hive.default.fileformat=ORC
 
 
  • 1
  • 2
  • 3

5. Parquet

一种通用的面向列的存储格式,基于Google的Dremel。特别擅长处理深度嵌套的数据。

parquet.png-237.6kB

对于嵌套结构,Parquet将其转换为平面的列存储,嵌套结构通过Repeat Level和Definition Level来表示(R和D),在读取数据重构整条记录的时候,使用元数据重构记录的结构。下面是R和D的一个例子:

AddressBook {
  contacts: {
    phoneNumber: "555 987 6543"
  }
  contacts: {
  }
}
AddressBook {
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

columns_0.png-34.3kB


3、压缩和解压

Hadoop 作为一个较通用的海量数据处理平台,每次运算都会需要处理大量数据,我们会在 hadoop 系统中对数据进行压缩处理来优化磁盘使用率,提高数据在磁盘和网络中的传输速度,从而提高系统处理数据的效率。在使用压缩方式方面,主要考虑压缩速度和压缩文件的可分割性。综合所述,使用压缩的优点如下:
1. 节省数据占用的磁盘空间;
2. 加快数据在磁盘和网络中的传输速度,从而提高系统的处理速度。
Hadoop 对于压缩格式的是自动识别。如果我们压缩的文件有相应压缩格式的扩展名(比如 lzo,gz,bzip2 等)。Hadoop 会根据压缩格式的扩展名自动选择相对应的解码器来解压数据,此过程完全是 Hadoop 自动处理,我们只需要确保输入的压缩文件有扩展名。
Hadoop 对每个压缩格式的支持, 详细见下表:

压缩格式 工具 算法 扩展名 多文件 可分割性
DEFLATE DEFLATE .deflate
GZIP gzip DEFLATE .gzp
ZIP zip DEFLATE .zip 是,在文件范围内
BZIP2 bzip2 BZIP2 .bz2
LZO lzop LZO .lzo

性能对比

 Hadoop 下各种压缩算法的压缩比,压缩时间,解压时间见下表:

表 2. 性能对比
压缩算法 原始文件大小 压缩文件大小 压缩速度 解压速度
gzip 8.3GB 1.8GB 17.5MB/s 58MB/s
bzip2 8.3GB 1.1GB 2.4MB/s 9.5MB/s
LZO-bset 8.3GB 2GB 4MB/s 60.6MB/s
LZO 8.3GB 2.9GB 49.3MB/s 74.6MB/s

因此我们可以得出:

1) Bzip2 压缩效果明显是最好的,但是 bzip2 压缩速度慢,可分割。

2) Gzip 压缩效果不如 Bzip2,但是压缩解压速度快,不支持分割。

3) LZO 压缩效果不如 Bzip2 和 Gzip,但是压缩解压速度最快!并且支持分割!

这里提一下,文件的可分割性在 Hadoop 中是很非常重要的,它会影响到在执行作业时 Map 启动的个数,从而会影响到作业的执行效率!

所有的压缩算法都显示出一种时间空间的权衡,更快的压缩和解压速度通常会耗费更多的空间。在选择使用哪种压缩格式时,我们应该根据自身的业务需求来选择。

下图是在本地压缩与通过流将压缩结果上传到 BI 的时间对比。

使用方式

MapReduce 可以在三个阶段中使用压缩。

1. 输入压缩文件。如果输入的文件是压缩过的,那么在被 MapReduce 读取时,它们会被自动解压。

2.MapReduce 作业中,对 Map 输出的中间结果集压缩。实现方式如下:

1)可以在 core-site.xml 文件中配置,代码如下

图 2. core-site.xml 代码示例
图 2. core-site.xml 代码示例

2)使用 Java 代码指定

 conf.setCompressMapOut(true);
 conf.setMapOutputCompressorClass(GzipCode.class);

最后一行代码指定 Map 输出结果的编码器。

3.MapReduce 作业中,对 Reduce 输出的最终结果集压。实现方式如下:

1)可以在 core-site.xml 文件中配置,代码如下

图 3. core-site.xml 代码示例
图 3. core-site.xml 代码示例

2)使用 Java 代码指定

 conf.setBoolean(“mapred.output.compress”,true);
 conf.setClass(“mapred.output.compression.codec”,GzipCode.class,CompressionCodec.class);

最后一行同样指定 Reduce 输出结果的编码器。


4、序列化

1)什么是序列化
    将结构化对象转换成字节流以便于进行网络传输或写入持久存储的过程。
2)什么是反序列化
    将字节流转化为一系列结构化对象的过程。
序列化的用途
    1)作为一种持久化格式
    2)作为一种通信的数据格式
    3)作为一种数据拷贝、克隆机制
序列化的特征:
    1)紧凑:Hadoop中最稀缺的资源是宽带,所以紧凑的序列化机制可以充分的利用宽带。
    2)快速:通信时大量使用序列化机制,因此,需要减少序列化和反序列化的开销。
    3)可扩展:随着通信协议的升级而可升级。
    4)互操作:支持不同开发语言的通信。
hadoop 1.x 序列化仅满足了紧凑和快速的特点。

Java序列化和反序列化
1)创建一个对象实现了Serializable
2)序列化:ObjectOutputStream.writeObject(序列化对象)
        反序列化:ObjectInputStream.readObject()返回序列化对象

Hadoop序列化
     Hadoop的序列化不采用Java的序列化,而是实现了自己的序列化机制。

     Hadoop通过Writable接口实现的序列化机制,不过没有提供比较功能,所以和Java中的Comparable接口合并,提供了一个接口WritableComparable。

    Writable接口提供了两个方法,Write和readFiles



外部集合的比较器

Comparable是用于集合内部的数据比较,在java.lang包中,Comparator是用于集合外部的数据比较,在java.util包中。


Hadoop序列化类
    实现了WritableComparable接口的类
    基础类:BooleanWritable,ByteWritable,IntWritable,VIntWritable,
FIntWritable,LongWritbale,VLongWritavle,DoubleWritable
    高级类:NullWritable,Text,BytesWritable,MDSHash,ObjectWritable,
GenericWritable

    仅实现了Writable接口的类
    数组:AbstractWritable,TwoDArrayWritable
    映射:AbstractMapWritable,MapWritable,SortedMapWritable



序列化框架

apache avro
1.丰富的数据结构类型
2.快速可压缩的二进制数据形式
3.存储持久数据的文件容器
4.远程过程调用RPC
5.简单的动态语言结合功能,Avro和动态语言结合后,读写文件和使用RPC协议都不需要生成代码,而代码生成作为一种可选的优化只值得在静态类型语言中实现。

Facebook Thrift
1.是一种可伸缩的跨语言服务的发展软件框架。
2.它结合了功能强大的软件堆栈的代码生成引擎,以建设服务,工作效率和无缝地与C++,C#,Java,Python和PHP和Ruby结合。
3.允许定义一个简单的定义文件中的数据类型和服务接口,以作为输入文件,编译器生成代码用来方便地生成RPC客户端和服务器通信的无缝跨编程语言。

Google Protocolbuffer
PB是Google开源的一种轻量级的结构化数据存储格式,可以用于结构数据的序列化和反序列化,很适合做数据存储或RPC数据交换格式

PB优点
1)和XML相比,更小、更快、也更简单。只需使用protobuf对数据结构进行一次描述,即可利用各种不同语言或从各种不同数据流中对数据化结构进行读写。
2)“向后”兼容性好,不必破坏已部署的、依靠老数据格式的程序就可以对数据结构进行升级。
3)语义清晰,无需类似XML解析器的东西(因为Protobuf编译器会将.proto文件编译生成对应的数据访问类以对Protobuf数据进行序列化、反序列化操作)。

PB的缺点
1)Protobuf与XML相比也有不足之处。它功能简单,无法用来表示复杂的概念。
2)由于文本并不适合用来描述数据结构,所以Protobuf也不适合用来对基于文本的标记文档(如HTML)建模。另外,由于XML具有某种程度上的自解释性,它可以被人直接读取编辑,在这一点上Protobuf不行,它以二进制的方式存储,除非你有.proto定义,否则你没法直接读出Protobuf的任何内容

   
   
  1. import java.io.DataInput;
  2. import java.io.DataInputStream;
  3. import java.io.DataOutput;
  4. import java.io.DataOutputStream;
  5. import java.io.File;
  6. import java.io.FileInputStream;
  7. import java.io.FileOutputStream;
  8. import java.io.IOException;
  9. import org.apache.hadoop.io.IntWritable;
  10. import org.apache.hadoop.io.Text;
  11. import org.apache.hadoop.io.WritableComparable;
  12. public class TestWritableComparable {
  13. public static void main(String[] agrs) throws Exception{
  14. Student s = new Student("li",20,"man");
  15. FileOutputStream fout = new FileOutputStream(
  16. new File("/usr/local/test.txt"));
  17. DataOutputStream out = new DataOutputStream(fout);
  18. s.write(out);
  19. fout.close();
  20. out.close();
  21. Student s1 = new Student();
  22. FileInputStream fin = new FileInputStream(
  23. new File("/usr/local/test.txt"));
  24. DataInputStream in = new DataInputStream(fin);
  25. s1.readFields(in);
  26. System.out.println(s1.toString());
  27. }
  28. }
  29. class Student implements WritableComparable<Student>{
  30. private Text name = new Text();
  31. private IntWritable age = new IntWritable();
  32. private Text sex = new Text();
  33. @Override
  34. public String toString() {
  35. return "Student [name=" + name + ", age=" + age + ", sex=" + sex + "]";
  36. }
  37. public Student() {
  38. super();
  39. }
  40. public Student(String name, int age, String sex) {
  41. super();
  42. this.name = new Text(name);
  43. this.age = new IntWritable(age);
  44. this.sex = new Text(sex);
  45. }
  46. public Text getName() {
  47. return name;
  48. }
  49. public void setName(Text name) {
  50. this.name = name;
  51. }
  52. public IntWritable getAge() {
  53. return age;
  54. }
  55. public void setAge(IntWritable age) {
  56. this.age = age;
  57. }
  58. public Text getSex() {
  59. return sex;
  60. }
  61. public void setSex(Text sex) {
  62. this.sex = sex;
  63. }
  64. @Override
  65. public void readFields(DataInput in) throws IOException {
  66. name.readFields(in);
  67. age.readFields(in);
  68. sex.readFields(in);
  69. }
  70. @Override
  71. public void write(DataOutput out) throws IOException {
  72. name.write(out);
  73. age.write(out);
  74. sex.write(out);
  75. }
  76. @Override
  77. public int compareTo(Student o) {
  78. int result = 0;
  79. if((result = name.compareTo(o.getName())) != 0){
  80. return result;
  81. }
  82. if((result = age.compareTo(o.getAge())) != 0){
  83. return result;
  84. }
  85. if((result = sex.compareTo(o.getSex())) != 0){
  86. return result;
  87. }
  88. return result;
  89. }
  90. }

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值