大数据系列(二) HDFS:把文件切碎了存到一堆机器上
2026/4/29 7:00:17 网站建设 项目流程

HDFS:把文件"切碎了"存到一堆机器上

大数据系列第 2 篇:海量数据存在哪?来看看大数据世界的"硬盘"——HDFS。


先问个问题:100TB 的文件,你怎么存?

假设你们公司有个需求:把过去 10 年的用户行为日志都保存下来,用于后续分析。这些日志加起来有100TB

你第一反应可能是:买个大硬盘?

市面上最大的单块硬盘大概 20TB 左右,100TB 需要 5 块。但这只是开始:

  • 硬盘会坏,坏了数据就没了,得做备份吧?
  • 5 块硬盘挂在一台机器上,这台机器挂了怎么办?
  • 100TB 只是现在的量,明年可能 200TB 了,后年 500TB,你准备一直买硬盘加机器?
  • 这么多数据,分析的时候怎么读?一台机器的网卡带宽撑得住吗?

说白了,单机存储方案在 PB 级数据面前,就是个弟弟。

这时候,HDFS 登场了。


HDFS 是什么?

HDFS(Hadoop Distributed File System,Hadoop 分布式文件系统),说白了就是把一个大文件切成很多小块,分散存到多台机器上,同时做好备份,坏了几台机器数据也不会丢。

它的设计哲学来自 Google 2003 年发表的一篇论文(GFS,Google File System)。Google 那帮人说:咱们别买昂贵的专用存储设备了,就用一堆便宜的普通机器(几百美元一台的那种),通过软件层面的设计来保证可靠性。量大管饱,便宜好用。

HDFS 有几个核心假设,理解这些假设很重要:

  1. 硬件故障是常态:机器随时可能挂,硬盘随时可能坏,设计时就要把容错放在第一位
  2. 文件很大:适合存 GB 甚至 TB 级别的大文件,不适合存大量小文件
  3. 一次写入,多次读取:文件写入后基本不修改,主要用来读和分析
  4. 高吞吐量优先:追求"单位时间内读写的数据总量",不追求"单次读写的延迟"

HDFS 的架构:一个"老板"带一群"工人"

HDFS 的架构很简单,就三种角色:

┌─────────────────────────────────────────────────────────────────┐ │ HDFS 架构(人话版) │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────────────┐ │ │ │ NameNode │ ← "大管家" │ │ │ (名称节点) │ │ │ │ │ • 记住所有文件存在哪 │ │ │ │ • 记住每个文件被切成了几块 │ │ │ │ • 记住每块数据在哪些机器上 │ │ │ │ • 不存实际数据,只存"目录信息" │ │ └────────┬────────┘ │ │ │ │ │ │ "dn1,你身上的 block_001 还在吗?" │ │ │ "在的,老大!" │ │ │ │ │ ┌────────┴───────────────────────────────────────────────┐ │ │ │ DataNode 集群 │ │ │ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │ │ │ │DataNode │ │DataNode │ │DataNode │ │DataNode │ │ │ │ │ │ dn1 │ │ dn2 │ │ dn3 │ │ dn4 │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │block_001│ │block_001│ │block_001│ │block_002│ │ │ │ │ │block_002│ │block_003│ │block_002│ │block_003│ │ │ │ │ │block_003│ │ │ │block_004│ │block_004│ │ │ │ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ │ │ │ │ │ │ │ 每个 DataNode 就是一台普通机器,存着实际的数据块 │ │ │ └─────────────────────────────────────────────────────────┘ │ │ │ │ 还有个 Secondary NameNode(辅助管家),后面再说它是干嘛的 │ │ │ └─────────────────────────────────────────────────────────────────┘

NameNode:大管家

NameNode 是 HDFS 的老大,所有元数据(Metadata)都存在它脑子里:

  • 文件系统目录树:有哪些文件夹、哪些文件
  • 文件到数据块的映射:每个文件被切成了哪几块
  • 数据块的位置信息:每块数据存在哪些 DataNode 上

注意:NameNode 不存实际数据,只存"地图"。实际数据都在 DataNode 上。

