Hadoop 入门系列 · 第 3/10 篇
上一篇:《HDFS 核心概念》
下一篇预告:《MapReduce 思想》
开头:300MB 的文件,从上传到下载经历了什么?
你在笔记本上有一个 300MB 的日志包,要存进 HDFS,第二天同事要从集群里下载分析。
这 300MB 不会「整包」塞进某一台机器。它会:
- 被切成 3 片(128 + 128 + 44 MB)
- 每片复制 3 份,散落到不同 DataNode
- 下载时再 就近取片,在客户端拼回原样
这篇我们就跟着这个文件,走完 写 → 存 → 读 → 故障恢复 的全流程。
一、写文件:客户端 → NameNode → 流水线复制
1.1 写流程时序图
sequenceDiagram
participant C as Client
participant NN as NameNode
participant DN1 as DataNode 1
participant DN2 as DataNode 2
participant DN3 as DataNode 3
C->>NN: create("/user/logs/app.log")
NN->>NN: 检查权限、父目录存在
NN->>C: 返回可写 + Block 流水线节点 [DN1,DN2,DN3]
loop 每个 Block
C->>DN1: 发送 Block 数据
DN1->>DN1: 写本地磁盘
DN1->>DN2: 转发副本
DN2->>DN2: 写本地磁盘
DN2->>DN3: 转发副本
DN3->>DN3: 写本地磁盘
DN3-->>DN2: ACK
DN2-->>DN1: ACK
DN1-->>C: ACK
DN1->>NN: 块报告更新
end
C->>NN: close()
NN->>NN: 持久化元数据到 edits
1.2 关键细节
文件 vs Block
- 客户端眼里:一个 300MB 的
app.log - NameNode 眼里:3 个 Block ID + 各自 3 副本位置
- DataNode 眼里:6 个块文件(3 Block × 2 额外副本,简化理解)
写过程中 NameNode 不参与数据传输
NameNode 只负责 指路,数据流不经过 NameNode —— 否则 NameNode 会成为带宽瓶颈。
写失败怎么办?
流水线中任一环节失败,Client 会:
- 关闭当前流水线
- 向 NameNode 申请新的 DataNode
- 重新建立流水线,从未成功的 Block 重传
二、读文件:客户端 → NameNode → 就近读取
2.1 读流程
sequenceDiagram
participant C as Client
participant NN as NameNode
participant DN2 as DataNode 2<br/>(同机架,最近)
C->>NN: open("/user/logs/app.log")
NN->>C: 返回 Block 列表 + 各副本位置
loop 每个 Block
C->>C: 选择最近副本(DN2)
C->>DN2: readBlock(blockId)
DN2->>C: 返回 Block 数据
end
C->>NN: close()
2.2 副本选择策略
NameNode 返回某个 Block 的所有副本地址,Client 按优先级选择:
- 与 Client 同节点的副本(本地读,零网络)
- 与 Client 同机架的副本
- 跨机架副本
读的时候 只从一个副本读,不会同时读 3 份 —— 副本是为了容错,不是为了并行读(HDFS 读并行靠多个 Block 同时读)。
2.3 校验
每个 Block 有 校验和(Checksum)。Client 读完后验证,发现损坏会:
- 向 NameNode 报告 corrupt block
- 从另一个副本重读
- NameNode 安排删除坏块、补副本
三、故障处理:DataNode 宕机怎么办?
3.1 DataNode 心跳超时
DataNode 每 3 秒 向 NameNode 发心跳。超过 10 分钟 没心跳,NameNode 标记该节点 Dead。
3.2 自动恢复流程
flowchart TD
A[DataNode 宕机] --> B[NameNode 标记 Dead]
B --> C[该节点上的 Block 副本数 -1]
C --> D{副本数 < 目标值?}
D -->|是| E[选择新 DataNode]
E --> F[从存活副本复制 Block]
F --> G[副本数恢复为 3]
D -->|否| H[无需操作]
例子: Block X 原本在 DN1、DN2、DN3 各一份。DN3 挂了:
- Block X 副本数从 3 → 2
- NameNode 发现 under-replicated
- 选 DN4,从 DN1 或 DN2 复制一份到 DN4
- 副本数恢复 3
你在 NameNode Web UI 的 Overview 页可以看到 Number of Under-Replicated Blocks,集群自愈期间这个数字会 > 0,恢复完成后归零。
3.3 NameNode 挂了怎么办?
这是更严重的问题 —— 元数据丢失 = 整个 HDFS 找不到文件。
早期 Hadoop 1.x:Secondary NameNode 只帮忙合并 fsimage,不能自动切换。
Hadoop 2.x 起:NameNode HA(High Availability)
四、NameNode 高可用(HA):两个大脑的接力赛
flowchart LR
subgraph nn_ha [NameNode HA 集群]
ANN[Active NameNode<br/>处理所有请求]
SNN[Standby NameNode<br/>热备,同步元数据]
ZK[ZooKeeper / JournalNode<br/>协调选举 + 共享 edits]
end
Client --> ANN
ANN --> ZK
SNN --> ZK
ANN -.->|实时同步 edits| SNN
| 组件 | 作用 |
|---|---|
| Active NameNode | 处理客户端请求,写 edits |
| Standby NameNode | 读共享 edits,保持元数据同步 |
| JournalNode 集群 | 存储 edits 日志,供 Standby 拉取 |
| ZooKeeper | 故障检测 + 自动选举新 Active |
切换过程:
- Active NameNode 宕机
- ZooKeeper 检测到,选举 Standby 升主
- 新 Active 加载最新 fsimage + edits
- 客户端自动连到新 Active,几十秒内恢复
类比:两个值班的目录管理员,一个干活,一个盯日志,主班倒了副班立刻顶上。
Docker 单容器 harisekhon/hadoop:2.7 是伪分布式,不含 HA,但生产集群必备。
五、动手实验:Docker 跑 HDFS,上传第一个文件
5.1 环境准备
参考 环境搭建篇,确保容器运行:
1
2
docker ps | grep hdfs
# 浏览器可访问 http://localhost:50070
5.2 上传文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
docker exec -it hdfs bash
# 创建测试内容
cat > /tmp/hello-hdfs.txt << 'EOF'
Hello HDFS!
这是 Hadoop 入门系列第 3 篇的测试文件。
HDFS 会把文件切成 Block 分散存储。
EOF
# 创建 HDFS 目录
hdfs dfs -mkdir -p /user/corey
# 上传
hdfs dfs -put /tmp/hello-hdfs.txt /user/corey/
# 验证
hdfs dfs -ls /user/corey/
hdfs dfs -cat /user/corey/hello-hdfs.txt
预期输出:
1
2
3
4
5
6
Found 1 items
-rw-r--r-- 3 root supergroup 89 2026-06-08 10:00 /user/corey/hello-hdfs.txt
Hello HDFS!
这是 Hadoop 入门系列第 3 篇的测试文件。
HDFS 会把文件切成 Block 分散存储。
5.3 Web UI 验证
打开 http://localhost:50070/explorer.html#/user/corey,应能看到 hello-hdfs.txt。

