DD 数据开发学习站DATA ENGINEERING
模块 04 · 离线计算

Hadoop 生态

HDFS、YARN、MapReduce 的定位与流程,理清大数据组件地图。

难度 · 进阶 约 120 分钟 必修 练习 8 项 计划内
建议先学:进阶维度建模
本页目录 · 24 节

本章目标:能用自己的话讲清 HDFS 怎么存、YARN 怎么分配资源、MapReduce 怎么算,以及 Hadoop 生态里各组件的位置。 前置知识:会基本电脑操作;SQL 学过一点更好,没有也能读。 预计用时:2 小时。

1. 为什么需要它

数据开发面对的表,动辄几十 GB 到 TB。单机会撞上三堵墙:

  1. 存储墙:一块硬盘装不下,加硬盘也有上限。
  2. 计算墙:一个 CPU 核读 1 TB 文件,按 100 MB/s 估算也要接近 3 小时。
  3. 故障墙:机器越多,坏盘、宕机越常见,必须靠软件自动绕过。

分布式的基本思路是:把数据切成块放到多台机器;把计算拆成任务送到数据附近;机器坏了用副本继续算。它要回答四个问题:数据怎么切、任务怎么分、故障怎么办、机器之间怎么少搬数据。Hadoop 的三个核心组件就是这四个问题的工程答案:

HDFS       存:大文件切块,多副本
YARN       管:CPU/内存切成 Container
MapReduce  算:Map -> Shuffle -> Reduce

三者可以独立使用:Spark 可以跑在 YARN 上,数据仍然放 HDFS。把存储、资源、计算拆开,是 Hadoop 最重要的设计思想。

注意:不是所有数据都要上集群。几百万行、单机放得下时,DuckDB、MySQL 甚至 pandas 更快。Hadoop 解决的是“单机放不下、算不完”的问题。

2. 核心概念

2.1 HDFS:分布式文件系统

HDFS 是主从结构:

客户端 -> NameNode(目录树、文件到块、块到 DataNode 的映射)

               | 返回块位置
DataNode1[block1,block2]  DataNode2[block1,block3]  DataNode3[block2,block3]

NameNode 管元数据,不管文件内容:目录树、文件切成哪些块、每个块在哪几台 DataNode 上。元数据常驻内存,靠 edits log 和 fsimage 持久化。DataNode 存真正的块,定期向 NameNode 发心跳和块报告;节点失联后,NameNode 把其副本标记不可用并安排恢复。SecondaryNameNode 不是备份,它只定期合并 edits log 和 fsimage;真正的 NameNode 高可用要 Active/Standby 两个节点,配合 JournalNode 和 ZooKeeper 切换。

块(Block) 是存储单位,Hadoop 2.x 以后默认 128 MB。500 MB 文件会切成 128 + 128 + 128 + 116 MB 四块。块大是为了减少寻址开销和元数据条数,代价是最后一块可能利用率低。副本默认 3 份,机架感知下通常一份本机架、一份另一机架、第三份在第二份所在机架的另一节点,既防单机故障也防机架断电。

写流程:客户端向 NameNode 请求创建文件,拿到第一个块可写的 DataNode 列表;把数据切成 packet,写到第一台 DataNode,再转发给第二台、第三台形成管道;每台逐级确认,失败节点被剔除并补副本;文件关闭时持久化元数据。

读流程:客户端向 NameNode 请求块位置,拿到按网络距离排序的 DataNode 列表;优先读最近的副本并做校验和,副本损坏或节点超时就换下一个重试。

常用命令:

命令 作用
hdfs dfs -ls /path 列目录
hdfs dfs -mkdir -p /user/student/input 递归建目录
hdfs dfs -put words.txt /user/student/input/ 上传
hdfs dfs -get /user/student/output/part-r-00000 . 下载
hdfs dfs -cat /path/file 查看文本
hdfs dfs -rm -r /path 递归删除,默认不进回收站
hdfs dfsadmin -report 看集群容量和节点状态

HDFS 适合“一次写入、多次读取”的大文件,不适合大量小文件、频繁随机修改和低延迟点查。1 MB 和 1 GB 文件都可能占一个块,但前者的有效吞吐极低。

