第5章 Hadoop的I/O操作
数据在磁盘与网络之间怎么保证不坏、怎么变紧凑、怎么打包成合适的容器格式。
本章学习目标
- 能说明 HDFS 数据完整性的实现方式并验证
- 能说出序列化与反序列化的作用,会写 Writable 类
- 能列出常用压缩格式的特点与适用场景
- 能读写 SequenceFile 与 MapFile
本章导览
核心内容
数据完整性
HDFS 用"校验和"保证数据在传输与存储过程中不被损坏。
HDFS的数据完整性
- 写入时按块计算 CRC32 校验和,与数据一起存到 DataNode
- DataNode 启动与定期扫描时校验块,发现损坏上报 NameNode
- 读取时客户端校验,不一致则换其它副本读取
- DataNode 还会定期做数据块扫描(默认每 6 小时一次)
- 校验和文件在 HDFS 中是隐藏的 .meta 文件
验证数据完整性
# 查看块的校验信息
hdfs fsck /data/input/w.txt -files -blocks -locations
# 损坏时的典型输出:CORRUPT block
hdfs fsck / -list-corruptfileblocks
# 修复:删除损坏副本后由 NN 自动补齐
hdfs debug recoverLease -path /data/input/w.txt -retries 3序列化与反序列化
序列化把内存对象变成可传输/可存储的字节流;反序列化反之。Hadoop 没有用 Java 原生序列化,而是自定义了 <b>Writable</b> 接口——更紧凑、更快。
序列化
Hadoop 只序列化"需要传输的数据",不带类结构信息,所以体积远小于 Java 原生序列化。
| 常用 Writable | 对应类型 |
|---|---|
| IntWritable | int |
| LongWritable | long |
| Text | String(UTF-8) |
| DoubleWritable / FloatWritable | double / float |
| BooleanWritable | boolean |
| NullWritable | 占位,无数据 |
public class FlowBean implements Writable {
private long up; // 上行流量
private long down; // 下行流量
@Override
public void write(DataOutput out) throws IOException {
out.writeLong(up);
out.writeLong(down);
}
@Override
public void readFields(DataInput in) throws IOException {
this.up = in.readLong();
this.down = in.readLong();
}
// getter / setter / toString 省略
}反序列化
数据压缩
压缩能减少磁盘占用和网络传输,但会增加 CPU 开销。选型原则是"看瓶颈在哪"。
压缩与解压缩方法Codec
| 格式 | 是否可切分 | 特点 | 适用 |
|---|---|---|---|
| DEFLATE | 否 | 压缩率与速度均衡 | 通用 |
| Gzip | 否 | 压缩率高,速度中等 | 冷数据归档 |
| Bzip2 | 是 | 压缩率最高,速度最慢 | 极致省空间 |
| LZO | 是(需建索引) | 速度快,压缩率中等 | 大文件并行处理 |
| Snappy | 否 | 速度最快,压缩率较低 | 热数据、中间结果 |
💡 是否可切分决定了压缩文件能不能被多个 Map 并行读取——大文件一定要选可切分格式,否则整个文件只能由一个 Map 处理。
压缩与输入分片
// 代码中指定压缩
FileOutputFormat.setCompressOutput(job, true);
FileOutputFormat.setOutputCompressorClass(job, GzipCodec.class);
// 或通过配置
<property><name>mapreduce.map.output.compress</name><value>true</value></property>
<property><name>mapreduce.map.output.compress.codec</name>
<value>org.apache.hadoop.io.compress.SnappyCodec</value></property>Hadoop文件的数据结构
HDFS 上的文件本身是字节流,Hadoop 提供两种带结构的容器格式,用于存放"多个小文件/键值对"。
SequenceFile
- 以 key-value 形式顺序存储的二进制文件
- 支持三种格式:不压缩、记录压缩、块压缩
- 适合作为 MapReduce 的输入输出,避免大量小文件
- 不支持随机读,只能顺序遍历
SequenceFile.Writer writer = SequenceFile.createWriter(conf,
SequenceFile.Writer.file(new Path("/seq/out.seq")),
SequenceFile.Writer.keyClass(Text.class),
SequenceFile.Writer.valueClass(IntWritable.class));
writer.append(new Text("hello"), new IntWritable(1));
writer.close();MapFile
- MapFile = 索引文件(index)+ 数据文件(data)
- 要求 key 有序,因此支持按 key 随机查找
- 本质是对 SequenceFile 加了索引
- 写入时必须已按 key 排序
本章小结
本章讲清了三件事:数据完整性(CRC 校验和 + fsck 检查)、序列化(Hadoop 用 Writable 而非 Java 原生序列化,紧凑且快)、压缩(五种常用格式的取舍与是否可切分),最后介绍 SequenceFile 与 MapFile 两种容器格式的用途与区别。
思考与练习 先自己做,再点开答案
Hadoop 为什么不用 Java 原生序列化?
查看参考答案
Java 原生序列化体积大(带类结构与元信息)、速度慢、且不支持随机访问。Hadoop 的 Writable 只序列化字段值,更紧凑、性能更好,并可与 Hadoop 的数据类型体系配合。
Gzip 压缩的文件能被多个 Map 并行处理吗?
查看参考答案
不能。Gzip 不可切分,一个 Gzip 文件只能由单个 Map 处理,大文件会成为性能瓶颈。需要并行处理应选 Bzip2 或带索引的 LZO。
SequenceFile 与 MapFile 的区别?
查看参考答案
MapFile 是在 SequenceFile 基础上增加了索引文件(index),要求 key 有序,从而支持按键随机查找;SequenceFile 只能顺序遍历。