NameNode 把所有元数据放在内存里,所以访问速度飞快。但这也意味着——NameNode 的内存就是整个集群的瓶颈。据说每个文件或目录大概占用 150 字节内存,如果你存 10 亿个文件,NameNode 需要 150GB 内存。这数字听着就刺激。

DataNode:干活的工人

DataNode 就是集群里的一台台普通机器,负责:

  • 存实际的数据块
  • 定期向 NameNode 汇报:“我还活着,我身上有这些块”
  • 按照 NameNode 的指令,复制数据块到其他机器(比如某台机器挂了,需要补副本)

Secondary NameNode:辅助管家(不是备胎!)

很多人一听"Secondary"就以为它是 NameNode 的备份,故障时能顶上去。其实不是。

Secondary NameNode 的工作是:定期帮 NameNode “整理账本”。NameNode 的元数据有两种存储形式:

  • FsImage:文件系统的完整快照(就像一本完整的账)
  • EditLog:最近的操作记录(就像每天的流水账)

Secondary NameNode 定期把 FsImage 和 EditLog 合并,生成新的 FsImage,减轻 NameNode 启动时的负担。它不能替代 NameNode,NameNode 挂了它顶不上。

那 NameNode 挂了怎么办?后面聊高可用的时候再说。


数据块与副本:怎么保证不丢数据?

文件怎么切?

HDFS 把大文件切成固定大小的数据块(Block),默认 128MB(Hadoop 1.x 是 64MB)。

┌─────────────────────────────────────────────────────────────────┐ │ HDFS 文件切分示意 │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ 你的文件:mydata.log(500MB) │ │ │ │ HDFS 把它切成: │ │ │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │ Block 1 │ │ Block 2 │ │ Block 3 │ │ Block 4 │ │ │ │ 128MB │ │ 128MB │ │ 128MB │ │ 116MB │ │ │ │ │ │ │ │ │ │(最后一块 │ │ │ │ │ │ │ │ │ │ 可能不满)│ │ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ │ │ │ 切分的好处: │ │ • 大文件能分散存到多台机器 │ │ • 读取时可以并行从多台机器读,速度快 │ │ • 方便做负载均衡,哪台机器空闲就往哪存 │ │ │ └─────────────────────────────────────────────────────────────────┘

副本机制:默认存 3 份

HDFS 默认给每个数据块存 3 个副本。这 3 个副本不是随便放的,有个**机架感知(Rack-Aware)**策略:

┌─────────────────────────────────────────────────────────────────┐ │ HDFS 副本放置策略(3 副本) │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ 机架 1(Rack 1) 机架 2(Rack 2) │ │ ┌─────────────────┐ ┌─────────────────┐ │ │ │ DataNode 1 │ │ DataNode 3 │ │ │ │ │ │ │ │ │ │ ★ Block 1 │◄────►│ ★ Block 1 │ │ │ │ (第 1 个副本) │ │ (第 3 个副本) │ │ │ │ │ │ │ │ │ │ ★ Block 2 │ │ ★ Block 2 │ │ │ └─────────────────┘ └─────────────────┘ │ │ │ │ │ │ 同机架传输,带宽高、速度快 │ │ ▼ │ │ ┌─────────────────┐ │ │ │ DataNode 2 │ │ │ │ │ │ │ │ ★ Block 1 │ │ │ │ (第 2 个副本) │ │ │ │ │ │ │ │ ★ Block 3 │ │ │ └─────────────────┘ │ │ │ │ 放置规则: │ │ 1. 第 1 个副本:放在客户端所在的机器(如果客户端在集群外, │ │ 就随机选一台) │ │ 2. 第 2 个副本:放在另一个机架的机器上 │ │ 3. 第 3 个副本:放在和第 1 个副本同一个机架、但不同机器上 │ │ │ │ 为什么这么放? │ │ • 同机架两台机器有副本:读取时优先读近的,速度快 │ │ • 跨机架也有副本:万一整个机架断电/断网,数据不会全丢 │ │ │ └─────────────────────────────────────────────────────────────────┘