2.2 YARN:集群资源的管理者

YARN 不负责计算,只负责把 CPU 和内存分给应用。

客户端 -> ResourceManager(全局资源、队列、调度)

              | 分配 Container
        NodeManager(单机代理,启动和监控 Container)
              +-- Container:ApplicationMaster(申请资源、协调任务)
              +-- Container:Map/Reduce Task 或 Spark Executor
组件 职责
ResourceManager 全局调度器,掌握整个集群资源,按队列分配
NodeManager 每台机器上的代理,管理本机 Container,上报资源
ApplicationMaster 每个应用一个,向 RM 申请资源、向 NM 下任务、监控进度
Container 资源单位,包含内存和虚拟 CPU 核,不是虚拟机

一次提交:客户端把程序提交给 RM;RM 在某个 NM 上启动 AM;AM 向 RM 申请 Container;RM 分配后,AM 通知对应 NM 启动任务;任务向 AM 汇报进度,全部完成后 AM 注销、资源回收。

常见调度器:FIFO 先来先服务,简单但大任务会堵后面;Capacity Scheduler 把资源划成队列,每队有保底容量和上限,适合多部门共享,Hadoop 3 默认;Fair Scheduler 按应用动态平分资源,可抢占。

资源参数要对齐:yarn.nodemanager.resource.memory-mb 是单机可供 YARN 的总内存,yarn.scheduler.maximum-allocation-mb 是单 Container 上限。Spark 的 spark.executor.memory 只是堆内存,YARN 实际申请还要加堆外开销。

2.3 MapReduce:把计算拆成两段

用 WordCount 理解最直观。输入三行,执行过程:

输入分片 -> Map -> Shuffle(按 key 分组、排序)-> Reduce -> 输出
hello hadoop    (hello,1)     hello  -> [1,1]        hello 2
hello spark     (hadoop,1)    hadoop -> [1,1]        hadoop 2
hadoop spark    (spark,1)     spark  -> [1,1]        spark 2

Map 阶段:每个输入分片由一个 Map Task 处理,逐行拆词,输出 (word, 1)。Map 输出先写环形缓冲区,到阈值后溢写磁盘,溢写前按分区号排序。

Shuffle 阶段:Reduce Task 从所有 Map 输出里拉取属于自己分区的数据,归并排序,把相同 key 的 value 放到一起。这里包含大量磁盘读写、网络传输和序列化,是 MapReduce 最贵的阶段。

Reduce 阶段:对每个 key 的 value 列表聚合。WordCount 就是把一串 1 加起来,每个 Reduce 输出一个 part-r-00000 文件。

Combiner 是 Map 端的预聚合,可以先在本地把相同单词加起来,减少传给 Reduce 的数据。它只能用于满足结合律和交换律的操作,比如求和、最大值;直接算平均值会错,因为平均值不能再平均。Partitioner 决定一条 (key, value) 去哪个 Reduce,默认 hash(key) % reduce 数;某个 key 特别多时,数据会压到一个 Reduce,这就是数据倾斜。

MapReduce 慢,主要慢在:

  1. Map 输出和 Reduce 输入都落盘,Shuffle 反复读写磁盘;迭代算法每轮重来。
  2. 复杂任务拆成多个作业,中间结果也落 HDFS。
  3. 每个 Task 启动 JVM,任务多时启动开销明显。
  4. 只有 Map 和 Reduce 两种固定阶段,没有全局 DAG 优化;数据倾斜时少数 Reduce 拖住全作业。

这些正是 Spark 要解决的问题,见 Spark 基础。

2.4 Hadoop 生态:每个组件只解决一件事

组件 定位 解决什么问题 不适合做什么
Hive 基于 HDFS 的离线 SQL 引擎 用 SQL 查 PB 级数据,元数据放 Metastore 低延迟点查、频繁 update
HBase 基于 HDFS 的分布式 NoSQL 数据库 按 RowKey 随机读写、海量明细查询 多表关联分析、全表报表
Sqoop 关系库与 HDFS 的批量搬运工具 MySQL 表批量导入 HDFS/Hive 实时同步、复杂转换
Flume 日志采集代理 把日志文件稳定送到 HDFS/Kafka 复杂业务数据同步
ZooKeeper 分布式协调服务 选主、配置管理、分布式锁 存业务数据、当数据库用

