HDFS 架构笔记:NameNode、DataNode 与元数据管理
上一篇把 Hadoop 的三大组件过了一遍。这篇专门整理 HDFS,重点是搞清楚三个角色各管什么——尤其是一个我一开始理解错了的地方:SecondaryNameNode 并不是 NameNode 的备份。
先记住一句话:HDFS 是为"大文件顺序读"设计的
HDFS 不是通用文件系统。它的设计目标非常具体:
- 存超大的文件(GB 到 TB 级)
- 一次写入、多次读取(不做频繁修改)
- 跑在廉价商用机器上,所以要靠软件层面容错
记住这三条,后面所有的设计取舍都能推出来。比如"为什么块这么大"、"为什么不适合存海量小文件"——答案都在这三条里。
三个角色
NameNode:管账本的
NameNode 是集群的主节点,它不存实际数据,只存元数据(metadata)——讲义里的定义是三项:
- 文件的大小
- 文件的位置——每个数据块分布在哪些 DataNode 上
- 文件的权限
打个比方:NameNode 是图书馆的索引卡,DataNode 是书架。你要找书,先查索引卡知道在哪排书架,再去书架上取。索引卡本身不装书。
这个类比能解释一个常见疑问:为什么 NameNode 内存不够是致命问题?因为元数据全部驻留在内存里。文件越多,索引卡越多,内存压力越大。而市面上的文件数量往往比总容量增长得快得多——这就是"HDFS 不适合存海量小文件"的根本原因:一个 1KB 的文件和一个 1GB 的文件,在 NameNode 内存里占的元数据条目差不多大。
DataNode:干活的
DataNode 是从节点,负责存实际的数据块。它干两件事:
一是存块。客户端写入的数据被切成固定大小的块,分散存到多个 DataNode 上。
二是定期汇报。每个 DataNode 会周期性地向 NameNode 发送"心跳"和"块报告",告诉它自己还活着、手上有哪些块。
心跳机制是 HDFS 容错的基础:如果 NameNode 一段时间收不到某个 DataNode 的心跳,就认定它挂了,然后把该节点上的块副本在其他机器上重新复制一份,恢复到设定的副本数。
这解释了为什么 HDFS 能在廉价机器上跑——单台机器坏了不需要人工介入,系统自己会补齐副本。
SecondaryNameNode:名字骗了很多人
这是最容易误解的地方。看到 "Secondary" 和 "NameNode" 放在一起,几乎所有人第一反应都是"它是 NameNode 的备份/备胎"。
不是。它不提供高可用,NameNode 挂了它顶不上去。
那它到底干什么?它做的是 checkpoint(检查点)。
要理解这个,得先知道 NameNode 的元数据是怎么存的。它由两部分组成:
- fsimage——某一时刻元数据的完整快照
- edits——这之后的所有写操作日志
客户端每次创建文件、写数据,都会往 edits 里追加一条记录。时间一长,edits 会变得非常大。而 NameNode 重启时需要把 fsimage 和庞大的 edits 合并起来才能恢复状态——这个过程很慢。
SecondaryNameNode 的作用就是定期帮 NameNode 做这个合并:它拉取 fsimage 和 edits,在本地合并成新的 fsimage,再交还给 NameNode。这样 edits 就能保持在较小的规模,重启也更快。
所以在讲义里的架构图中,它被描述为"辅助管理元数据"——注意是辅助管理,不是备份接管。
真正的高可用方案是配置两个 NameNode 互为主备(Active/Standby),用 JournalNode 集群同步元数据。这就是上一篇提到的第三种和第四种架构。
这个误解的危害在于:如果真以为 SecondaryNameNode 是备胎,就会觉得"我已经有高可用了"——实际上 NameNode 一挂,集群照样不可用。
两个关键参数
搭集群时改的配置里,有两个直接决定了 HDFS 的行为:
块大小 dfs.blocksize
<property>
<name>dfs.blocksize</name>
<value>134217728</value>
</property>134217728 字节 = 128MB。这是 Hadoop 2.x 之后的默认块大小(1.x 时代是 64MB)。
块设得这么大,是为了降低寻址时间占比。读一个 128MB 的块,磁盘寻道时间可能只有几十毫秒,而顺序读取要花好几秒——寻道开销被摊薄了。块太小的话,寻道时间占比上升,吞吐就上不去。
反过来,块太大也有问题:MapReduce 里一个块通常对应一个 map 任务,块太大导致单个任务跑得太久,整个作业的并行度下降。
副本数 dfs.replication
<property>
<name>dfs.replication</name>
<value>3</value>
</property>默认 3 副本。为什么是 3 不是 2?
2 副本的话,坏一块盘,剩下的唯一副本就成了单点;而且 HDFS 的机架感知策略要保证"副本不在同一个机架上",2 个副本凑不出合理的分布。3 副本可以做到:1 个在本机架,2 个在其他机架——既保证了容错,又兼顾了读性能。
代价是存储空间实际可用率只有 1/3。存 1TB 数据要占 3TB 磁盘。这是可靠性的成本。
讲义里的集群实验把这个值设成了 3——三台机器刚好每台存一份。
写数据的流程
把上面这些串起来,一次写文件大致是这样:
客户端请求写文件
↓
NameNode 检查权限、确认文件不存在
↓
NameNode 返回一批可用的 DataNode 列表(按机架感知排好序)
↓
客户端把数据切成块,向第一个 DataNode 写
↓
第一个 DataNode 收到后,同时转发给第二个,第二个再转发给第三个(管道)
↓
三份都写完后,逐级往回确认
↓
客户端通知 NameNode 写入完成注意几个设计点:
数据不经过 NameNode。NameNode 只告诉客户端"去哪些机器写",实际的数据流是客户端直接和 DataNode 打交道。这样 NameNode 不会成为吞吐瓶颈——它只处理元数据请求,数据量再大也不经过它。
副本是流水线复制的。不是客户端写三遍,而是第一个 DataNode 收到后转发给第二个。客户端只上传一份,节省了客户端出口带宽。
写成功后 NameNode 才更新元数据。这保证了元数据不会指向不存在的数据。
两个容易记混的地方
这一章概念不少,但真正容易记错、而且记错了会有实际后果的,我觉得是两个。
第一个是 SecondaryNameNode。名字太有迷惑性,看到它就以为 NameNode 挂了能顶上。实际上它做的是 checkpoint——定期把 fsimage 和 edits 合并,让 NameNode 重启时不用回放庞大的日志。它不提供高可用。真要容错得配 Active/Standby 双 NameNode 加 JournalNode。
这个概念搞错的代价不小:以为已经有了备胎,实际上 NameNode 一挂整个集群就停摆,而且你还不会提前做数据备份。
第二个是"不适合存小文件"。这句话经常被当成 HDFS 的缺点来讲,但我觉得它不是缺点,是定位。它面对的本来就是"单机装不下的大文件",元数据全部驻留内存这个设计也是为此服务的。真有几百万个 1KB 的日志碎片要存,该用的是 HBase 或者对象存储,而不是硬塞给 HDFS。
暂无评论