数据完整性
Hadoop用户肯定都希望系统在存储和处理数据时,数据不会有任何丢失或损坏。 但是,尽管磁盘或网络上的每个I/O操作不太可能将错误引入自己正在读写的数据,但是,如果系统需要处理的数据量大到Hadoop能够处理的极限,数据裤损坏的概率还是很髙的。
检测数据是否损坏的常见措施是,在数据第一次引入系统时计算校验和(checksum), 并在数据通过一个不可靠的通道进行传输时再次计算校验和,这样就能发现数据是否损坏。如果计算所得的新校验和和原来的校验和不匹配,我们就认为数据已损坏。但该技术并不能修复数据——它只能检测出数据错误。(这正是不使用低端硬件的原因。具体说来,一定要使用ECC内存。)注意,校验和也是可能损坏的,不只是数据,但由于校验和比数据小得多,所以损坏的可能性非常小。
常用的错误检测码是CRC-32(循环冗余校验),任何大小的数据输入均计算得到一 个32位的整数校验和。
HDFS的数据完整性
HDFS会对写入的所有数据计算校验和,并在读取数据时验证校验和。它针对每个由io.bytes.per.checksum指定字节的数据计算校验和。默认惰况下为512个字节,由于CRC-32校验和是4个字节,所以存储校验和的额外开销低于1%。
datanode负责在验证收到的数据后存储数据及其校验和。它在收到客户端的数据或复制期间其他daunode的数据时执行这个操作。正在写数据的客户端将数据及其校验和发送到由一系列datanode组成的管线,管线中最后一个datanode负责验证校验和。如果datanode检测到错误,客户端便会收到一个ChecksumException异常,它是IOException异常的一个子类,后者应以应用程序特定的方式来处理,比如重试这个操作。
客户端从datanode读取数据时,也会验证校验和,将它们与datanode中存储的校验和进行比较。每个datanode均持久保存有一个用于验证的校验和日志(persistent log of checksum verification),所以它知道毎个数据块的最后一次验证时间。客户端成功验证一个数据块后,会告诉这个datanode,datanode由此更新日志。保存这些统计信息对于检测损坏的磁盘很有价值。
不只是客户端在读取数据块时会验证校验和,毎个datanode也会在一个后台线程中运行一个DataBlockScanner,从而定期验证存储在这个datanode上的所有数据块。该项措施是解决物理存储媒体上位损坏的有力措施。
由于HDFS存储着每个数据块的复本(replica),因此它可以通过复制完好的数据复本来修复损坏的数据块,进而得到一个新的、完好无损的复本。基本思路是,客户端在读取数据块时,如果检测到错误,就向namenode报告已损坏的数据块及其正 在尝试读操作的这个datanode,最后才抛出ChecksumException异常。namenode 将这个已损坏的数据块的复本标记为已损坏,所以并不需要直接与datanode联系,或尝试将这个复本复制到另一个datanode。之后,它安排这个数据块的一个复本复制到另一个datanode,如此一来,数据块的复本因子(replication factor)又回到期望水平。此后,已损坏的数据块复本便被删除。
在使用open()方法读取文件之前,将false值传递给FileSystem对象的 setVerifyChecksum()方法,既可以禁用校验和验证。如果在命令解释器中结合 使用-ignoreCrc 和-get 选项或等价的-copyToLocal命令,也可以达到相同的效果。如果有一个已损坏的文件需要检查并决定如何处理,这个特性是非常有用的。 例如,可以试试在删除该文件之前是否能够恢复部分数据。
LocalFileSystem
Hadoop的LocalFileSystem执行客户端的校验和验证。这意味着在你写入一个名为filename的文件时,文件系统客户端会明确地在包含每个文件块校验和的同一个目录内新建一个名为.filename.crc的隐藏文件。就像HDFS一样,文件块的大小由属性io•bytes•per•checksum控制,默认为512个字节。文件块的大小作为元数据存储在.crc文件中,所以即使文件块大小的设置已经发生变化,仍然可以正确读回文件。在读取文件时需要验证校验和,并且如果检测到错误,LocalFileSystem 将抛出一个 ChecksumException 异常。
校验和的计算代价是相当低的(在Java中,它们是用本地代码实现的),一般只是增加少许额外的读写文件的时间。对大多数应用来说,为保证数据完整性,这样的额 外开销是可以接受的。但是,我们可以禁用校验和计算,特别是在底层文件系统本身就支持校验和时。在这种情况下,使用RawLocalFileSystem替代 LocalFileSystem。要想在一个应用中实现全局校验和验证,需要将 fs .file. impl 属性设置为org.apache.hadoop.fs.RawLocalFileSystem进而对文件URI实现重新映射。还有一个可选方案,可以直接新建一个 RawLocalFileSystem实例,如果你想针对一些读操作禁用校验和,它是非常有用的。示例如下:
Configuration conf =... FileSystem fs = new RawLocalFileSystem(); fs.initialize(null,conf);
ChecksumFileSystem
LocalFileSystem通过ChecksumFileSystem来完成自己的任务,有了这个类,向其他文件系统(无校验和系统)加入校验和就非常简单,因为ChecksumFileSystem类继 承自FileSystem类。一般用法如下:
FileSystem rawFs =... FileSystem checksummedFs = new ChecksumFileSystem(rawFs);
底层文件系统称为“源”(raw)文件系统,可以使用ChecksumFileSystem实例的getRawFileSystem()方法获取它。ChecksumFileSystem类还有其他一些与校验 和有关的有用方法,比如getChecksumFile(),这个方法用于获得任意一个文件 的校验和文件路径。其他方法,请参考文档。
如果在读取文件时,ChecksumFileSystem类检测到错误,它会调用自己的 reportChecksumFailure()方法。默认实现为空方法,但 LocalFileSystem 类会将这个出错的文件及其校验和移到同一存储设备上一个名为bad_files的边际 文件夹(side directory)中。管理员应定期检查这些坏文件并采取相应的行动。