TT Lab
开始
学习 学习路径 课程

Apache Hadoop — 在一个 Pod 里搭起并运维 HDFS 与 YARN

把 2,000 个小文件打包成归档,并测量 NameNode 对象数

在 TT Lab 中继续学习

目标

把 2,000 个传感器 CSV(每个文件只有几百字节)上传到 HDFS,测出 NameNode 必须持有的对象(文件、目录、块)有多少个,再用 Hadoop Archive(HAR)打包,看这个数量减少了多少。打包之后,还要体验通过 har:// 仍然能逐个读取原来的文件,以及用 distcp 整体迁移归档。

为什么重要

NameNode 把每个文件、目录和块都当作 Java 对象放在堆里。无论文件是 1 字节还是 1GB,对象数量都一样。所以 HDFS 的极限大多不在磁盘,而是在 NameNode 的堆,几百万个小文件几乎不占磁盘,却会最先压垮 NameNode。从 MapReduce 和 Spark 的角度看,小文件也会因为每个文件都要附带任务或打开的开销而变慢。 解决办法是少生成文件——写入时就合并后再写(Spark 的 repartition、coalesce,大的容器文件格式),或者把已经产生的文件打包。HAR 属于后一种办法。它把许多文件变成几个大文件(part-*)和索引(_index、_masterindex),以减少 NameNode 的对象数量,读取时由 har:// 文件系统查看索引,按原来的路径找到文件。代价是,归档一旦创建就无法修改(只读)。 这个实验把归档和 distcp 用本地执行器运行,不使用 YARN。两者都是 MapReduce 作业,但运行位置用一项配置就能改变——同一个作业,在笔记本上和在集群上都能运行。

步骤

  1. 把 /data/small 目录(2,000 个文件)上传到 HDFS 的 /user/root/small/raw(用 hdfs dfs -put -t 8 以多个线程上传会更快)。
  2. 把 raw 的文件数、目录数和块数,按 {"raw_files": 정수, "raw_dirs": 정수, "raw_blocks": 정수} 的格式写入 /root/hdp/small/objects.json(占位符均为整数)。
  3. 用 hadoop archive -D mapreduce.framework.name=local -archiveName raw.har -p /user/root/small -r 1 raw /user/root/small/archive 创建归档。
  4. 用 hdfs dfs -ls -R har:///user/root/small/archive/raw.har 统计归档内文件的数量,以整数写入 /root/hdp/small/har_files.txt。
  5. 通过 har:// 路径读取 raw/sensor-0042.csv,保存到本地的 /root/hdp/small/sensor-0042.csv。
  6. 把 raw 和 raw.har 的 NameNode 对象数(文件 + 目录 + 块),按 {"raw_objects": 정수, "har_objects": 정수} 的格式写入 /root/hdp/small/compare.json(占位符均为整数)。
  7. 用 hadoop distcp -D mapreduce.framework.name=local /user/root/small/archive /user/root/backup/small-archive 复制归档。
  8. 在 /root/hdp/small/report.md 中写出 ## NameNode 가 치르는 값、## HAR、## 옮기기 三个小节。第一节写入第 6 步的 raw_objects,第二节写入 har_objects。

参考

上传 2,000 个小文件

把 /data/small 上传到 HDFS 的 /user/root/small/raw。像 hdfs dfs -put -t 8 /data/small /user/root/small/raw 这样用多个线程上传会更快。

单线程上传时,每个文件都要与 NameNode 往返好几次,2,000 个文件要花 10 多分钟。小文件付出成本,凭的不是大小,而是个数——这本身就是小文件问题。

统计 NameNode 持有的对象

用 hdfs dfs -count 和 hdfs fsck 统计 /user/root/small/raw 的文件数、目录数和块数,按 {"raw_files": 정수, "raw_dirs": 정수, "raw_blocks": 정수} 的格式写入 /root/hdp/small/objects.json(占位符均为整数)。

一个文件一个块(因为文件比块小),再加上一个目录。在 NameNode 的堆里,这三样都是对象。块大小虽然是 128MB,但只有几百字节的文件的块并不会占用 128MB,而对象却同样占用一个。

打包成归档

用 hadoop archive -D mapreduce.framework.name=local -archiveName raw.har -p /user/root/small -r 1 raw /user/root/small/archive 创建 /user/root/small/archive/raw.har。

归档是一个 MapReduce 作业。它把输入文件列表分开,把内容拼接到 part-* 里,再把每个文件放在什么位置记到 _index 中。-D mapreduce.framework.name=local 的意思是不使用 YARN,在当前 JVM 里运行。原始文件(raw)保持原样。

通过 har:// 查看内部

统计 hdfs dfs -ls -R har:///user/root/small/archive/raw.har 输出中文件(权限以 - 开头的行)的数量,以整数写入 /root/hdp/small/har_files.txt。

har:// 文件系统读取 _index,呈现出原来的目录树。NameNode 并不知道这 2,000 个文件——归档内的文件不是 NameNode 的对象,而是索引中的一行。

读取归档内的一个文件

把用 hdfs dfs -cat har:///user/root/small/archive/raw.har/raw/sensor-0042.csv 读取到的内容保存到本地的 /root/hdp/small/sensor-0042.csv。

路径是 har://<아카이브 경로>/<-p 기준 상대 경로>(占位符依次为归档路径与以 -p 指定的父目录为基准的相对路径)。har 文件系统通过 _masterindex 和 _index 找到 part 文件中的位置,只读取那部分字节。请用 cmp 与原始文件 /data/small/sensor-0042.csv 对比。

对比对象数量

分别求出 raw 和 raw.har 的 NameNode 对象数(文件数 + 目录数 + 块数),按 {"raw_objects": 정수, "har_objects": 정수} 的格式写入 /root/hdp/small/compare.json(占位符均为整数)。

raw 是 2,000 个文件 + 1 个目录 + 2,000 个块。raw.har 只有一个目录、几个文件(_index、_masterindex、part-0、_SUCCESS)以及它们的块。请算一算缩减到了几分之一。

用 distcp 整体迁移

用 hadoop distcp -D mapreduce.framework.name=local /user/root/small/archive /user/root/backup/small-archive 复制归档。复制品的 raw.har 中的各个文件必须与原件长度相同。

distcp 是把文件列表拆开、由多个 Map 并行复制的 MapReduce 作业。它是集群之间迁移和备份的标准工具,把小文件打包之后,要复制的文件数减少,复制也会变快。如果目标路径不存在,源目录的内容会以那个名字被复制过去。

用数字记录小文件的代价

在 /root/hdp/small/report.md 中写出 ## NameNode 가 치르는 값、## HAR、## 옮기기 三个小节。第一节以数字写入第 6 步的 raw_objects,第二节以数字写入 har_objects。

请写出:NameNode 的堆统计的是什么;HAR 缩减了什么、放弃了什么(只读);要让小文件从一开始就不产生,写入一侧该做什么。