前哨站/第5章 Hadoop的I/O操作
第5章 Hadoop的I/O操作

第5章 Hadoop的I/O操作

数据在磁盘与网络之间怎么保证不坏、怎么变紧凑、怎么打包成合适的容器格式。

数据完整性序列化压缩

本章学习目标

  • 能说明 HDFS 数据完整性的实现方式并验证
  • 能说出序列化与反序列化的作用,会写 Writable 类
  • 能列出常用压缩格式的特点与适用场景
  • 能读写 SequenceFile 与 MapFile

本章导览

核心内容

5.1

数据完整性

HDFS 用"校验和"保证数据在传输与存储过程中不被损坏。

5.1.1

HDFS的数据完整性

  • 写入时按块计算 CRC32 校验和,与数据一起存到 DataNode
  • DataNode 启动与定期扫描时校验块,发现损坏上报 NameNode
  • 读取时客户端校验,不一致则换其它副本读取
  • DataNode 还会定期做数据块扫描(默认每 6 小时一次)
  • 校验和文件在 HDFS 中是隐藏的 .meta 文件
5.1.2

验证数据完整性

SHELL
# 查看块的校验信息
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
5.2

序列化与反序列化

序列化把内存对象变成可传输/可存储的字节流;反序列化反之。Hadoop 没有用 Java 原生序列化,而是自定义了 <b>Writable</b> 接口——更紧凑、更快。

5.2.1

序列化

Hadoop 只序列化"需要传输的数据",不带类结构信息,所以体积远小于 Java 原生序列化。

常用 Writable对应类型
IntWritableint
LongWritablelong
TextString(UTF-8)
DoubleWritable / FloatWritabledouble / float
BooleanWritableboolean
NullWritable占位,无数据
JAVA
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 省略
}
5.2.2

反序列化

自定义 Writable 必须满足三点:① 实现 Writable 接口;② 提供无参构造(反射创建对象);③ 字段顺序在 write 与 readFields 中必须完全一致,否则读到错位数据。
5.3

数据压缩

压缩能减少磁盘占用和网络传输,但会增加 CPU 开销。选型原则是"看瓶颈在哪"。

5.3.1

压缩与解压缩方法Codec

格式是否可切分特点适用
DEFLATE否压缩率与速度均衡通用
Gzip否压缩率高,速度中等冷数据归档
Bzip2是压缩率最高,速度最慢极致省空间
LZO是(需建索引)速度快,压缩率中等大文件并行处理
Snappy否速度最快,压缩率较低热数据、中间结果

💡 是否可切分决定了压缩文件能不能被多个 Map 并行读取——大文件一定要选可切分格式,否则整个文件只能由一个 Map 处理。

5.3.2

压缩与输入分片

SHELL
// 代码中指定压缩
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>
5.4

Hadoop文件的数据结构

HDFS 上的文件本身是字节流,Hadoop 提供两种带结构的容器格式,用于存放"多个小文件/键值对"。

5.4.1

SequenceFile

  • 以 key-value 形式顺序存储的二进制文件
  • 支持三种格式:不压缩、记录压缩、块压缩
  • 适合作为 MapReduce 的输入输出,避免大量小文件
  • 不支持随机读,只能顺序遍历
JAVA
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();
5.4.2

MapFile

  • MapFile = 索引文件(index)+ 数据文件(data)
  • 要求 key 有序,因此支持按 key 随机查找
  • 本质是对 SequenceFile 加了索引
  • 写入时必须已按 key 排序
一句话区分:SequenceFile 顺序读,MapFile 可按 key 定位(内部仍是 SequenceFile + 索引)。

本章小结

本章讲清了三件事:数据完整性(CRC 校验和 + fsck 检查)、序列化(Hadoop 用 Writable 而非 Java 原生序列化,紧凑且快)、压缩(五种常用格式的取舍与是否可切分),最后介绍 SequenceFile 与 MapFile 两种容器格式的用途与区别。

思考与练习 先自己做,再点开答案

01

Hadoop 为什么不用 Java 原生序列化?

查看参考答案

Java 原生序列化体积大(带类结构与元信息)、速度慢、且不支持随机访问。Hadoop 的 Writable 只序列化字段值,更紧凑、性能更好,并可与 Hadoop 的数据类型体系配合。

02

Gzip 压缩的文件能被多个 Map 并行处理吗?

查看参考答案

不能。Gzip 不可切分,一个 Gzip 文件只能由单个 Map 处理,大文件会成为性能瓶颈。需要并行处理应选 Bzip2 或带索引的 LZO。

03

SequenceFile 与 MapFile 的区别?

查看参考答案

MapFile 是在 SequenceFile 基础上增加了索引文件(index),要求 key 有序,从而支持按键随机查找;SequenceFile 只能顺序遍历。