容易混淆的点:

  • Hive 的表不是数据库表:一张表 = HDFS 上一个目录 + Metastore 里一份元数据。建表只是写元数据,导数往往只是把文件放进目录。分区表是目录套目录,查某天只扫对应目录,这叫分区裁剪。
  • HBase 不是 Hive 的替代品:它擅长按 RowKey 查一行或一小段,不擅长 GROUP BY、多表 JOIN 和全表分析。RowKey 设计决定查询性能。
  • Sqoop 已进入 Apache Attic(退休),新项目更多用 DataX、Flink CDC 或云厂商的数据集成服务,但老公司仍会问。
  • Flume 的核心是 Source、Channel、Sink:Source 收数据,Channel 暂存,Sink 送出。文件 Channel 能在重启后继续传,代价是吞吐下降。
  • ZooKeeper 不存大数据,它存少量协调信息;HBase、Kafka、Hadoop HA 都依赖它做选主和状态协调。

3. 动手示例

3.1 不装集群:用 Python 模拟 WordCount

保存为 scripts/mr_wordcount_demo.py 后运行,不需要 Hadoop:

# -*- coding: utf-8 -*-
from collections import defaultdict
lines = ["hello hadoop", "hello spark", "hadoop spark"]
mapped = [(w, 1) for line in lines for w in line.split()]      # Map
grouped = defaultdict(list)
for word, one in mapped:
    grouped[word].append(one)                                  # Shuffle
result = {w: sum(v) for w, v in grouped.items()}               # Reduce
for word in sorted(result):
    print(word, result[word])
python scripts/mr_wordcount_demo.py

输出:

hadoop 2
hello 2
spark 2

这段代码是单机的,但和 Hadoop 的差别只在“任务分到哪台机器”。Map 的输出结构、Shuffle 的分组、Reduce 的聚合逻辑完全一样。先把这个流程讲顺,再看真实集群,名词就不会悬空。

3.2 方案一:Docker 单节点(可选)

装了 Docker Desktop 和 WSL2 后,可以从 Docker Hub 找 Hadoop 单节点镜像,按镜像说明启动 NameNode、DataNode、ResourceManager、NodeManager,映射 9870(NameNode UI)和 8088(YARN UI),进容器执行 hdfs dfs -ls / 和 yarn application -list,再跑一次自带 WordCount。

注意:Hadoop 镜像的 JDK、版本和环境变量差异很大,命令以镜像文档为准。Docker 方案的价值是看到 Web UI,不是维护生产集群。Hadoop 2 的 NameNode UI 端口是 50070,别和 3.x 的 9870 混淆。

3.3 方案二:WSL2 伪分布式(可选,更接近真实)

Windows 原生跑 Hadoop 容易卡在权限和 winutils.exe 上,推荐在 WSL2 的 Ubuntu 里做。关键步骤如下,不要求背:

  1. 安装 WSL2、Ubuntu、Java 11 与 SSH:sudo apt update && sudo apt install -y openjdk-11-jdk ssh rsync;下载 Hadoop 3.3.6,解压后配置 JAVA_HOME、HADOOP_HOME 和 PATH。Hadoop 3.3 官方支持 Java 8/11,伪分布式就用 Java 11;跑 PySpark 时再切到 Java 17。
  2. 在 core-site.xml 设置 fs.defaultFS 为 hdfs://localhost:9000;在 hdfs-site.xml 把 dfs.replication 设为 1;在 mapred-site.xml 设置 mapreduce.framework.name=yarn;在 yarn-site.xml 开启 mapreduce_shuffle;在 hadoop-env.sh、yarn-env.sh 写明 JAVA_HOME。
  3. 配置本机免密 SSH,执行 ssh localhost 确认不用输密码。
  4. 第一次启动前格式化:hdfs namenode -format。只执行一次,重复格式化可能清空元数据。
  5. 启动:start-dfs.sh、start-yarn.sh。执行 jps,应看到 NameNode、DataNode、SecondaryNameNode、ResourceManager、NodeManager。
  6. 跑 WordCount:
