2、Hadoop生态基础:HDFS架构与原理、MapReduce计算模型、YARN资源调度
好,咱们正式开始Hadoop生态的三大核心组件。说实话,很多新手一上来就被HDFS、MapReduce、YARN这三个名字给唬住了。其实没那么玄乎。我当年刚接触大数据时,也觉得这玩意儿高深莫测,后来亲手搭过集群、调过参数、踩过坑,才真正理解它们各自扮演的角色。
简单来说:HDFS负责存,MapReduce负责算,YARN负责管。这三兄弟配合好了,你的离线批处理才能跑得稳、跑得快。
2.1 HDFS架构与原理:数据到底存在哪?
HDFS,全称Hadoop Distributed File System。说白了,就是把一堆普通服务器的硬盘拼起来,对外假装是一块超级大硬盘。你往里面写文件,它自动给你切成块,分散存到不同机器上。
核心设计思想: 移动计算比移动数据更划算。所以HDFS的设计目标就是让计算靠近数据,而不是把数据搬来搬去。
2.1.1 两大主角:NameNode 和 DataNode
HDFS采用的是经典的主从架构。主节点叫NameNode,从节点叫DataNode。
- NameNode(老大):负责管理文件系统的元数据。比如文件名、目录结构、每个文件被切成了多少块、每个块存在哪台机器上。它不存实际数据,只存“地图”。
- DataNode(小弟):真正存数据的地方。每个DataNode定期向NameNode汇报:“我还活着,我这里有这些数据块。”
我记得有一次线上集群出问题,客户端一直报文件找不到。排查了半天,发现是NameNode的元数据损坏了。嗯,这里要注意:NameNode是HDFS的单点故障,虽然现在有HA方案,但元数据的管理依然是重中之重。
2.1.2 数据块与副本机制
HDFS默认把文件切成128MB一块(老版本是64MB)。为什么要切这么大?你想想看,如果块太小,NameNode要管理的元数据就太多,内存扛不住。128MB是个经验值,我个人习惯在集群规模大、文件数量多时,适当调大到256MB。
每个数据块默认有3个副本。副本存放策略是这样的:
- 第一个副本放在客户端所在的节点(如果客户端不在集群内,就随机选一个)。
- 第二个副本放在与第一个不同机架的节点上。
- 第三个副本放在与第二个相同机架的不同节点上。
这样做的好处是:既保证了容错(一个机架断电了,数据还在),又兼顾了读性能(同机架内网络快)。
避坑指南: 我曾经遇到过因为副本数设置不合理导致磁盘爆满的情况。默认3副本,如果你有100TB数据,实际占用就是300TB。所以,如果数据有冷热之分,冷数据可以调低副本数到2甚至1,能省不少钱。
2.1.3 读写流程:数据是怎么流动的?
读文件流程:
- 客户端向NameNode请求文件元数据。
- NameNode返回该文件所有数据块的位置列表(按网络拓扑排序,离客户端近的排在前面)。
- 客户端直接与DataNode建立连接,并行读取数据块。
写文件流程:
- 客户端向NameNode请求创建文件。
- NameNode在元数据中注册该文件,返回可写的DataNode列表。
- 客户端将数据切分成packet(默认64KB),以管道方式写入第一个DataNode。
- 第一个DataNode将数据转发给第二个,第二个转发给第三个,形成复制管道。
- 所有副本写完后,客户端通知NameNode“写完了”。
这里有个细节:客户端是直接跟DataNode通信的,NameNode不参与数据传输。否则NameNode早就被流量打爆了。
2.2 MapReduce计算模型:分而治之的智慧
MapReduce是一种编程模型,不是具体的技术实现。它的核心思想就四个字:分而治之。
你想想看,如果让你统计1000本《红楼梦》里“宝玉”出现了多少次,你会怎么做?一个人肯定干不完。正确的做法是:把书分给100个人,每人统计10本,最后把100个人的结果汇总起来。这就是MapReduce。
2.2.1 Map阶段:拆解任务
Map阶段做的事情很简单:读取输入数据,解析成键值对,然后应用你写的map函数,输出新的键值对。
举个例子,经典的WordCount:
// 输入:一行文本
// 输出:(word, 1) 这样的键值对
public static class TokenizerMapper
extends Mapper<Object, Text, Text, IntWritable>{
private final static IntWritable one = new IntWritable(1);
private Text word = new Text();
public void map(Object key, Text value, Context context
) throws IOException, InterruptedException {
StringTokenizer itr = new StringTokenizer(value.toString());
while (itr.hasMoreTokens()) {
word.set(itr.nextToken());
context.write(word, one);
}
}
}
你看,map函数把每一行拆成单词,每个单词输出一个“1”。这就是最原始的数据打散过程。
2.2.2 Shuffle阶段:默默无闻的幕后英雄
Shuffle是MapReduce里最复杂、最耗时的阶段,也是调优的重点。它负责把map输出的数据,按照key进行分组,然后发送给对应的reduce任务。
具体流程包括:分区、排序、合并、归并。我当年调优时,80%的时间都花在Shuffle上。为什么?因为数据在网络间传输,磁盘I/O频繁,稍不注意就成瓶颈。
注意: Shuffle默认会进行排序。如果你的业务不需要排序,可以设置mapreduce.job.reduce.shuffle.merge.percent等参数来优化。我曾经有个任务,去掉不必要的排序后,性能提升了40%。
2.2.3 Reduce阶段:汇总结果
Reduce阶段接收Shuffle之后的数据,对每个key对应的value列表进行聚合计算。
// 输入:(word, [1, 1, 1, ...])
// 输出:(word, count)
public static class IntSumReducer
extends Reducer<Text, IntWritable, Text, IntWritable> {
private IntWritable result = new IntWritable();
public void reduce(Text key, Iterable<IntWritable> values,
Context context
) throws IOException, InterruptedException {
int sum = 0;
for (IntWritable val : values) {
sum += val.get();
}
result.set(sum);
context.write(key, result);
}
}
说白了,MapReduce就是:Map把数据打散,Reduce把数据聚合。中间Shuffle负责把相同key的数据送到同一个Reduce。
2.3 YARN资源调度:谁来决定任务跑在哪?
在Hadoop 1.x时代,MapReduce既要负责计算,又要负责资源管理,耦合太紧。到了2.x,YARN被剥离出来,专门做资源调度。现在YARN不仅能跑MapReduce,还能跑Spark、Flink等计算框架。
2.3.1 YARN的核心组件
| 组件 | 角色 | 职责 |
|---|---|---|
| ResourceManager | 全局老大 | 管理集群资源,接收客户端提交的任务,分配Container |
| NodeManager | 每台机器的管家 | 管理本节点的资源(CPU、内存),启动和监控Container |
| ApplicationMaster | 每个任务的经理 | 负责一个任务的整个生命周期,向RM申请资源,与NM协调 |
| Container | 资源容器 | 封装了CPU、内存等资源,任务实际运行的地方 |
我刚开始接触YARN时,总觉得ApplicationMaster是个多余的角色。后来才明白,每个任务都有自己的AM,相当于给每个任务配了一个项目经理,这样任务之间互不干扰,资源分配也更灵活。
2.3.2 任务提交与调度流程
一个MapReduce任务在YARN上的运行流程是这样的:
- 客户端向ResourceManager提交任务。
- RM在某个NodeManager上启动一个Container,运行ApplicationMaster。
- AM向RM注册,并申请运行Map和Reduce所需的Container。
- RM分配Container后,AM通知对应的NodeManager启动任务。
- 任务运行期间,AM持续监控进度,如果某个Container挂了,AM会重新申请。
- 任务完成后,AM向RM注销,释放所有资源。
个人经验: 我曾经遇到过一个任务总是卡在90%不动,排查后发现是Reduce阶段申请不到Container。原因是集群资源被其他任务占满了,而我的任务设置的Reduce数量又太多。后来我调整了yarn.scheduler.minimum-allocation-mb参数,并合理设置Reduce数量,问题就解决了。
2.3.3 三种调度器
YARN提供了三种调度策略,你可以根据业务场景选择:
- FIFO Scheduler(先进先出):简单粗暴,谁先来谁先跑。缺点是后面的任务要等前面的跑完,不适合多租户场景。
- Capacity Scheduler(容量调度):把集群分成多个队列,每个队列有固定的资源上限。适合多个部门共用集群的场景。
- Fair Scheduler(公平调度):所有任务动态分配资源,短任务优先。适合任务大小不一、追求响应速度的场景。
我个人在实际项目中用得最多的是Capacity Scheduler。为什么呢?因为公司里不同部门之间资源隔离的需求很明确,谁也别抢谁的。你想想看,如果数据组和算法组共用集群,没有资源隔离,算法组跑个模型就把数据组的ETL任务挤死了,那可就麻烦了。
小结
这一章我们聊了Hadoop生态的三大基石:
- HDFS:分布式文件系统,NameNode管元数据,DataNode存数据,副本机制保证可靠性。
- MapReduce:分而治之的计算模型,Map打散、Shuffle分组、Reduce聚合。
- YARN:资源调度框架,RM管全局、NM管单机、AM管任务、Container是资源单元。
下一章,我们会深入Hive,看看怎么用SQL来写MapReduce任务。说实话,自从有了Hive,我再也不想手写MapReduce了。但理解底层原理,对调优和排错至关重要。咱们下章见。