Apache Hadoop — 在一个 Pod 里搭起并运维 HDFS 与 YARN
文件被切成数据块,存放在 DataNode 磁盘上
一句话总结
HDFS 文件被切成大小相同的块,以普通文件的形式存放在 DataNode 磁盘上,每个块按副本系数生成相应数量的副本。块大小可以按文件指定,但一旦确定,块数、NameNode 的负担,甚至文件校验和的值都会随之改变。
为什么块这么大
本地文件系统的块通常只有几 KB。HDFS 的块由 hdfs-default.xml 中的 dfs.blocksize 决定,默认为 134217728 字节,也就是 128MB。相差数万倍是有原因的。
首先,HDFS 是为从头到尾读取大文件而设计的。设计文档指出,它更看重吞吐量而不是延迟。块大,意味着定位一次之后可以长时间顺序读取。其次,每个块都是 NameNode 内存中的一个对象。把 1TB 的文件切成 4KB 的块,块数会远远超过 2 亿个,NameNode 必须把它们的位置全部放在内存里。第三,块也是划分计算的单位。正如设计文档所说,移动计算比移动数据更便宜,所以 MapReduce 这类引擎会在块所在的位置运行任务。
但也不能无限缩小。dfs.namenode.fs-limits.min-block-size 默认为 1048576 字节(1MB),说明中写道,这是为了防止误把块大小设得极小而导致块数暴增所设的下限。
工作原理
块大小因文件而异。设计文档指出,块大小和副本系数都可以按文件指定。集群默认值只用于创建时没有另行指定的文件。此外,除最后一个块以外,所有块大小相同。因此,以 4MiB 的块大小上传 12MiB 的文件会得到 3 个块,以默认的 128MB 上传则只有 1 个块。1KB 的文件也会占用一个块。50 个小文件就是 50 个块——块不会在文件之间共用。
# 이 파일만 블록 4MiB 로 올린다
hdfs dfs -D dfs.blocksize=4194304 -put big.dat /data/big.dat
# 블록 목록과 위치를 본다
hdfs fsck /data/big.dat -files -blocks -locations
HDFS 命令文档中的 fsck 配合 -files -blocks -locations,会为每个文件显示块 ID 以及持有该块的 DataNode。在这里得到块 ID,就可以继续深入下一步。
磁盘上的块。DataNode 存放块的位置由 dfs.datanode.data.dir 指定,默认为 file://${hadoop.tmp.dir}/dfs/data(实验镜像把它改成了 /var/lib/hadoop/data)。顺着这个目录往下找,会看到以 blk_ 开头的普通文件,旁边有同名并带 .meta 后缀的配对文件。前者是块本身的字节,后者是这些字节的校验和。设计文档说明,DataNode 不会把文件堆在同一个目录里,而是自行拆分出子目录,因为本地文件系统处理不好单个目录中的海量文件。如果上传的是文本文件,用 head 打开 blk_ 文件就能原样读到原来的内容。这说明 HDFS 并没有使用特殊的存储格式。
复制。副本系数默认为 dfs.replication 的 3。对于副本系数为 3 的放置策略,设计文档是这样说明的:一个副本放在写入方所在的机器(或同一机架的任意节点),一个放在另一个机架的节点上,最后一个放在那个另一机架的另一个节点上。这样即使整个机架宕掉数据也能保留,同时减少机架之间的写入流量。
有一个重要的限制。NameNode 不会在同一个 DataNode 上放置同一个块的多个副本,所以能创建的副本数上限就是当时的 DataNode 数量。在只有一个 DataNode 的实验环境中,对文件系统 Shell执行 setrep 3,命令会成功,但副本永远只有一个。fsck 会把这样的块报告为副本不足(under-replicated)。命令成功并不代表复制已经完成。
为什么校验和会与块大小绑定
HDFS 会对每一小段字节做校验。dfs.bytes-per-checksum 默认为 512,dfs.checksum.type 默认为 CRC32C。每 512 字节计算一个 CRC,存放在 .meta 文件中。
问题出在 hadoop fs -checksum 返回的文件级校验和上。默认的组合方式 dfs.checksum.combine.mode 是 MD5MD5CRC。顾名思义,它先把每 512 字节的 CRC 按块用 MD5 合并,再把各块的 MD5 用 MD5 合并一次。块边界被带进了计算过程,所以即使内容相同,块大小不同,值也会不同。hdfs-default.xml 中的说明同样指出,原来的方式无法在块布局不同的文件之间比较,而 COMPOSITE_CRC 这类方式与块布局无关,可以比较。
hadoop fs -checksum /a/4m.dat /a/128m.dat # 값이 다르다
hadoop fs -D dfs.checksum.combine.mode=COMPOSITE_CRC \
-checksum /a/4m.dat /a/128m.dat # 값이 같다
这在现场酿成事故的地方就是复制校验。DistCp 文档指出,-update 会对比源端和目标端的大小、块大小和校验和。没有保留块大小就迁移过去的副本,在默认方式下校验和可能显得对不上。
在现场相遇的样子
第一,副本系数改成 3 之后 fsck 仍持续告警。DataNode 少于三个,或者没有地方满足机架布局时,就会出现这种情况。setrep 只是修改目标值,真正创建副本的,是 NameNode 之后指示 DataNode 去做的后台工作。
第二,缩小块大小,最先吃不消的是 NameNode。块数等于文件大小除以块大小,所以块大小减半,块对象就翻倍。这与小文件过多是同一方向的负担。
第三,复制后的文件校验和不同,并不代表数据损坏。可能只是块大小不同。把组合方式改为 COMPOSITE_CRC 再对比一次,就能分辨。
第四,不要手动改动 DataNode 磁盘上的 blk_ 文件。这些文件与 NameNode 的块映射是成对的。一旦移动或删除,NameNode 要等到块报告才会发现,在此期间读取会失败。
实际工作中真正重要的事
- 块大小和副本系数是每个文件自己的属性。集群默认值只用于没有指定的文件。
- 块的数量就是 NameNode 的负担。小文件和小块都会朝同一个方向增加对象。
- 副本数的上限是 DataNode 数量。即使
setrep成功,也要用fsck确认实际副本数。 - 块是 DataNode 磁盘上的普通文件。它们以
blk_与.meta成对的形式存放。 - 文件校验和的默认值与块布局绑定。块大小不同的副本之间,要用 COMPOSITE_CRC 来比较。
下一项实验要做什么
把 12MiB 的文件以 4MiB 的块大小上传,用 fsck 看到它被拆成三个块,再顺着块 ID 到 DataNode 的本地磁盘上找到并打开真正的块文件。同时对比:用默认块大小上传同一个文件,只会得到一个块。把副本系数提高到 3,看在只有一个 DataNode 的环境里副本不足是如何报告的,并数出多个小文件各自变成了多少个块。最后确认:只有块大小不同的两个文件,校验和在默认方式下不同,而在 COMPOSITE_CRC 下相同。