想象你是一座大型社区图书馆的馆长。图书馆有数百万册藏书,一个巨大的仓库,以及成千上万的读者。你不可能把所有书都放在一间屋子里——放不下,也太危险(一场火灾就全毁了)。于是你想出一个办法:建一栋有无数房间的大楼,把书拆成整齐的章节分册,分散存放在不同房间,每个房间放多份副本。为了让读者能快速找到任何一本书,你设置了一本“总索引册”,记录每本书的每个章节放在了哪个房间的哪个架子上。 这就是分布式文件系统的核心思想——而 Google 的 GFS(Google 文件系统)和开源的 HDFS(Hadoop 分布式文件系统)就是这套“图书馆系统”的工程实现。它们把海量数据“拆书分册”,存进成百上千台廉价服务器组成的“大仓库”里,由一台“总索引册”服务器(NameNode)统一调度。本文将带你走进这栋数据大楼的内部,看看它如何设计楼层、分配房间、处理故障,以及今天的大数据工程师又是如何对它进行“改造升级”的。

岗位关联

掌握分布式文件系统架构,是理解整个大数据生态(Hive、Spark、Flink)的地基。无论你做数据工程师、架构师还是 AI 基础设施开发,面试和实战中都绕不开这套设计逻辑。

目录
  1. 可视化演示
  2. 总索引册与大楼
  3. 写入流水线
  4. 副本放置策略
  5. 心跳机制与故障
  6. 小文件问题
  7. 纠删码演进
  8. AI 应用场景
  9. Python 演示
  10. 常见误区
  11. 面试题

可视化演示

展示HDFS写入流水线中副本在DataNode间的传递、副本放置与容错机制,以及纠删码相较3副本的存储开销优化,可切换三种模式观察。

本节配有交互动效(进入视口后自动加载,约 3KB)。

写入流水线 客户端向NameNode请求,获得DataNode列表 数据按pipeline依次传给后续DataNode作副本 DN1 → DN2 → DN3 流水线写入避免客户端重复传输,效率高。

总索引册与大楼

社区图书馆决定不再把书一本本完整存放在单间库房,而是建一栋巨大的“数据大楼”。大楼里有成千上万个房间(DataNode),每个房间专门存放标准大小的“分册”——比如 128MB 一章。馆长办公室里摆着一本总索引册(NameNode),上面记录了:哪些书被拆成了哪些分册、每个分册存放在哪个房间的哪个架子上、读者当前正在借走哪些分册。这套设计,就是 HDFS 的宏观骨架。

在技术世界里,“总索引册”叫 NameNode(名字节点),“数据大楼的房间”叫 DataNode(数据节点)。NameNode 只负责管理元数据——即“文件叫什么、分成几块、每块在哪”这类信息,不存真实数据。DataNode 则真正保管数据内容。为了让管理更简单,文件被拆成固定大小的 块(Block),默认 128MB,每个块独立存储、独立复制。

这个“拆书分册”的设计带来了三个好处:第一,单本书可以超过单个房间的容量(单个文件可以大于单台磁盘);第二,每个分册可以复制多份放在不同房间,防止火灾/磁盘损坏造成数据丢失;第三,读者可以直接从离自己最近的房间读取某个分册,实现负载均衡。当然也有代价——总索引册一旦丢失,整栋大楼就“失明”了,这引出了后面要讲的 HA(高可用)方案。

回到图书馆的比喻:总索引册虽然不占太多空间,但它必须放在最安全的保险柜里,并且时刻保持更新。任何一个分册的房间出了问题,索引册都能立刻发现并安排重新抄录一份。这就是 NameNode 与 DataNode 各司其职的精髓——元数据集中、数据分散。

