1. Hadoop文件系统的数据完整性
1.1 关于checksum
- 学习计算机组成原理什么的,一般都会提到数据校验以保证数据的完整性
检测数据是否损坏的常见操作:
- 数据第一次写入系统时,对数据计算校验和(
checksum
),数据和checksum一起存入系统 - 之后读取数据时,重新计算数据的checksum,将计算出来的checksum与系统存储的checksum做比较
- 如果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的处理
- client读取block时,检测到block损坏,首先向NameNode上报损坏的block,再抛出
ChecksumException
异常 - NameNode会标记该block为不可用,后续的client读请求将不会发送至block,也不会将该block复制到其他DataNode
- 为了保证满足副本数,NameNode会将其他完好的副本复制到新的DataNode —— 在另一个DataNode新建副本
- 删除损坏的block
checksum相关的一些操作
- 通过
open()
打开一个文件前,调用setVerifysum()
为false,关闭checksum -
-get
操作使用-ignoreCrc
禁用checksum -
-copyToLocal
也可以禁用checksum -
hadoop fs -checksum
计算文件的校验和,还可以用于判断两个文件的内容是否相同
1.3 LocalFileSystem的数据完整性
-
LocalFileSystem
中,写入一个名为filename
的文件,则会在相同目录创建一个名为.filename.crc
的隐藏文件,用于存储数据的checksum - 当读取数据时,
LocalFileSystem
会校验checksum,如果校验失败会抛出ChecksumException
如何禁用禁用 LocalFileSystem
的checksum?
- 尤其是在底层文件系统支持checksum时,可以禁用
LocalFileSystem
,这时可以使用RawLocalFileSystem
- 或者将
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库:
- native库:就是基于特定操作系统实现的代码类库,例如基于Linux的c语言native库
- native库总是比使用其他语言实现的相同代码,具有更高的执行效率
- Hadoop的二进制安装包中,有为64位linux构建好的native库文件:
libhadoop.so
- 可以通过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
的两大方法
-
createOutputStream(OutputStream out)
:对写入输出数据流(out
)的数据进行压缩,即在底层数据流中对需要以压缩格式写入、在此之前没有压缩的数据进行压缩后再写入 -
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任务处理这个文件
压缩格式的选择
- 使用容器文件格式:容器文件格式,如avro、ORC、parquet等,同时支持压缩和split,不要求压缩算法支持split。这时,可以考虑使用性能较好的压缩工具,如LZO、LZ4、snappy
- 使用支持split的压缩算法:bzip2、基于split点构建索引从而支持split的LZO
- 先在应用中将文件split成块,再使用压缩算法为每一个块创建压缩文件
- 存储未经压缩的文件
- 注意: 针对大文件,最好使用支持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:
- HDFS使用
CRC-32
的变体CRC-32C
,该变体会更高效 - Hadoop中,
ChecksumFileSystem
类使用CRC-32,可以对不支持checksum的文件系统进行装饰,使其支持checksum
3.1.1 HDFS的checksum
- 基于packet计算checksum,将packet和checksum一起写入HDFS
- 通过
dfs.bytes-per-checksum
设置计算checksum的数据大小 - 三种校验checksum的场景:
- client将数据写入DataNode:client负责划分packet并计算checksum,pipeline中的最后一个DataNode负责校验,校验出错返回
IOException
的子类;或DataNode从其他DataNode复制block - client从DataNode读取数据:DataNode永久存储checksum日志文件,记录block最近一次的校验时间;client完成checksum校验后,会报告给DataNode,从而更新checksum日志文件;可以检查磁盘损坏
- DataNode的后台线程DataBlockScanner,定期扫描DataNode上的所有block,是预防物理存储介质
bit rot
的有力措施
- HDFS对数据块损坏的操作:
- 报告给NameNode,再返回
ChecksumException
异常; - NameNode会标记block,并不在将client的读请求发送到该block对应的DataNode;
- NameNode发现副本数不够,在其他DataNode新建block的副本;
- 删除DataNode中已损坏的block
3.1.2 LocalFileSystem
- 文件名为
file
,对应的checksum元数据:同一目录的.file.crc
- 当文件系统本身就支持checksum是,可以使用
RawLocalFileSystem
代替LocalFileSystem
3.2 压缩
- 常见的压缩格式,及对应的压缩算法、文件扩展名、是否支持split、是否有java实现、是否有native实现等
- 特殊的:
- gzip使用DEFALTE算法,文件在DEFLATE文件基础上增加了文件头和文件尾
- bzip2唯一支持split的压缩算法,且无native实现
- LZO代码库使用GPL,使用时需要单独配置
- Hadoop安装包,64为linux创建了native类库:
libhadoop.so
- 压缩算法的实现:
CompressionCodec
接口及具体的codec实现、creteOutputStream(OutputStream out)
和createInputStream(InputStream in)
-
CompressionCodecFactory
,根据压缩文件的扩展名,获取对应的Codec实例 -
CodecPool
:避免频繁创建Codec,实现codec的重复使用 - HDFS中压缩格式的选择:
- 使用容器文件:avro、ORC、parquet等,它们同时支持压缩和split
- 使用支持split的算法:bzip2;通过为split点创建索引,从而支持split的LZO
- 文件先分块,为每个分块创建压缩文件
- 存储未经压缩的文件
- MR中压缩的配置:
- 为reduce的输出设置压缩:
mapreduce.output.fileoutputformat.compress
及mapreduce.output.fileoutputformat.compress.codec
、mapreduce.output.fileoutputformat.compress.type
- 为map任务的输出设置压缩:
mapreduce.map.output..compress
及mapreduce.map.output.compress.codec