上传后刷新 Explorer,文件会出现在对应目录。Overview 页 DFS Used 会从 28 KB 略微增加。
5.4 查看 Block 信息
1
hdfs fsck /user/corey/hello-hdfs.txt -files -blocks
小文件通常只占 1 个 Block。
本节小结
| 流程 | 核心要点 |
|---|---|
| 写 | Client → 流水线 DN1→DN2→DN3,NameNode 只指路 |
| 读 | Client 问 NameNode 拿 Block 位置,就近读一个副本 |
| DataNode 故障 | 心跳超时 → 副本不足 → 自动补副本 |
| NameNode HA | Active/Standby + JournalNode + ZK 自动切换 |
| 实验 | hdfs dfs -put 上传,explorer.html 可视化验证 |
下篇预告
第 4 篇:《MapReduce 思想——分而治之,还是 Google 那套老古董?》
- Map / Shuffle / Reduce 三阶段
- WordCount 完整 Java 代码 + 命令行运行
- 为什么 Spark 会取代 MapReduce
思考题
写文件时,如果流水线中 DN2 写成功了但 DN3 写失败,Client 会怎么做?最终 Block 的 3 副本如何保证一致?
提示:回顾「写失败关闭流水线 → 向 NameNode 申请新节点 → 重传」。
下一篇见 🐘