《Hadoop权威指南》学习笔记(5)——Hadoop的I/O操作(1)

因为这部分内容比较多,所以打算把这部分分成三次来写。第一部分是关于数据完整性和数据的压缩。

数据完整性

首先要了解的是,当系统中需要处理的数据量很大,达到Hadoop的处理极限时,数据会有较高的被损坏概率。

检测数据是否损坏的常见措施是——校验和。在数据第一次引入系统以及通过不可靠通道进行传输时分别计算校验和,并判断其是否匹配,若不匹配,则认为数据已损坏。需要注意的是,校验和也有可能会损坏,但因为校验和很小,所以损坏的可能性也很小。

常用的错误检测码是CRC-32(32位循环冗余校验)。Hadoop ChecksumFileSystem 使用这种检测码,而HDFS使用的是一个更有效的变体CRC-32C。

HDFS

HDFS会对写入 的所有数据计算校验和,并在读取数据时验证校验和。它针对每个由dfs.bytes-per-checksum指定字节的数据计算校验和,默认为512个字节,而CRC-32校验和是4个字节,所以额外开销小于1%。

datanode负责在收到数据后、存储该数据及其校验和之前对数据进行验证,正在收到客户端的数据或复制其他datanode的数据时执行这个操作。对于一系列datanode组成的管线,由管线中的最后一个datanode负责验证校验和。若检测道错误,会小客户端发送一个IOException的一个子类,从而使应用以其特定方式进行处理。

并且,每个datanode会在一个后台线程中运行一个DataBlockScanner,从而定期验证存储在这个 datanode 上的所有数据。这种方法可以有效解决物理存储媒体上的位损坏。

客户端从datanode读取数据时也会验证校验和,并将它们与datanode中存储的校验和进行比较。每个datanode会保存一个用于验证的校验和日志。客户端成功验证一个数据块后,会告知这个datanode,datanode由此更新日志。

因为HDFS中存储了每个数据的复本,因此它可以通过复本来修复损坏的数据块。基本思路是:客户端在读取数据块时,若检测到错误,先向namenode报告已损坏的数据块及其正在尝试读操作的这个datanode,再抛出ChecksumException异常。namenode将这个数据块复本标记为已坏,这样它不再讲客户端处理请求发送到这个节点,或者尝试将这个复本复制到另一个datanode。然后,它安排这个该数据块的一个副本复制到另一个datanode。之后再删除已损坏的数据块复本。这样就完成了修复。

如果想要禁用校验和验证也是可以的,在使用open()方法读取文件前,将false传递给FileSystem对象的setVerifyChecksum()方法即可。

可以用hadoop的命令fs -checksum来检查一个文件的校验和。

LocalFileSystem

Hadoop 的LocalFileSystem执行客户端的校验和验证。

也就是说,在写入一个名为filename的文件时,文件系统客户端会在包含每个文件块校验和的的同一个目录内新建一个filename.crc隐藏文件。文件块大小由dfs.bytes-per-checksum控制。文件块大小作为元数据存储在.crc文件中,所以即使文件块大小的设置以变化,仍可正确读回文件。

校验和的计算代价很低,对大部分应用来说,付出这样的开销以保证数据完整性是可以接受的。
当然,我们也可以近用校验和计算,特别是在底层文件系统本身支持校验和时。这种情况下用RawLocalFileSystem替代LocalFileSystem
具体而言,有两种可选方案:
①将属性fs.file.impl属性设置为org.apache.hadoop.fs.RawLocalFileSystem进而实现对文件URI的重映射,这种方法适用于想要在应用中实现全局校验和验证。
②直接新建一个RawLocalFileSystem实例。例如想要对一些读操作禁用校验和:

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

LocalFileSystem通过ChecksumFileSystem来完成自己的任务,通过这个类可以非常容易的像其他无校验和系统的文件系统加入校验和。一般用法如下:

FileSystem rawfs = ...
FileSystem checksummedFs = new ChecksumFileSystem(rawFs);

压缩

文件压缩有两大好处:减少存储文件所需要的磁盘空间,并加速数据在网络和磁盘上的传输。
压缩格式有很多,我见过的有.rar.zip.gz等,下面这张表列出了与Hadoop结合使用的常见压缩方法。
压缩格式总结
压缩算法需要权衡空间/时间,压缩和解压缩的速度越快,往往意味着可节省的空间越少。上述的几种压缩工具都提供了九个不同的选项来控制压缩时的权衡,-1为优化压缩速度,-9为优化压缩空间。
在其中,gzip 在空间/时间的权衡中居于二者中间;bzip2 的压缩能力强于 gzip,但压缩速度要慢一些;LZO,LZ4 和 Snappy 均优化压缩速度,比 gzip 要快很多,但是压缩效率会差一些;Snappy 和 LZ4 的解压缩速度比 LZO 快很多。

codec

Codec 是压缩 - 解压缩的一种实现。Hadoop 中,一个对CompressionCodec接口的实现代表一个codec,其中包括了对应格式的压缩和解压缩算法。
Hadoop的压缩codec
注意其中的LZO是没有包含在Apache的发行版本中的,因此需要单独下载(GithubGoogle)。

