Hadoop 架构导论
HDFS 负责存 · YARN 负责调度 · MapReduce 负责算
Hadoop 架构导论¶
部署 Hadoop 之前,先花十几分钟建立起整体画面:Hadoop 由哪几块组成、每一块里谁说了算、它们怎么配合完成一次计算。
本篇只回答"是什么、谁是谁、怎么协作"。至于"怎么配置、怎么写代码、怎么调参数",请看每章末尾的阅读地图,那里列出了对应的专题文档。
本系列的实验环境统一为:机器
node0(192.168.179.100),Hadoop 版本3.1.3。
为什么需要分布式¶
单机遇到的两堵墙¶
- 存不下:单个业务的数据量很快超过一台机器的磁盘容量,而给单机加盘总有上限;
- 算不完:1TB 数据即便用最好的机器顺序读一遍也要几分钟,遇到多次迭代的计算就更慢。
单机纵向扩容(换更贵的机器)成本高且有天花板,于是自然的思路是:用一堆普通机器横向扩展。
横向扩展带来的新问题¶
把数据分散到多台机器后,必须有人解决三件事:
- 文件被切成放在不同机器上的碎片,谁来记住"哪个碎片在哪台机器"?
- 某台机器宕机,怎么保证数据不丢?
- 几十台机器同时干活,谁来分配 CPU 和内存,谁来盯着失败的任务?
这三个问题,正好对应 Hadoop 的三个核心组件 —— HDFS、MapReduce 与 YARN。
三个组件的分工¶
一张图看清三层¶
digraph hadoop_stack {
rankdir=TB;
fontname="Microsoft YaHei";
labelloc=b;
label="Hadoop 三层遵循同一套主从结构:一个主节点管多个从节点";
node [shape=box, style="rounded,filled", fontname="Microsoft YaHei", fontsize=11];
edge [fontname="Microsoft YaHei", fontsize=10, color="#5a6673"];
subgraph cluster_calc {
label="计算层:以应用的形式提交"; color="#c0504d"; fillcolor="#fdf2f2"; style="filled";
node [fillcolor="#fce4e4", color="#c0504d"];
mr [label="MapReduce"];
other [label="Spark / Flink 等"];
{rank=same; mr; other;}
}
subgraph cluster_yarn {
label="资源层 YARN:1 个主 + 3 个从"; color="#2e75b6"; fillcolor="#f2f7fc"; style="filled";
node [fillcolor="#e3eef8", color="#2e75b6"];
rm [label="ResourceManager\n全局资源分配(主)"];
nm1 [label="NodeManager"];
nm2 [label="NodeManager"];
nm3 [label="NodeManager"];
rm -> nm1; rm -> nm2; rm -> nm3;
{rank=same; nm1; nm2; nm3;}
}
subgraph cluster_store {
label="存储层 HDFS:1 个主 + 3 个从"; color="#548235"; fillcolor="#f3f8ef"; style="filled";
node [fillcolor="#e6f2dd", color="#548235"];
nn [label="NameNode\n保管元数据(主)"];
dn1 [label="DataNode"];
dn2 [label="DataNode"];
dn3 [label="DataNode"];
nn -> dn1; nn -> dn2; nn -> dn3;
{rank=same; dn1; dn2; dn3;}
}
mr -> rm [label="申请资源"];
other -> rm;
}
主从结构:1 个主 + 3 个从¶
图中的资源层与存储层,采用的是同一种组织方式 —— 主从(Master / Slave):
NameNode、ResourceManager:主。整个集群各一个,负责管理与调度,相当于"大脑";DataNode、NodeManager:从。每台工作机器上一个,负责真正干活,并定期向主汇报。
数量不是巧合:3 个从节点对应的正是本系列最后要搭建的三台工作机器(node1、node2、node3)。而在前面的伪分布式阶段,它们会被压缩到同一台 node0 上 —— 进程分布在哪台机器不重要,谁主谁从才重要。
特别注意:主节点宕机时,从节点不会自动顶替成主。这既是后面要讲 SecondaryNameNode 的原因,也是高可用(HA)方案存在的意义。
一句话记住各自的角色¶
| 组件 | 一句话定位 | 类比 |
|---|---|---|
| HDFS | 把大文件切块、存多份,分散保存在多台机器上 | 一个跨机器的网盘 |
| YARN | 统一管理全部机器的 CPU 与内存,谁要用就分给谁 | 集群的操作系统 |
| MapReduce | 一种编程模型:把计算拆成 Map 与 Reduce 两个阶段 | 跑在上面的应用程序 |
关键理解:这三层是可以分开的。存储、计算框架、资源管理各自独立后,同一套集群既能跑 MapReduce,也能跑其他计算框架。
HDFS:数据是怎么存的¶
块与副本¶
HDFS 不是把文件整份存起来,而是做两件事:
- 切块:文件按固定大小切成多个
Block(Hadoop 2.x/3.x 默认为128MB),分散存到不同机器; - 复制:每个块默认保存
3份副本,且尽量分布在不同机器上。
这样做的收益很直接:
- 文件大小不再受单机磁盘限制;
- 某台机器宕机,副本还在别的机器上,数据不会丢;
- 计算时可以并行读多个块。
这也解释了后面伪分布式时的一个坑:只有一台 DataNode 时,副本数必须改成
1,否则多出来的副本无处安放,HDFS 会持续告警。
NameNode 与 DataNode¶
::: cols 30:70
二者职责的分界线很重要:
NameNode:只管元数据(文件名、权限、块列表、块在哪些机器上),数据本身不经过它;DataNode:只管块本体,并定期向 NameNode 心跳汇报"我这儿有哪些块"。
::: split
digraph hdfs_arch {
rankdir=TB;
fontname="Microsoft YaHei";
labelloc=b;
label="HDFS 主从结构";
node [shape=box, style="rounded,filled", fontname="Microsoft YaHei", fontsize=10, margin="0.2,0.12"];
edge [fontname="Microsoft YaHei", fontsize=10, color="#5a6673", splines=ortho];
graph [nodesep=0.6, ranksep=0.8];
client [label="客户端\nhdfs dfs", fillcolor="#fff2cc", color="#bf9000"];
subgraph cluster_master {
label="主(NameNode)";
color="#548235";
fillcolor="#f3f8ef";
style="filled";
node [fillcolor="#e6f2dd", color="#548235"];
nn [label="NameNode\n元数据:\n文件名 → 块 → 所在机器"];
snn [label="SecondaryNameNode\n(辅助节点)\n合并 fsimage + edits"];
}
subgraph cluster_slaves {
label="从(DataNodes)";
color="#548235";
fillcolor="#f3f8ef";
style="filled";
node [fillcolor="#e6f2dd", color="#548235"];
dn1 [label="DataNode 1\n块 1\n块 2 副本\n块 3 副本"];
dn2 [label="DataNode 2\n块 2\n块 1 副本\n块 3 副本"];
dn3 [label="DataNode 3\n块 3\n块 1 副本\n块 2 副本"];
}
client -> nn [label="① 查位置"];
client -> dn1 [label="② 直连读写(示例)", constraint=false];
dn1 -> nn [label="心跳 / 块汇报"];
dn2 -> nn [label="心跳 / 块汇报"];
dn3 -> nn [label="心跳 / 块汇报"];
nn -> snn [label="检查点协作", dir=both, color="#548235"];
}
:::
SecondaryNameNode 不是备份(重点)¶
这是初学者最普遍的误解,请务必记住:
SecondaryNameNode只负责定期把 NameNode 的fsimage与edits合并成新的fsimage(即做检查点),减小 NameNode 重启时的恢复时间;- 它不能在 NameNode 宕机时顶替工作,算不上高可用;
- 真正的高可用方案是 HDFS HA(双 NameNode + JournalNode),属于后续进阶内容。
一次写、一次读¶
写入大致过程(了解即可):
- 客户端向 NameNode 申请写入权限与可用的 DataNode 列表;
- 客户端把数据直接写给第一个 DataNode,由它像管道一样接力复制到其余副本节点;
- 全部副本写完后,NameNode 记录下这次产生了哪些块。
读取大致过程:
- 客户端先问 NameNode:"这个文件有哪些块、分别在哪些机器上";
- 然后直接连对应的 DataNode 读数据,不再经过 NameNode。
关键点:NameNode 只在"寻址"时出现,真正的数据流不经过它,这是 HDFS 能支撑大规模并发的原因。
YARN:资源交给谁来分¶
四个角色¶
| 角色 | 级别 | 职责 |
|---|---|---|
| ResourceManager | 集群一个 | 全局资源管理与分配,接收作业提交 |
| NodeManager | 每台机器一个 | 管理本机资源,启停 Container 并汇报 |
| ApplicationMaster | 每个作业一个 | 向 RM 申请资源,监控本作业的所有任务 |
| Container | 运行时 | 资源的抽象单位,本质上是一个受限的进程(JVM) |
一句话概括层次:ResourceManager 管全集群 → NodeManager 管单机 → Container 是真正干活的那块资源 → ApplicationMaster 是某个作业的"项目经理"。
一次作业的生命周期¶
digraph yarn_flow {
rankdir=LR;
fontname="Microsoft YaHei";
labelloc=b;
label="YARN 上跑一个作业的过程";
node [shape=box, style="rounded,filled", fillcolor="#e3eef8", color="#2e75b6", fontname="Microsoft YaHei", fontsize=11];
edge [fontname="Microsoft YaHei", fontsize=10, color="#5a6673"];
client [label="Client\n提交作业"];
rm [label="ResourceManager"];
nm [label="NodeManager"];
am [label="ApplicationMaster\n本作业的项目经理"];
ct [label="Container\n真正执行任务"];
client -> rm [label="① 提交"];
rm -> nm [label="② 分配首个 Container"];
nm -> am [label="③ 启动 AM"];
am -> rm [label="④ 申请更多资源"];
rm -> nm [label="⑤ 分配 Container"];
nm -> ct [label="⑥ 运行任务"];
ct -> am [label="⑦ 汇报进度"];
}
流程要点:
- 作业提交后,RM 先分配一个 Container,用来启动这个作业专属的 ApplicationMaster;
- 之后的事情由 AM 去和 RM 谈判申请资源,作业之间互不干扰;
- 任务在 Container 里跑,跑完或失败都由 AM 负责处理,Container 随之释放。
为什么 YARN 要单独拆出来¶
早期 Hadoop 1.x 里,资源调度和 MapReduce 是绑在一起的(JobTracker 既调度资源又管 MR 任务),带来三个问题:
- 集群只能跑 MapReduce,无法同时跑别的计算框架;
JobTracker单点压力大,集群规模被限制在三四千节点;- 无法在同一集群上共存多个版本的计算框架。
拆出 YARN 之后:资源调度统一由 YARN 负责,MapReduce 只是"跑在 YARN 上的一个应用",Spark、Flink 同样可以。
这也解释了配置文件里为什么要有
mapreduce.framework.name=yarn:就是告诉 MapReduce"把作业交给 YARN 去调度"。
MapReduce:到底怎么算¶
MapReduce 把一次计算强行拆成两步,这也是它名字的由来:
- Map 阶段:对每条输入记录独立处理,输出一批键值对(
<单词, 1>); - Reduce 阶段:把相同 key 的数据聚到一起做汇总(
<单词, 总次数>)。
关键在于:Map 阶段各干各的、互不依赖,所以天然可以并行到多台机器上。
MapReduce词频统计¶
| node | data | Map | Reduce |
|---|---|---|---|
| node1 | a | ||
| node2 | b | ||
| node2 | b | ||
| node3 | c | ||
| node3 | c | ||
| node3 | c |
本质上等价于 SQL 的
SELECT key, SUM(1) GROUP BY key:Map 把每个 token 变成<k,1>,Shuffle 按 key 归并,Reduce 把同 key 的 value 加起来。
reducer 个数与输出文件¶
Reduce 任务的数量是可以配置的(mapreduce.job.reduces,默认为 1),它直接决定结果的样子:
- 有几个 Reducer,结果目录里就有几个
part-r-*文件; - 文件名里的
r代表 Reduce 阶段,00000是 Reduce 任务编号。
后面跑完 wordcount 看到
part-r-00000,就知道它来自"第 0 号 Reducer"。
三者怎么配合干完一件事¶
一次 wordcount 的完整链路¶
把上面三层串起来,hadoop jar ... wordcount /input /output 这条命令背后发生的是:
- 输入文件已切成块存在 HDFS 的若干 DataNode 上;
- 客户端把作业提交给 YARN 的 ResourceManager;
- RM 找 NodeManager 要 Container,先起 ApplicationMaster,再申请更多 Container 跑任务;
- 每个 MapReduce 任务从 HDFS 读自己负责的那个块;
- Map 的输出经过 Shuffle 整理后交给 Reduce;
- Reduce 的结果写回 HDFS,落成
part-r-00000。
一句话:HDFS 提供数据,YARN 提供算力,MapReduce 提供算法。
概念与你即将启动的进程(关键对照)¶
导论里讲的这些角色,就是接下来部署篇里 jps 看到的那些进程:
| 组件 | 角色 | jps 中的进程名 | 对外端口 |
|---|---|---|---|
| HDFS | NameNode | NameNode |
RPC 9000 / Web 9870 |
| HDFS | DataNode | DataNode |
Web 9864 |
| HDFS | SecondaryNameNode | SecondaryNameNode |
Web 9868 |
| YARN | ResourceManager | ResourceManager |
Web 8088 |
| YARN | NodeManager | NodeManager |
无独立 Web 端口 |
| YARN | ApplicationMaster | 作业运行时才出现 | — |
记住这张表,下一篇安装时"配什么地址、起哪些进程、打开哪个网页"就都能对上号了。
阅读地图¶
深入某一块该看哪篇¶
| 想了解的内容 | 去这里 |
|---|---|
| HDFS 命令、快照、纠删码、机架感知、JAVA API | Hadoop_HDFS |
| YARN 的调度器、容量/公平调度、队列配置 | Hadoop_YARN |
| 编写 MapReduce 程序、Shuffle 细节、分区与分组 | Hadoop_MapReduce |
| Combiner、Partitioner、序列化等进阶写法 | Hadoop_MapReduce_Combiner |
接下来做什么¶
- 回到硬件:先配好一台 Linux 虚机(用户、网络、JDK),见 Linux 系统配置篇;
- 装 Hadoop:从最简的单机模式开始跑第一个 wordcount,见 Hadoop 部署(一);
- 再过渡到伪分布式,亲手把上表中那五个进程在同一台机器上启动起来,见 Hadoop 部署(二)。
谢谢
下一篇:Hadoop单机部署