这个设计挺巧妙的:两个副本在同机架(读取快),一个副本在异机架(容错强)。既考虑了性能,又考虑了可靠性。

数据完整性校验

HDFS 还会给每个数据块算一个校验和(Checksum)。DataNode 定期扫描自己存的数据块,重新算一遍校验和,跟原来的比对。如果发现不一致(说明数据损坏了),就报告给 NameNode,NameNode 会从其他副本复制一份好的过来,替换掉坏的。

所以,除非同一时刻 3 个副本所在的机器全挂了,否则数据不会丢。这种概率,比你中彩票还低。


读写流程:数据怎么存进去、怎么读出来?

写入流程

┌─────────────────────────────────────────────────────────────────┐ │ HDFS 写入流程(简化版) │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ 你(客户端) NameNode DataNode 1 DataNode 2 │ │ │ │ │ │ │ │ │ "我要写文件" │ │ │ │ │ │─────────────►│ │ │ │ │ │ │ │ │ │ │ │◄─────────────│ "去 dn1、dn2、dn3 上写" │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ 建立流水线 ──►│───────────────►│─────────────►│ │ │ │ (你→dn1→dn2→dn3) │ │ │ │ │ │ │ │ │ 发数据包 ────►│───────────────►│─────────────►│ │ │ │ (64KB/包) │ │ │ │ │ │ │ │ │ │ │ │◄─────────────│◄───────────────│◄─────────────│ "写好了" │ │ │ │ │ │ │ │ │ "关闭文件" │ │ │ │ │ │─────────────►│ │ │ │ │ │◄─────────────│ "OK,元数据已更新" │ │ │ │ 关键点: │ │ • 数据通过流水线传输:你发给 dn1,dn1 转发给 dn2,dn2 转发给 dn3 │ │ • 每个数据包(默认 64KB)都要等最后一个节点确认后才发下一个 │ │ • 这样设计是为了保证所有副本都写成功后才算完成 │ │ │ └─────────────────────────────────────────────────────────────────┘

写入过程有几个值得注意的点:

  1. 流水线传输:客户端不是分别给 3 个 DataNode 各发一份数据,而是发给第一个,第一个转发给第二个,第二个转发给第三个。这样客户端只需要维护一个网络连接。

  2. 确认机制:每个数据包要逐级返回确认,客户端收到确认后才发下一个包。这保证了数据确实写到了所有副本上。

  3. 内存缓冲:数据先写入 DataNode 的内存缓冲区,缓冲区满了或者文件关闭时,才刷到磁盘。所以 HDFS 写入性能还不错,但断电时可能丢一点缓冲区的数据。

读取流程

读取就简单多了:

  1. 你问 NameNode:“我要读这个文件,数据在哪些机器上?”
  2. NameNode 告诉你:“block_001 在 dn1、dn2、dn3 上,block_002 在 dn2、dn3、dn4 上……”
  3. 你挑最近的 DataNode(通常是同机架的)去读数据
  4. 读完一个 block,自动去读下一个 block,直到文件读完

NameNode 会按网络距离排序返回副本位置,优先让你读最近的。比如你跟 dn1 在同一个机架,那就先读 dn1,dn1 挂了再读 dn2,以此类推。


NameNode 高可用:大管家不能挂

前面说了,Secondary NameNode 不是 NameNode 的备份。那 NameNode 挂了怎么办?在 Hadoop 2.x 之前,这确实是个大问题——NameNode 一挂,整个 HDFS 集群就停摆了,恢复可能要几十分钟。

Hadoop 2.x 引入了**高可用(HA)**方案:

┌─────────────────────────────────────────────────────────────────┐ │ HDFS NameNode 高可用(HA) │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────────┐ ┌─────────────┐ │ │ │ Active │◄─────────►│ Standby │ │ │ │ NameNode │ 状态同步 │ NameNode │ │ │ │ │ │ │ │ │ │ • 处理所有 │ │ • 实时同步 │ │ │ │ 客户端请求 │ │ 元数据 │ │ │ │ • 写 EditLog │ │ • 热备待命 │ │ │ └──────┬──────┘ └──────┬──────┘ │ │ │ │ │ │ │ 写 EditLog │ 读 EditLog │ │ │ │ │ │ └──────────┬──────────────┘ │ │ │ │ │ ┌─────────┴─────────┐ │ │ │ JournalNode 集群 │ │ │ │ ┌─────┐┌─────┐┌─────┐ │ │ │ │ JN1 ││ JN2 ││ JN3 │ (通常 3 个或 5 个) │ │ │ └─────┘└─────┘└─────┘ │ │ │ │ │ │ • Active NN 把操作日志写到 JournalNode │ │ │ • Standby NN 从 JournalNode 读日志,保持同步 │ │ │ • 多数派写入成功才算成功(类似 Paxos) │ │ └───────────────────────┘ │ │ │ │ ZooKeeper:负责监控 NameNode 健康状态,故障时自动切换 │ │ │ │ 故障切换流程: │ │ 1. ZooKeeper 发现 Active NameNode 挂了 │ │ 2. Standby NameNode 自动升级为 Active │ │ 3. 整个过程秒级完成,客户端几乎无感知 │ │ │ └─────────────────────────────────────────────────────────────────┘

核心思路是:搞两个 NameNode,一个 Active(干活),一个 Standby(待命)。Active 的所有操作都写到 JournalNode 集群里,Standby 实时从 JournalNode 读日志,保持元数据同步。Active 挂了,Standby 秒级切换上位。

JournalNode 通常部署奇数个(3 或 5),采用多数派写入机制——Active 把日志写到超过一半的 JournalNode 上才算成功。这样即使个别 JournalNode 挂了,系统还能正常工作。


HDFS 的坑:小文件问题

聊到这里,不得不提 HDFS 的一个大坑——小文件问题

HDFS 的设计假设是"文件很大",每个文件无论多小,NameNode 都要在内存里记录它的元数据(文件名、权限、块列表等)。如果你存了几千万个几 KB 的小文件:

  • NameNode 内存会被撑爆
  • 读取时要跟 NameNode 频繁交互,效率极低
  • MapReduce/Spark 处理时,每个小文件对应一个 Task,Task 启动开销比处理数据还大

所以,HDFS 不适合存大量小文件。如果你的场景确实有很多小文件,常见的解决方案有:

  1. 合并小文件:写数据时定期把小文件合并成大文件(比如用 SequenceFile、Avro、Parquet 格式)
  2. HAR 文件:Hadoop Archive,把多个小文件打包成一个 HAR 文件
  3. 对象存储:小文件存到 HBase 或 S3 等对象存储里

HDFS 适合什么、不适合什么?

适合的场景不适合的场景
存大文件(100MB 以上)存大量小文件(几 KB 几 MB)
批量数据读取(MapReduce/Spark 输入)低延迟随机读写(像数据库那样)
数据归档和备份频繁修改文件内容
日志存储和分析事务型 OLTP 应用
一次写入多次读取需要 POSIX 文件系统语义

小结

今天咱们聊了 HDFS 的核心机制:

  1. 架构:NameNode 管元数据,DataNode 存实际数据,Secondary NameNode 帮忙整理账本
  2. 数据块:大文件切成 128MB 的块,分散存储
  3. 副本机制:默认 3 副本,机架感知放置,兼顾性能和容错
  4. 读写流程:流水线传输、逐级确认、就近读取
  5. 高可用:Active + Standby NameNode + JournalNode,秒级故障切换
  6. 小文件问题:HDFS 的软肋,大量小文件会撑爆 NameNode 内存

HDFS 的定位很清晰:它是一个高容错、高吞吐量的分布式文件系统,专门为批量数据处理场景设计。它不是数据库,不能随机读写;它不是低延迟存储,不适合交互式查询。但在"海量数据、批量读取、一次写入多次读取"的场景下,HDFS 是当之无愧的基石。

你用过 HDFS 吗?有没有遇到过小文件问题或者 NameNode 内存不够的情况?欢迎聊聊~


需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询