文件大小=块大小×块数量\text{文件大小} = \text{块大小} \times \text{块数量}
一个 1GB 的文件,在默认 128MB 块大小下会被拆成 8 个块,分别存储,并各自拥有副本。
  1. 块大小HDFS 默认每个块 128MB,远大于传统文件系统的 4KB,这是为了减少元数据条目数。
    Block Size=128MB\text{Block Size} = 128\text{MB}
  2. 块数量文件总大小除以单块大小,向上取整。
    Num Blocks=⌈File SizeBlock Size⌉\text{Num Blocks} = \left\lceil \frac{\text{File Size}}{\text{Block Size}} \right\rceil

写入流水线

现在,一位读者(客户端)想把一本新书存入图书馆。流程是什么样的?他不能自己抱着一堆分册随便找个房间塞进去——他必须先到馆长办公室(NameNode)申请一个“图书编号”(写入许可)。馆长翻开总索引册,找到一个有空间的房间列表,告诉读者:“你可以把这些分册依次放到 1 号房、3 号房、5 号房。”更聪明的是,馆长还会指定“管线顺序”:先放入 1 号房,再由 1 号房直接转发给 3 号房,3 号房再转发给 5 号房,像接力赛一样,而不是让读者来回跑三趟。

在 HDFS 中,这个“接力赛”就叫 写入流水线(Pipeline)。客户端将数据切分成一个个 数据包(Packet,默认 64KB),沿管线依次发送。第一个 DataNode 收到一个包后,一边落盘一边转发给下一个 DataNode,同时最后一个节点会向上游逐级发送确认(ACK)。整个过程像消防员传递水桶救火——每一级只做“接、传、确认”三件事,效率极高。

这样做最大的好处是减少了客户端与各个 DataNode 之间的网络传输次数。如果让客户端分别给三个副本节点各传一遍,就要消耗三倍的客户端带宽;而流水线方式下,客户端只需传一份数据,由节点间内部转发完成复制。Google 的 GFS 论文中详细描述了这一设计,HDFS 沿用了同样的思想。

图书馆馆长不需要亲自帮读者搬书,读者只需把书交给第一个房间的管理员,后面的房间之间会自动接力。这套流水线设计让“写一本 1TB 的大书”变得像“串一串糖葫芦”一样顺畅——一颗一颗串,串完一整串。而在 AI 时代,训练数据动辄 TB 级,流水线写入的高效性直接决定了数据集准备的速度。

写入时间≈数据量节点间带宽+网络延迟×管线深度\text{写入时间} \approx \frac{\text{数据量}}{\text{节点间带宽}} + \text{网络延迟} \times \text{管线深度}
流水线让“一份数据写一次,多个副本同步形成”,而不是“写 N 份数据”。
  1. 数据量文件被切分为数据包依次发送,总数据量不变。
    数据量=Packet 大小×Packet 数量\text{数据量} = \text{Packet 大小} \times \text{Packet 数量}
  2. 管线深度每个 Packet 在管线中经过的节点数,约等于副本数加一。
    管线深度≈副本数\text{管线深度} \approx \text{副本数}

副本放置策略

图书馆馆长有一个重要的“排书原则”:同一本书的分册副本,不能放在同一栋楼里(否则火灾就全没了),也不能放在治安太差的街区。在分布式系统中,这个“街区”就是 机架(Rack)。一个机架上的多台服务器共享一个网络交换机和电源,如果机架断电或交换机故障,整批机器都会“失联”。

HDFS 默认的副本放置策略是 机架感知(Rack Awareness):第一个副本放在客户端所在机架的一个 DataNode 上(写入最快);第二个副本放在同机架的另一个 DataNode 上(满足机架内冗余,且流量可控);第三个副本放在不同机架的某个 DataNode 上(防止整个机架失效)。这就像图书馆把一本书的三个副本分别放在“本栋楼一层”“本栋楼二层”和“隔壁栋楼”——既兼顾了写入效率,又最大化了容灾能力。

