2.2 写入路径:分块与副本放置 本节摘要:HDFS 写入路径由五步构成:路线申请、管线建立、流式写入、ACK 确认、提交。本节逐跳跟踪一次写入,重点拆解机架感知副本放置策略——三个副本各放在哪、为什么这样放,以及写入途中节点故障时数据流如何重新组织。 一个字节的五步旅程 回到我们的主角:300 MB 日志的第 100 万个字节。它随文件流到达客户端后,经历如下五步。 第一步,路线申请。 客户端调用 create,NameNode 检查路径与权限,在账本登记"文件创建中",然后为第一个块挑选接收节点列表(假设 dn1、dn2、dn4),返回客户端。注意:此刻没有任何数据落盘,只是领到了路线。 第二步,建立写入管线。
本节摘要:HDFS 写入路径由五步构成:路线申请、管线建立、流式写入、ACK 确认、提交。本节逐跳跟踪一次写入,重点拆解机架感知副本放置策略——三个副本各放在哪、为什么这样放,以及写入途中节点故障时数据流如何重新组织。
回到我们的主角:300 MB 日志的第 100 万个字节。它随文件流到达客户端后,经历如下五步。
第一步,路线申请。 客户端调用 create,NameNode 检查路径与权限,在账本登记"文件创建中",然后为第一个块挑选接收节点列表(假设 dn1、dn2、dn4),返回客户端。注意:此刻没有任何数据落盘,只是领到了路线。
第二步,建立写入管线。 客户端与节点列表建立流水线:先连 dn1,dn1 再连 dn2,dn2 连 dn4。三个节点串成一串,就像工厂流水线,不是客户端分别给三家各发一份。
第三步,流式写入与校验。 客户端把数据装进 64 KB 的数据包(Packet),逐包发往管线。字节到 dn1 后并不直接躺平——dn1 边写本地磁盘边把包转发给 dn2,dn2 同样边写边转发 dn4。每 512 字节数据配 4 字节循环冗余校验(CRC)组成校验块单元,落盘时一并写入,供日后读取时验证数据完整性。
第四步,ACK 回传。 dn4 写满一个包后向 dn2 回 ACK,dn2 收到自己的 ACK 与下游 ACK 后回传 dn1,dn1 汇总后回客户端。客户端确认"这个包三副本全部落盘",才发送下一个包。写满一个块(或文件到尾),客户端向 NameNode 报告块完成。
第五步,提交。 所有块写完,客户端调用 close,NameNode 等待最小副本数(默认 1,即至少一个 DataNode 确认)达标后,把文件从"创建中"转为"已提交"。此后文件对读者可见。
这里有个值得咀嚼的细节:写三副本占用的客户端上行带宽只有一份。因为复制发生在管线内部(dn1→dn2、dn2→dn4),客户端只发一份数据。这是管线设计相对"客户端三发"的核心优势。
NameNode 挑选 dn1、dn2、dn4 不是随机的。生产集群的物理拓扑是:集群 → 机架 → 节点。机架内节点间带宽高(通常万兆内互联),机架间带宽相对稀缺(上联交换机是共享出口)。默认的副本放置策略(Hadoop 2.3 之后)如下:

算一笔带宽账:若三副本放在三个不同机架,跨机架复制要发生两次,且第三副本远离热数据;若三副本全在机架内,跨机架带宽最省,但整架故障即丢块。现行策略是折中的最优解——跨机架链路只占用一次(dn1→dn2),第二跳复制(dn2→dn4)在机架 B 内部完成;同时任何单一故障(无论节点级还是机架级)都至少保住两副本。
NameNode 怎么知道谁在哪个机架?答案是外部脚本或类映射:配置 net.topology.script.file.name 指向一个脚本,输入 DataNode IP,输出机架路径(如 /rack-a)。管理员按机房布线维护这个映射。若不配置,Hadoop 视所有节点为同一默认机架——副本全堆在一起,容错形同虚设。生产集群上线检查清单里这一项永远排前列。
<property> <name>net.topology.script.file.name</name> <value>/etc/hadoop/rack-topology.sh</value> </property> <property> <name>net.topology.node.switch.mapping.impl</name> <value>org.apache.hadoop.net.ScriptBasedMapping</value> </property>
脚本本身极简,一张 IP 到机架的对照表加 echo 即可。要点是与物理布线严格一致:脚本写错机架,副本放置的容错前提就全部作废——Hadoop 不会替你发现这种错误。
管线不是永动机,节点会掉线。两种典型故障与处理:
DataNode 挂掉。客户端检测到管线中某节点无响应,把故障节点从管线摘除,恢复数据流:好节点继续接收,NameNode 被告知新的副本分布(此刻副本数可能暂时低于目标),后续由 NameNode 异步调度补齐到三副本。写入不中断,这是流式管线设计的韧性问题。
客户端崩溃。文件停留在"创建中",租约(Lease)到期后 NameNode 自动回收,已落盘的块保留、未完成的块作废,文件视同没写完整。软超时 60 秒、硬超时 1 小时,超时后别的客户端才能同名重建。
一个容易忽略的细节:写成功的块不会立刻写满三副本。提交条件是最小副本数(默认 1),异步复制随后补齐。这意味着极端情况下(写入完成瞬间两副本所在节点同时故障)存在丢块窗口。对可靠性要求极高的写入可以把最小副本数配置为 2 或 3,代价是写入延迟上升。
# 准备一个超过两个块的文件 dd if=/dev/urandom of=/tmp/big.bin bs=1M count=300 # 上传并观察块与副本分布 hdfs dfs -put /tmp/big.bin /data/ hdfs fsck /data/big.bin -files -blocks -locations | grep -A1 blk_ # 关注每个块的三个位置:在伪分布式里全在同一节点;全分布式里 # 应能看到 副本1在客户端或近端节点、副本2在另一机架、副本3与副本2同架 # 调整副本因子重传,观察位置变化 hdfs dfs -setrep 2 /data/big.bin hdfs fsck /data/big.bin -files -blocks -locations # repl=2 时通常保留 同机架一份+跨机架一份
在全分布式上做这个实验,能亲眼验证图 2-2 的拓扑——这是建立"放置策略"直觉最快的方式。
数据落了盘,下一节让它流出去:读取路径与客户端操作。