1. Hadoop文件系统的数据完整性

1.1 关于checksum

  • 学习计算机组成原理什么的,一般都会提到数据校验以保证数据的完整性

检测数据是否损坏的常见操作:

  1. 数据第一次写入系统时,对数据计算校验和(checksum),数据和checksum一起存入系统
  2. 之后读取数据时,重新计算数据的checksum,将计算出来的checksum与系统存储的checksum做比较
  3. 如果checksum不一致,则认为数据已损坏
  • 计算checksum只能检查数据是否损坏,并不能修复数据
  • 目前,常用的错误校验码是CRC-32,即32位的循环冗余校验:无论输入多大的数据,最终将产生4 byte的checksum(32位的整数
  • Hadoop中有基础的校验和FileSystem类ChecksumFileSystem,它可以通过装饰其他无checksum的FileSystem对象,使其支持checksum。
  • ChecksumFileSystem使用的就是CRC-32计算checksum
  • HDFS文件系统使用的是CRC-32的变体 —— CRC-32C,计算更为高效

1.2 HDFS的数据完整性

  • HDFS对所有写入的数据计算checksum,并在读取数据时验证checksum
  • 计算checksum的数据大小由dfs.bytes-per-checksum指定,默认为512 byte,产生的checksum为4 byte

HDFS验证checksum的三种场景:

  • client向DataNode写入数据或DataNode从其他DataNode复制数据时,会校验数据的checksum。校验完成后,才会存储数据及其checksum
  • client写入数据的具体校验步骤:
    ① client负责计算数据的checksum,然后将数据及其checksum发送到由DataNode组成的pipeline中
    ② pipeline中的最后一个DataNode负责校验数据的checksum,校验失败向client返回IOException的一个子类
  • client从DataNode读取数据时,会校验数据的checksum
  • DataNode中永久保存一个checksum日志,存储block上一次的校验时间
  • 当client完成block的校验后,会告知DataNode,DataNode由此更新checksum日志
  • 在DataNode中保存checksum日志,有利于检测磁盘的损坏情况
  • DataNode中运行DataBlockScanner的后台线程,用于定期校验存储在该DataNode上的所有block
  • 这是解决物理存储介质bit rot损坏的有力措施

HDFS对损坏block的处理

  1. client读取block时,检测到block损坏,首先向NameNode上报损坏的block,再抛出ChecksumException异常
  2. NameNode会标记该block为不可用,后续的client读请求将不会发送至block,也不会将该block复制到其他DataNode
  3. 为了保证满足副本数,NameNode会将其他完好的副本复制到新的DataNode —— 在另一个DataNode新建副本
  4. 删除损坏的block

checksum相关的一些操作

  1. 通过open()打开一个文件前,调用setVerifysum()为false,关闭checksum
  2. -get操作使用-ignoreCrc禁用checksum
  3. -copyToLocal也可以禁用checksum
  4. hadoop fs -checksum计算文件的校验和,还可以用于判断两个文件的内容是否相同

1.3 LocalFileSystem的数据完整性

  • LocalFileSystem中,写入一个名为filename的文件,则会在相同目录创建一个名为.filename.crc的隐藏文件,用于存储数据的checksum
  • 当读取数据时, LocalFileSystem会校验checksum,如果校验失败会抛出ChecksumException

如何禁用禁用 LocalFileSystem的checksum?

  1. 尤其是在底层文件系统支持checksum时,可以禁用LocalFileSystem,这时可以使用RawLocalFileSystem
  2. 或者将fs.file.impl设置为org.apache.hadoop.fs.RawLocalFileSystem

1.4 ChecksumFileSystem

  • 如果文件系统不支持checksum,可以使用 ChecksumFileSystem对其进行装饰,使原文件系统支持checksum
  • 重要方法:reportChecksumFailure()默认实现是一个空方法。 ChecksumFileSystem重写了该方法,当校验出错时,会将文件和checksum移动到一个名为bad_files的边际文件夹

2. 压缩

  • 向他人分享本机文件、将本地文件拷贝到U盘等,我们为了减少数据的size、加快传输速度,总喜欢将文件打包
  • 其实,文件打包就是压缩,查看打包好的文件是,还需要进行解压缩
  • 文件压缩的两大好处:
    ① 减少文件存储所需的磁盘空间
    ② 加快数据在网络和磁盘上的传输
  • 一般,我们说的压缩是动词,与解压缩对应;但压缩算法是名词,包括压缩和解压缩的实现

2.1 与Hadoop结合的常见压缩格式

  • 压缩格式、压缩算法、压缩工具等层出不穷,有的压缩算法还有对应的native库,可以极大的提高压缩或解压缩的效率

压缩格式

压缩算法

压缩工具

文件扩展名

是否支持split

是否有Java实现

是否有native库

DEFLATE

DEFLATE


.deflate




gzip

DEFLATE

gzip

.gz




bzip2

bzip2

bzip2

.bz2




LZO

LZO

lzop

.lzo




LZ4

LZ4


.lz4




snappy

snappy


.snappy




  • 关于gzip:使用的算法仍然是DEFLATE,只是在DEFALTE文件格式上增加了一个文件头和文件尾
  • 关于slipt:
    ① 压缩中的split是指可以搜索数据流的任意位置并进一步往下读取数据
    ② 只有bzip2支持split
    ③ LZO可以通过split索引来实现split

关于native库:

  1. native库:就是基于特定操作系统实现的代码类库,例如基于Linux的c语言native库
  2. native库总是比使用其他语言实现的相同代码,具有更高的执行效率
  3. Hadoop的二进制安装包中,有为64位linux构建好的native库文件: libhadoop.so
  4. 可以通过java系统属性java.library.path指定native库的路径

2.2 CompressionCodec

  • codec是压缩 - 解压缩算法的一种实现,一般xxxCodec表示某种xxx压缩算法的实现
  • Hadoop中Codec有一个统一的接口CompressionCode,具体的压缩算法都是基于该接口进行实现的

压缩格式

Codec类

DEFLATE

DefaultCodec

gzip

GzipCodec

bzip2

BZip2Codec

LZO

LzopCodec

LZ4

Lz4Codec

snappy

SnappyCodec

CompressionCodec的两大方法

  1. createOutputStream(OutputStream out):对写入输出数据流(out)的数据进行压缩,即在底层数据流中对需要以压缩格式写入、在此之前没有压缩的数据进行压缩后再写入
  2. createInputStream(InputStream in):对从输入数据流(in)读取的数据进行解压缩

2.3 CompressionCodecFactory

  • 在读取Hadoop压缩文件时,需要为不同的压缩格式,设置不同的Codec
  • 为了针对不同的压缩文件,智能地创建对应的CompressCodec,可以使用CompressCodecFactory去获取压缩文件的codec
  • CompressionCodecFactory中的getCodec()方法,可以压缩文件的扩展名,自动推断CompressionCodec
  • 例如,.gz为扩展名的压缩文件,将获取到GzipCodec

2.4 CodecPool

  • 大量使用压缩/解压缩算法时,避免频繁创建codec实例,使用codecPool实现codec的重复使用
  • 两个重要方法:getCompressor()returnCompressor()

2.5 MR中的压缩

2.5.1 关于split

  • 考虑这样一个场景,有一个大小为1 GB.gz文件,按照HDFS的block配置,应该分为8个block存储
  • 如果MR任务的输入为该文件,按理说应该会创建8个split,对应8个map任务
  • 但是,.gz文件不支持split,不能单独读取某个block,必须由一个map任务处理8个block
  • 这样会限制数据的本地化,使得map任务的执行时间会大大增加
  • 但是,如果文件是.bzip2,或能基于split点构建索引的.lzo,则可以为每个block创建map的slit,从而分配8个map任务处理这个文件

压缩格式的选择

  1. 使用容器文件格式:容器文件格式,如avro、ORC、parquet等,同时支持压缩和split,不要求压缩算法支持split。这时,可以考虑使用性能较好的压缩工具,如LZO、LZ4、snappy
  2. 使用支持split的压缩算法:bzip2、基于split点构建索引从而支持split的LZO
  3. 先在应用中将文件split成块,再使用压缩算法为每一个块创建压缩文件
  4. 存储未经压缩的文件
  5. 注意: 针对大文件,最好使用支持split的压缩算法
2.5.2 设置MR的压缩算法
  • MR作业包含map任务和reduce任务,reduce任务的输出会存储到HDFS上
  • 将MR的输出进行压缩,也就是将reduce任务的输出进行压缩
  • 可以在MR任务的主程序中进行如下设置,以GzipCodec为例:
FileOuputFormat.setCompressOutput(job, true);
FileOuputFormat.setCompressorClass(job, GzipCodec.class);
  • 如果输出为顺序文件,可以设置mapreduce.output.fileoutputformat.compress.type 属性,来控制是对每条记录(RECORD)还是对一组记录(BLOCK)进行压缩
  • 通过代码设置压缩,同样可以通过属性来实现
    |属性 | 类型 | 默认值 | 描述 |
    |–|–|–|–|–|
    | mapreduce.output.fileoutputformat.compress | bolean | false | 是否压缩输出|
    |mapreduce.output.fileoutputformat.compress.codec | Codec的类名 | DefaultCodec的全名 | 输出使用的压缩算法|
    | mapreduce.output.fileoutputformat.compress.type | String | RECORD | 顺序文件可以使用的压缩方式:NONE、RECORD、BLOCK |
2.5.3 设置map任务输出的压缩算法
  • Hadoop不仅支持对reduce任务的输出进行压缩,还支持对map任务的输出进行压缩
  • 这样可以减少从map节点到reduce节点传输数据大小,提高MR任务的效率
  • Java代码进行设置:
Configuration conf = new Configuration();
conf.setBoolean(Job.MAP_OUTPUT_COMPRESS, true);
conf.setClass(Job.MAP_OUTPUT_COMPRESS_CODEC, GzipCodec.class);
  • 通过属性设置:
    |属性 | 类型 | 默认值 | 描述 |
    |–|–|–|–|–|
    | mapreduce.map.ouput.compress | bolean | false | 是否将map任务的输出进行压缩|
    |mapreduce.map.output.compress.codec | Codec的类名 | DefaultCodec的全名 | 输出使用的压缩算法|

3. 总结

3.1 checksum

  • checksum的常见措施:第一次写入前计算checksum,存储数据和checksum;后续读取时,计算数据的checksum,并与原始的checksum做对比
  • Hadoop中的checksum:
  1. HDFS使用CRC-32的变体CRC-32C,该变体会更高效
  2. Hadoop中,ChecksumFileSystem类使用CRC-32,可以对不支持checksum的文件系统进行装饰,使其支持checksum
3.1.1 HDFS的checksum
  • 基于packet计算checksum,将packet和checksum一起写入HDFS
  • 通过dfs.bytes-per-checksum设置计算checksum的数据大小
  • 三种校验checksum的场景:
  1. client将数据写入DataNode:client负责划分packet并计算checksum,pipeline中的最后一个DataNode负责校验,校验出错返回IOException的子类;或DataNode从其他DataNode复制block
  2. client从DataNode读取数据:DataNode永久存储checksum日志文件,记录block最近一次的校验时间;client完成checksum校验后,会报告给DataNode,从而更新checksum日志文件;可以检查磁盘损坏
  3. DataNode的后台线程DataBlockScanner,定期扫描DataNode上的所有block,是预防物理存储介质bit rot的有力措施
  • HDFS对数据块损坏的操作:
  1. 报告给NameNode,再返回ChecksumException异常;
  2. NameNode会标记block,并不在将client的读请求发送到该block对应的DataNode;
  3. NameNode发现副本数不够,在其他DataNode新建block的副本;
  4. 删除DataNode中已损坏的block
3.1.2 LocalFileSystem
  • 文件名为 file,对应的checksum元数据:同一目录的.file.crc
  • 当文件系统本身就支持checksum是,可以使用RawLocalFileSystem代替LocalFileSystem

3.2 压缩

  • 常见的压缩格式,及对应的压缩算法、文件扩展名、是否支持split、是否有java实现、是否有native实现等
  • 特殊的:
  1. gzip使用DEFALTE算法,文件在DEFLATE文件基础上增加了文件头和文件尾
  2. bzip2唯一支持split的压缩算法,且无native实现
  3. LZO代码库使用GPL,使用时需要单独配置
  4. Hadoop安装包,64为linux创建了native类库: libhadoop.so
  • 压缩算法的实现:CompressionCodec接口及具体的codec实现、creteOutputStream(OutputStream out)createInputStream(InputStream in)
  • CompressionCodecFactory,根据压缩文件的扩展名,获取对应的Codec实例
  • CodecPool:避免频繁创建Codec,实现codec的重复使用
  • HDFS中压缩格式的选择:
  1. 使用容器文件:avro、ORC、parquet等,它们同时支持压缩和split
  2. 使用支持split的算法:bzip2;通过为split点创建索引,从而支持split的LZO
  3. 文件先分块,为每个分块创建压缩文件
  4. 存储未经压缩的文件
  • MR中压缩的配置:
  1. 为reduce的输出设置压缩:mapreduce.output.fileoutputformat.compressmapreduce.output.fileoutputformat.compress.codecmapreduce.output.fileoutputformat.compress.type
  2. 为map任务的输出设置压缩:mapreduce.map.output..compressmapreduce.map.output.compress.codec