hdfs dfs -mkdir -p /user/student/input
printf 'hello hadoop\nhello spark\nhadoop spark\n' > words.txt
hdfs dfs -put words.txt /user/student/input/
hadoop jar $HADOOP_HOME/share/hadoop/mapreduce/hadoop-mapreduce-examples-*.jar \
  wordcount /user/student/input /user/student/output
hdfs dfs -cat /user/student/output/part-r-00000

输出应与 Python 模拟一致。Web UI 在 localhost:9870 和 localhost:8088,能看到 DataNode 存活、副本数和任务历史,这一章的流程就算跑通。

3.4 知道即可的边界

必须会:说出 HDFS 的 NameNode、DataNode、块、副本;画出读写流程;说出 YARN 的 RM、NM、AM、Container;口述 Map、Shuffle、Reduce;说出 MapReduce 为什么慢;说出 Hive、HBase、Sqoop、Flume、ZooKeeper 的定位;记住常用 HDFS 命令。

知道即可,不要背配置:NameNode HA 的 JournalNode、ZKFC 细节;Kerberos 与多租户权限;机架拓扑脚本和跨机房容灾;生产 JVM 参数和 NameNode 堆内存计算;不同版本端口差异;自己搭高可用集群。

4. 常见坑

坑 现象 原因与处理
重复格式化 NameNode DataNode 起不来,clusterID 不一致 格式化会生成新 clusterID;删旧数据目录重格式化,或恢复原元数据
把 SecondaryNameNode 当备份 以为 NameNode 挂了能接管 它只做 checkpoint;高可用要用 Standby NameNode
大量小文件 任务启动慢、NameNode 内存涨 先合并小文件,再用 Parquet/ORC 列式存储
以为 HDFS 能改文件 追加或覆盖失败 HDFS 以一次写入为主,随机修改用 HBase、Iceberg 等
副本数设成 1 节点挂了数据丢失 测试环境可设 1,生产至少 3

5. 练习

练习 1:算块数和实际占用

一个 500 MB 文件,块大小 128 MB,副本数 3。它被切成几个块?集群里一共有几份块副本?按块大小粗略估算,占多少存储空间?

参考答案

500 / 128 = 3.9,向上取整是 4 个块:128、128、128、116 MB。每个块 3 个副本,共 4 × 3 = 12 份副本。粗略占用 500 MB × 3 = 1.5 GB。最后一块只有 116 MB,严格算略小于 1.5 GB;面试里说“约 3 倍原始大小”即可。块数向上取整,不要漏掉最后不满的块。

练习 2:写出上传、计算、查看、删除的命令

把本地 words.txt 上传到 /user/student/input/,用 Hadoop 自带示例跑 WordCount,输出到 /user/student/output,查看结果,最后删除输出目录。

参考答案
hdfs dfs -mkdir -p /user/student/input
hdfs dfs -put words.txt /user/student/input/
hadoop jar $HADOOP_HOME/share/hadoop/mapreduce/hadoop-mapreduce-examples-*.jar \
  wordcount /user/student/input /user/student/output
hdfs dfs -cat /user/student/output/part-r-00000
hdfs dfs -rm -r /user/student/output

注意:输出目录必须不存在,否则 WordCount 会报错。重跑前先删旧目录,或者换一个输出路径。

练习 3:手写 WordCount 的 Map 和 Reduce 输出

输入 a b a、b c、a c。写出 Map 输出、按 key 分组后的 Shuffle 结果,以及最终 Reduce 输出。

参考答案

Map 输出(顺序不重要):(a,1) (b,1) (a,1) (b,1) (c,1) (a,1) (c,1)。

Shuffle 后:a -> [1,1,1],b -> [1,1],c -> [1,1]。

Reduce 输出:a 3,b 2,c 2。Map 负责拆开打标,Shuffle 负责把相同 key 放到一起,Reduce 负责汇总列表。

练习 4:为什么小文件比大文件麻烦