这个策略是“效率”与“安全”的折中。如果三个副本都放同一机架,虽然机架内带宽高、写入快,但一个机架断电就全丢;如果三个副本放三个不同机架,安全但跨机架网络流量巨大。Google 的 GFS 论文和 Hadoop 官方文档都强调“机架感知”是分布式存储的基石——它要求管理员配置网络拓扑,让系统“知道”每台服务器在哪个机架。

回到图书馆:馆长把副本放得“既不太近也不太远”。太近,一起遭殃;太远,调书太慢。这个朴素的“三副本折中”至今仍是分布式存储领域最经典可靠的设计之一——虽然它浪费了 200% 的空间,但换来了“随便挂掉一台机器都不丢数据”的确定性保障。对 AI 训练来说,几 TB 的模型参数和数据集,可靠性远比空间成本更重要。

副本分布=(RackA,DNA2)+RackB\text{副本分布} = (\text{Rack}_{A}, \text{DN}_{A2}) + \text{Rack}_{B}
三副本原则:同一机架两个 + 跨机架一个。区块可以完美容忍单机架故障。
  1. 本地副本第一副本写客户端所在节点,省去跨机架传输。
    Replica1=DNlocal\text{Replica}_1 = \text{DN}_{\text{local}}
  2. 机架内第二副本第二副本放同机架另一节点,带宽高。
    Replica2=RackA,DNother\text{Replica}_2 = \text{Rack}_{A}, \text{DN}_{\text{other}}
  3. 跨机架第三副本第三副本放不同机架,防止整机架故障。
    Replica3=RackB,DNany\text{Replica}_3 = \text{Rack}_{B}, \text{DN}_{\text{any}}

心跳机制与故障

图书馆的每个房间管理员每隔几分钟就会到馆长办公室报到一次:“我还在,书都完好。”如果有管理员连续很久不来报到,馆长就会在索引册上标记该房间“失联”,并安排其他房间重新抄录这些分册的副本。这就是 心跳机制(Heartbeat)——是分布式系统感知“谁还活着”的眼睛。

在 HDFS 中,每个 DataNode 默认每 3 秒 向 NameNode 发送一次心跳,携带自己的存储容量、剩余空间、当前块数量等信息。NameNode 如果连续 10 分钟 没有收到某个 DataNode 的心跳,就判定该节点死亡,然后把该节点上所有块的副本标记为“副本数不足”,并调度其他节点重新复制。这个“3 秒报到、10 分钟判死”的参数设计很讲究:太短会误判(网络抖动);太长会导致系统对故障反应迟钝。

心跳不只是“我活着”的信号,它还是 控制指令的下行通道。NameNode 会在心跳响应中携带命令:如“把某块复制到某节点”“删除某块”“关停某个节点进入维护模式”。这就像馆长在管理员报到时顺便递上一张纸条:“今天去 3 号房抄一本书。”这种“上行状态 + 下行指令”的双向通信模式是所有分布式系统(包括 K8s、ZooKeeper)的通用设计。

图书馆馆长不需要亲自巡查每个房间,管理员的自发报到让“这件事”变得极其轻量。同样,HDFS 的心跳机制让 NameNode 不必主动轮询所有节点,而是被动接收状态——这对上万个节点的集群来说,是唯一可行的规模化解法。如果你在面试中被问“NameNode 怎么知道 DataNode 挂了”,答案就是心跳超时。

失联判定时间=心跳间隔×超时倍数\text{失联判定时间} = \text{心跳间隔} \times \text{超时倍数}
HDFS 默认 3 秒心跳、10 分钟超时。超时时间 = 心跳间隔 × 200。
  1. 心跳间隔DataNode 每 3 秒发送一次心跳给 NameNode。
    Heartbeat Interval=3s\text{Heartbeat Interval} = 3\text{s}
  2. 超时倍数NameNode 连续 10 分钟未收到心跳,即 3 秒 × 200。
    Timeout=3s×200=600s=10min\text{Timeout} = 3\text{s} \times 200 = 600\text{s} = 10\text{min}