CompressionCodec包含两个函数,createOutputStream(OutputStream out)方法用于对写入输出数据流的数据进行压缩,它在底层数据流中对需要以压缩格式写入在此之前尚未压缩的数据新建一个CompressionOutputStream对象;createInputStream(InputStream in)方法用于对输入数据流中读取的数据进行解压缩,它从底层数据流读取解压缩后的数据。

在读取一个压缩文件时,通常可以通过文件扩展名推断需要使用哪个codec,如一个文件以.gz结尾,那么就用GzipCodec来读取。
CompresssionCodecFactory提供了getCodec()方法将文件扩展名映射到一个CompressionCodec。使用方式如下:

String uri = ...
Path inputPath = ...
Configuration conf = ...
CompressionCodecFactory factory = new CompressionCodecFactory(conf);
CompressionCodec codec = factory.getCodec(inputPath);
String outputUri = 
	CompressionCodecFactory.removeSuffix(uri, codec.getDefaultExtension());

CompressionCodecFactory 加载了上面的表中处理LZO外的所有 codec 以及 io.compression.codec 配置属性列表中的所有 codec。默认情况下该属性列表为控,在拥有一个希望注册的定制 codec 时才需要加以修改。

为了提高性能,最好使用“原生”类库来实现压缩和减压缩。“原生”类库是相对 Java 实现来说的,所有格式都有原生类库实现,但并非都有 Java 实现。默认情况下,Hadoop会根据自身运行的平台搜索原生代码库,若找到相应的代码库就会自动加载。但在某些情况下,需要禁用原生代码库,那么将属性io.native.lib.available的值设置成false即可,者可以确保在有 Java 代码库的情况下使用它。
若使用原生代码库并需要在应用中执行大量的压缩和解压缩操作,可以考虑使用CodecPool,它支持反复使用压缩和解压缩,以分摊创建这些对象的开销。

在之前的压缩格式总结表格中我们能注意有一栏的内容是是否支持拆分,这一点是非常重要的。
可以想象一下,一个存储在HDFS中的压缩前大小为 1GB 的文件,在HDFS的块大小设置为 128MB 的情况下,将被存储到8个块中,若将其作为 MapReduce 作业的输入数据,那么 MapReduce 将创建8个分片,每个分片作为一个单独的 map 任务独立处理。
但是,若这个文件是通过 gzip 格式压缩,那么因为无法实现从 gzip 压缩数据流的任意位置读取数据,所以无法将每个数据块单独作为一个输入分片实现工作。这种情况下,MapReduce 不会尝试切分 gzip 文件。但这样牺牲了数据的本地性,也可能会导致作业的运行时间增加。
简单说,就是一些压缩格式不支持数据读取和数据流同步。

但是,LZO格式虽然不可切分,但是如果在预处理LZO文件时,使用包含在Hadoop LZO库文件中的索引工具就可以构建切分点索引,从而使用恰当的 MapReduce 输入格式就可以有效实现文件的可切分特性。
至于在具体的使用中,应该使用哪种压缩格式,有这样一个排序(效率从高到低):
①使用容器文件格式,如顺序文件,Avro数据文件、ORCFiles或Parquet文件,这些文件同时支持压缩和拆分。通常与一个快速压缩工具联合使用,如LZO,LZ4,Snappy。
②使用支持切分的压缩格式,如bzip2;或使用通过索引实现切分的压缩格式,如LZO。
③在应用中将文件分成块,并用任一种压缩格式为每个数据块建立压缩文件,这种情况下要合理选择数据块大小以确保压缩后数据块大小近似于HDFS块的大小。
④存储未经压缩的文件。

想要在 MapReduce 中使用压缩来压缩 MapReduce 作业的输出,有两种方案:
①在作业配置过程中将mapreduce.output.fileoutputformat.compress属性设为true,将mapreduce.output.fileoutputformat.compress.codec属性设为打算使用的压缩 codec 的类名。
②在FileOutputFormat中使用更便捷的方法设置,如;

Job job = new job();
......
FileOutputFormat.setCompressOutput(job, true);
FileOutputFormat.setOutputCompressorClass(job, GzipCodec.class);
......

若为输出生成顺序文件,可以设置mapreduce.output.fileoutputformat.compress.type 属性来控制限制使用压缩格式,默认值为 RECORD(针对每条记录压缩),推荐使用 BLOCK(对于一组记录进行压缩),压缩效率更高。
上述配置文件属性设置可以总结如下表:
MapReduce的压缩属性

虽然 MapReduce 应用读/写的是未压缩的数据,但若对 map 任务的输出进行压缩,也可以获得性能提升,因为 map 任务的输出要写到磁盘并通过网络串数到 reducer 节点,压缩后需要传输的数据变少了。
相应的配置属性如下:
map任务输出的压缩属性

若要通过作业中的代码进行压缩也是可以的,如下:

Configuration cond = new Configuration();
conf.setBoolean(Job.MAP_OUTPUT_COMPRESS, true);
conf.setClass(Job.MAP_OUTPUTCOMPRESS_CODEC, GzipCodec.class, 
	CompressionCodec.class);
Job job = new Job(conf);
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值