有 1000 个 1 MB 的小文件,和 1 个 1 GB 的大文件,数据量相同。从 NameNode 元数据、块数量、任务数量三个角度解释:为什么小文件更麻烦?

参考答案

元数据与块:HDFS 为每个文件、每个块维护元数据。1000 个小文件至少 1000 个文件对象和 1000 个块;1 GB 大文件按 128 MB 只需 8 个块。

任务:计算框架通常按文件分片启动任务,1000 个小文件可能启动 1000 个任务,每个只读 1 MB,调度和 JVM 启动开销远大于计算本身。处理办法是合并成较大的 Parquet/ORC 文件,让每个文件接近 128–256 MB。

练习 5:DataNode 挂了,读写会怎样

副本数为 3,一个 DataNode 突然宕机。正在写的数据会怎样?已经写好的数据还能读吗?

参考答案

正在写:管道会感知失败,客户端剔除故障节点,用剩余副本继续写,并由 NameNode 安排补齐副本;已确认成功的 packet 不会丢。

已经写好:NameNode 收不到心跳后把该节点副本标记不可用,客户端改读其他副本;只要剩余副本数大于 0 就还能读,副本不足时会在其他节点补到 3 份。HDFS 的容错不是“等机器修好”,而是“用副本继续服务,再自动恢复副本数”。

6. 面试延伸

面试题:NameNode 和 SecondaryNameNode 有什么区别?

是什么:NameNode 管理命名空间和块映射;SecondaryNameNode 定期合并 edits log 和 fsimage,帮助缩短 NameNode 重启恢复时间。

解决什么问题:edits log 不断增长,重启要重放很久;checkpoint 把旧日志合并进新的 fsimage,控制恢复成本。

我的实操边界:我在本地伪分布式环境里看过两者进程和目录,理解 checkpoint;没有做过生产 NameNode HA 切换和元数据恢复。

面试题:HDFS 的写流程是怎样的?

是什么:客户端向 NameNode 申请创建文件,拿到块和 DataNode 列表后,通过 DataNode 管道逐包写入,逐级确认,最后关闭文件并持久化元数据。

解决什么问题:大文件切块、多副本并行写,用管道确认保证“写入成功”不是只写了一份;节点失败时可以剔除并补副本。

我的实操边界:我用伪分布式跑过上传和 WordCount,能画清楚客户端、NameNode、DataNode 三段关系;没有处理过生产环境的管道恢复和跨机架副本放置。

面试题:MapReduce 为什么慢?Spark 改进了什么?

是什么:MapReduce 用固定的 Map、Shuffle、Reduce 阶段,中间结果大量落盘;Spark 用 DAG 组织多个转换,尽量在内存中传递中间结果,并用统一的 DataFrame/SQL API 表达计算。

解决什么问题:MapReduce 在迭代计算、多阶段 ETL 和交互查询上磁盘开销大;Spark 减少不必要的落盘和作业串联,让复杂流水线在一次作业里完成。

我的实操边界:我用 Python 模拟过 WordCount,也了解 MapReduce 的 Shuffle 和 Combiner;Spark 只在本地模式跑过小数据,没有调过生产集群。面试时我会明确说“原理清楚,生产运维没做过”。

7. 本章验收

  • 能画出 HDFS 的 NameNode/DataNode 结构,并口述一次写流程和一次读流程
  • 能解释块、副本、机架感知分别解决什么问题
  • 能写出 6 条以上 HDFS 常用命令,并说明 -put、-get、-cat、-rm -r 的区别
  • 能说出 YARN 的 RM、NM、AM、Container 的职责
  • 能不看资料口述 WordCount 的 Map、Shuffle、Reduce 过程
  • 能说出 MapReduce 慢的至少 3 个原因,以及 Spark 的对应改进
  • 能说出 Hive、HBase、Sqoop、Flume、ZooKeeper 各自的定位和不适合的场景
  • 完成 5 道练习,其中至少 4 道能独立写出答案

下一章

Spark 基础:理解 Driver/Executor、RDD/DataFrame 和执行模型,并跑通第一个 PySpark 程序。

本章任务0 / 8
本页目录