小文件问题

图书馆的“总索引册”有个尴尬的短板:它的纸张是有限的,每一条“某书某章在某房间”的记录都要占一行。如果一个读者存了 1000 本只有 1 页的小册子,索引册就要记 1000 行;但如果他存 10 本很厚的书(每本 100 分册),索引册只需 1000 行记录。索引册的内存空间,取决于文件数量,而不是数据总量。HDFS 里这条规则同样成立——NameNode 的元数据条目与文件/块数量成正比,而与存储的数据量关系不大。

一个小文件(比如 1KB)在 HDFS 中也要占用一个独立的 Block,产生一条元数据记录。每条元数据记录在 NameNode 内存中大约占用 150 字节。1000 万个 1KB 的小文件意味着 1000 万个块,元数据就占了 1.5GB 内存——而它实际存储的数据只有 10GB。相比之下,1000 万个 128MB 的块(即约 1.2PB 数据)的元数据也只有 1.5GB。这就是 小文件问题(Small Files Problem):文件数量才是 NameNode 内存的杀手,而不是数据量。

解决方案也很直接:把许多小文件“打包”成一个大文件——比如使用 HAR(Hadoop Archive)、SequenceFile,或者数据湖方案中的 Hudi/Iceberg,它们本质都是“把小册子合订成一本厚书”,减少索引册的记录条数。在 AI 场景中,海量小图片、小文本片段常常作为训练数据,通常会先用 TFRecord 或 Parquet 格式做 合并(Coalescing),这正是对 HDFS 小文件问题的经典规避。

图书馆馆长最怕的不是书太多,而是“书太碎”。所以优秀的图书管理员会把零散的小册子定期合订成合集,贴上醒目标签。HDFS 用户也养成了同样的习惯:宁可写大文件,也不要写海量小文件。对 AI 工程师而言,理解这一点能避免“数据明明不多,集群却 OOM”的诡异问题。

NameNode 内存≈150B×Blocks 数量\text{NameNode 内存} \approx 150\text{B} \times \text{Blocks 数量}
文件数量决定块数量,块数量决定 NameNode 内存占用。小文件是 NameNode 内存的隐形杀手。
  1. 元数据大小每个 Block 约 150 字节元数据。
    Metadataper Block≈150B\text{Metadata}_{\text{per Block}} \approx 150\text{B}
  2. 总内存NameNode 内存与块数量成线性关系。
    MemoryNN=150B×NumBlocks\text{Memory}_{\text{NN}} = 150\text{B} \times \text{NumBlocks}

纠删码演进

三副本方案虽然简单可靠,但它太“奢侈”了——存 1TB 数据实际要占 3TB 空间。图书馆馆长会想:“有没有一种办法,既不用存三份,又能在任一分册丢失时把它恢复出来?”答案是数学:纠删码(Erasure Coding, EC)。它把数据切成若干数据块,再计算出若干校验块。任何一块丢了,都可以用剩下的块(数据块+校验块)通过数学运算恢复出来。

纠删码最经典的是 Reed-Solomon(RS)编码,常用配置是 RS(6,3):把数据切成 6 个数据块,计算 3 个校验块,总共 9 块,任意丢失 3 块都能恢复。对比三副本:同样存储 6 个数据块,三副本需要 18 块空间;而 EC 只需 9 块,节省了 50% 的存储空间。代价是什么呢?如果一块丢失,重建时需要读取至少 6 块(副本重建只需读 1 块),网络 IO 和 CPU 开销显著增加。

HDFS 在 3.0 版本中正式引入了 EC 支持(此前主要靠 Apache Ozone/Kudu 等外部系统实现)。工程上的权衡是:冷数据(不常访问)用 EC 省空间,热数据(频繁读写)用副本保性能。这就像图书馆对“新书畅销书”买多本放各层,对“十年没人借的古籍”只存一份原稿+电子扫描即可——访问频率决定冗余策略。GFS 论文中的设计虽然没有完整实现 EC,但后续的 Colossus(GFS 的下一代)已经将纠删码作为核心存储手段。

纠删码是分布式存储从“蛮力冗余”走向“数学冗余”的关键一步。今天的云存储厂商(AWS S3、Azure Blob、阿里云 OSS)几乎全部使用 EC 来降低成本——这也是“对象存储”与 HDFS 在底层架构上的核心差异之一。对 AI 工程师来说,理解 EC 能帮你回答“为什么同样是存 100TB 数据,S3 比自建 HDFS 便宜这么多”。

存储利用率RS(6,3)=66+3=23≈66.7%\text{存储利用率}_{\text{RS(6,3)}} = \frac{6}{6+3} = \frac{2}{3} \approx 66.7\%
RS(6,3):6 个数据块 + 3 个校验块,丢任意 3 块可恢复,存储利用率 67%,相比三副本提升 100%。
  1. 数据块数原始文件被切成 k 个数据块,如 k=6。
    Data Blocks=6\text{Data Blocks} = 6
  2. 校验块数通过编码算法生成 m 个校验块,如 m=3。
    Parity Blocks=3\text{Parity Blocks} = 3
  3. 容错能力最多可丢失 m 个块而不丢数据。
    Tolerated Loss=m=3\text{Tolerated Loss} = m = 3
  4. 存储效率有效数据占比为 k/(k+m)。
    Efficiency=kk+m=69\text{Efficiency} = \frac{k}{k+m} = \frac{6}{9}

AI 应用场景

大数据训练集的海量小文件合并

阶段:数据准备(Data Preparation)

解决的问题:AI 训练数据集常包含数百万张小图片或短文本片段,直接写入 HDFS 会导致 NameNode 元数据爆炸、训练读取性能低下。传统方式“小文件直接存”会让集群在几百万文件时陷入卡顿。

具体案例:某自动驾驶公司积累了 500 万张标注图片(每张约 200KB)。数据工程师将这些图片按 TFRecord 格式每 1000 张打包成一个文件,最终只产生 5000 个文件,训练读取吞吐量提升了 5 倍,NameNode 内存占用从可能崩溃的边缘降到安全范围。这个过程本质是对“小文件问题”的工程化解。

模型 checkpoint 的三副本存储

阶段:模型训练与容灾(Training & Checkpointing)

解决的问题:大模型训练(如 GPT 级别)每隔几小时保存一次数 TB 的模型权重。传统单机存储无法容纳且不安全。使用 HDFS 三副本策略,任一节点宕机都不影响 checkpoint 的完整性。

具体案例:某大模型团队使用 10 台 GPU 服务器做分布式训练,每 2 小时将 5TB 的模型权重写入 HDFS(默认三副本)。训练中途某台 DataNode 磁盘损坏,系统通过心跳感知并自动在其他节点重建副本,训练任务无感知继续运行。

冷热数据分层与纠删码存储

阶段:数据归档(Archival)

解决的问题:AI 公司积累了多年训练数据、日志和实验记录,总量达数十 PB,使用三副本成本过高。传统方式“一律三副本”导致存储成本几乎占 IT 预算三分之一。

具体案例:某互联网 AI 部门将超过 180 天未访问的冷数据迁移到启用 EC(6,3) 的 HDFS 归档目录,存储空间占用降低 50%,每年节省约 800 万元服务器采购费用。查询冷数据时读取延迟有轻微上升(需要解码),但对离线分析影响可忽略。

Python 演示

python95 行
import hashlibimport randomimport time  class DataNode:    def __init__(self, node_id, rack):        self.node_id = node_id        self.rack = rack        self.blocks = {}        self.alive = True        self.last_heartbeat = time.time()     def heartbeat(self):        self.last_heartbeat = time.time()     def store_block(self, block_id, data):        self.blocks[block_id] = data     def is_dead(self, timeout=10):        return time.time() - self.last_heartbeat > timeout  class NameNode:    def __init__(self, replication=3):        self.datanodes = []        self.file_table = {}        self.replication = replication     def register_datanode(self, datanode):        self.datanodes.append(datanode)     def choose_datanodes(self, rack):        same_rack = [dn for dn in self.datanodes if dn.rack == rack]        other_rack = [dn for dn in self.datanodes if dn.rack != rack]        chosen = []        if same_rack:            chosen.append(random.choice(same_rack))        if len(chosen) < self.replication and same_rack:            chosen.append(random.choice(same_rack))        while len(chosen) < self.replication:            chosen.append(random.choice(other_rack))        return chosen[:self.replication]     def write_file(self, path, data):        block_size = 64        blocks = [data[i:i + block_size] for i in range(0, len(data), block_size)]        locations = []        for idx, block in enumerate(block_id_assign()):            if len(locations) == 0:                continue        block_ids = [hashlib.md5(f"{path}-{i}".encode()).hexdigest()[:8] for i in range(len(blocks))]        for bid, block in zip(block_ids, blocks):            client_rack = random.choice(list(set(dn.rack for dn in self.datanodes)))            targets = self.choose_datanodes(client_rack)            for dn in targets:                dn.store_block(bid, block)            locations.append((bid, [dn.node_id for dn in targets]))        self.file_table[path] = locations        return locations     def check_health(self):        dead = []        for dn in self.datanodes:            if dn.is_dead():                dead.append(dn.node_id)        return dead  def block_id_assign():    yield hashlib.md5(b"fake").hexdigest()[:8]  def main():    nn = NameNode(replication=3)    racks = ["rack-a", "rack-a", "rack-b", "rack-b"]    for i in range(4):        dn = DataNode(node_id=f"dn-{i}", rack=racks[i])        nn.register_datanode(dn)     data = b"hello hdfs! " * 40    locations = nn.write_file("/user/ai/data.bin", data)    print("=== 文件块分布 ===")    for block_id, nodes in locations:        print(f"block {block_id} -> {', '.join(nodes)}")     print("=== 心跳检查 ===")    time.sleep(1)    for dn in nn.datanodes:        dn.heartbeat()    print(f"存活节点数: {len(nn.datanodes) - len(nn.check_health())}")  if __name__ == "__main__":    main()
目的:模拟 HDFS 的简化版——文件分块、机架感知的副本放置和心跳上报机制,帮助理解核心概念。
  1. block_size = 64设定块大小(实际 HDFS 为 128MB),演示文件被切分。
  2. def choose_datanodes(self, rack)实现机架感知策略:优先同机架、再跨机架。
  3. dn.store_block(bid, block)将块数据写入 DataNode 的 blocks 字典,模拟磁盘存储。
  4. def check_health(self)遍历所有 DataNode,判定心跳超时的节点并返回。
  5. nn.write_file("/user/ai/data.bin", data)模拟客户端通过 NameNode 完成一次文件写入。

常见误区

误区很多人以为 NameNode 保存了一份完整的数据(或数据副本),所以 NameNode 挂了数据就全丢了。实际它是“索引”,不存数据本体。

正解NameNode 只存元数据——文件路径、块 ID、块位置、副本数,总内存通常几 GB 到几十 GB。它不存数据内容。NameNode 挂了会导致“找不到文件”,但 DataNode 上的数据块还在,等 NameNode 恢复后仍可继续使用。就像图书馆的总索引册丢失后,书仍在各个房间里,只是没人知道在哪。需要重建索引(或依赖 JournalNode 恢复)。

为何常见:因为“NameNode“这个名称里的”Name“看似重要,初学者容易把它理解成”数据中枢“,而不是”名字中枢”。

误区有人以为 HDFS 写三副本是客户端拷贝三份,分别发给三个 DataNode,导致客户端带宽占用三倍。

正解实际是流水线写入:客户端写一份给第一个 DataNode,第一个节点边存边转发给第二个,第二个再转发给第三个。客户端只需消耗一份数据的网络带宽。这就像消防员传水桶,不是每个人跑三趟水源。

为何常见:直觉上以为“多个副本 = 多次写入”,忽略了流水线这种经典优化设计。GFS 论文首次描述了该机制。

误区以为心跳设得越短,系统发现故障越快,所以应该设 100ms 而不是 3 秒。

正解心跳太短会给 NameNode 和网络带来巨大压力。上万节点每 100ms 上报一次,每秒 10 万次心跳,NameNode 的 RPC 处理会成瓶颈。3 秒是权衡后的经验值。而且故障检测不仅要快,还要避免误判——网络抖动时短心跳容易误杀健康节点,导致无谓的副本重建风暴。

为何常见:初学者只关注“快”而忽略了分布式系统中的效率和稳定性权衡。

误区以为把块从 128MB 调到 1GB 能省更多空间,因为块越多元数据越多,块少就省了。

正解块大小与磁盘利用率不是直接关系。文件大小不能被块大小整除时,最后一个块会有空间浪费(这部分才是碎片)。增大块可以减少 NameNode 内存压力,但会降低存储灵活性和 MapReduce 并行度。块过大会让单任务处理数据量过大,导致并行度下降。

为何常见:将“块大小”与“存储效率”混淆,实际关键是“文件数量”而非“块大小”。

误区EC 存储效率更高,所以应该把所有数据都用 EC 存,彻底取消副本机制。

正解EC 在读取时需要解码计算,单块丢失重建要读 k 个块,相比三副本直接复制一块,CPU 和网络开销大很多。对热数据(频繁读)、小文件(k+m 块的元数据开销大)不合适。生产实践通常“热数据三副本、冷数据 EC”。S3 等对象存储大量使用 EC,但底层服务器仍保留部分副本以保障低延迟读取。

为何常见:只看到 EC 的空间优势,忽视了它的计算和重建代价。

面试题

Q1. HDFS 中 NameNode 的主要职责是什么?

单选题 · easy

  • A.存储所有的文件数据内容
  • B.管理文件系统的元数据← 正确答案
  • C.执行 MapReduce 计算任务
  • D.负责数据的加密解密

答案:B。NameNode 相当于图书馆的“总索引册”,它只管理元数据(文件路径、块位置、副本数),不存数据本体,也不执行计算任务。存储数据的是 DataNode。选项 A 混淆了 NameNode 与 DataNode 的职责。

Q2. HDFS 默认的块大小是多少?

单选题 · easy

  • A.4KB
  • B.64MB
  • C.128MB← 正确答案
  • D.1GB

答案:C。HDFS 默认块大小为 128MB(Hadoop 2.x 之后),早期版本是 64MB。4KB 是传统本地文件系统的块大小,1GB 则太大,会严重影响并行度。块越大元数据越少,但并行度越低。

Q3. HDFS 三副本的默认放置策略是?

单选题 · mid

  • A.三个副本分别放在三个不同机架
  • B.两个副本同机架,一个副本跨机架← 正确答案
  • C.三个副本都放在客户端所在机架
  • D.随机放置在任何 DataNode 上

答案:B。默认策略是“第 1 副本在客户端所在节点,第 2 副本在同机架另一节点,第 3 副本在跨机架节点”。三个全放不同机架虽安全但跨机架网络开销大;全放同机架又无法容忍机架故障。B 是效率与安全的经典折中。

Q4. 关于 HDFS 写入流水线,下列说法正确的是?

单选题 · mid

  • A.客户端分别向每个副本节点独立传输数据
  • B.客户端只向第一个 DataNode 传输,后续节点间自行复制← 正确答案
  • C.副本数量由客户端在写入时决定
  • D.写入过程不需要 NameNode 参与

答案:B。流水线是 HDFS/GFS 的核心设计之一:客户端将数据包发给第一个 DataNode,第一个节点存好后再转发给第二个,依次传递,最后逐级确认。这样客户端只需消耗一份数据的带宽。选项 D 错误,写请求必须先向 NameNode 申请。

Q5. HDFS 中 DataNode 心跳超时默认判定为节点死亡的时间是?

单选题 · mid

  • A.3 秒
  • B.30 秒
  • C.10 分钟← 正确答案
  • D.1 小时

答案:C。HDFS 中 DataNode 每 3 秒发送一次心跳,NameNode 若连续 10 分钟(600 秒)未收到某节点心跳才判定其死亡。3 秒是心跳间隔而非超时时间。10 分钟是经验值,兼顾故障检测速度和误判率。

Q6. HDFS 中“小文件问题”会导致哪些影响?

多选题 · mid

  • A.NameNode 内存被大量元数据占满← 正确答案
  • B.文件读写性能急剧下降← 正确答案
  • C.磁盘空间利用率下降← 正确答案
  • D.DataNode 存储空间不足

答案:ABC。每个小文件至少占一个块,产生一条元数据,消耗 NameNode 内存;大量小文件让读写频繁切换,性能下降;最后一个小块不足 128MB 会浪费磁盘空间。这三点是小文件问题的核心影响。选项 D 的“存储空间不足”不是小文件导致的。

Q7. 在 Reed-Solomon 编码 RS(6,3) 中,存储空间利用率和容错能力分别是?

单选题 · hard

  • A.67%,可容忍丢失 3 个块← 正确答案
  • B.50%,可容忍丢失 6 个块
  • C.75%,可容忍丢失 9 个块
  • D.100%,可容忍丢失 3 个块

答案:A。RS(6,3) 表示 6 个数据块 + 3 个校验块,总存储 9 块,数据占 6/9≈67%。最多可容忍丢失任意 3 块(m=3)。B 选项数据错误,C 是“仅存副本”的概念,D 是“无冗余”的概念。EC 相对三副本(30% 利用率)显著节省空间。

Q8. 关于 HDFS 与 GFS 的共同设计思想,下列说法正确的是?

多选题 · hard

  • A.元数据集中管理,数据分散存储← 正确答案
  • B.通过心跳机制感知节点故障← 正确答案
  • C.移动计算比移动数据更高效← 正确答案
  • D.所有副本必须跨机架放置

答案:ABC。GFS 与 HDFS 都遵循“单一元数据节点 + 大量数据节点”的模式,都用心跳探活,都强调将计算任务调度到数据所在节点(数据本地性)。但 D 错误——默认策略不是“所有副本跨机架”,而是两副本同机架、一副本跨机架。

Q9. HDFS 中数据块默认复制因子(副本数)是多少?

单选题 · easy

  • A.1
  • B.2
  • C.3← 正确答案
  • D.5

答案:C。HDFS 默认副本数为 3,这是可靠性与成本权衡的结果。1 份太危险,2 份在机架故障时仍可能全丢,3 份可以容忍单机架故障。这个配置可通过 dfs.replication 参数调整,但生产环境默认 3 是业界共识。

Q10. 在 HDFS 数据写入过程中,哪些组件参与了确认应答(ACK)?

多选题 · hard

  • A.客户端(Client)← 正确答案
  • B.NameNode← 正确答案
  • C.第一个 DataNode
  • D.最后一个 DataNode← 正确答案

答案:ABD。数据包沿管线从第一个 DataNode 传到最后一个,最后一个节点写完返回 ACK,逐级回传到客户端。NameNode 参与的是写入许可和块分配阶段,不参与数据包的 ACK 链。选项 B 的 NameNode 在 ACK 链路中不出现,这是流水线设计的要点。