很多人把Hadoop三大组件背得滚瓜烂熟但真被问到从提交一个任务到看到结果HDFS、YARN、MapReduce到底各自干了什么一下子就卡壳了。这不怪你网上绝大多数教程都是把三个组件拆开讲每个都讲得头头是道但就是不讲它们怎么配合——就像分别介绍了采购部、生产部和销售部的工作职责却不提订单是怎么从客户手里一路变成货架上商品的。这篇文章我想换个讲法把三个组件放到一条完整的任务链里来讲。这样你不仅知道每个组件是什么还能真正理解它们为什么要被设计成三个独立的部分以及一个MapReduce任务从提交到结束数据是怎么流转的。适合正在学Hadoop的初学者也适合准备面试但总觉得差一口气的开发者哪怕你已经用过Hadoop再看一遍也大概率会有新的理解。1. Hadoop到底解决了什么问题从单机瓶颈到分布式协作先说个最朴素的问题为什么需要Hadoop这套东西理解了这个问题三大组件的分工逻辑就自然清楚了。1.1 单机时代的两个天花板假设你要处理1TB的日志文件一台普通服务器大概什么样CPU算力先不说单块硬盘的读写速度大约在100-200MB/s就算按150MB/s算读完1TB数据需要接近2小时。如果要做排序、聚合这种需要多轮扫描的计算时间还要翻好几倍。这是第一个天花板存储和IO的吞吐上限。你可以在单机上加硬盘但单台机器的磁盘槽位、供电、散热都有限加到一定程度就加不上去了。第二个天花板是计算能力。单颗CPU的算力增长这几年已经明显放缓而且单个任务的复杂度一旦上去再快的CPU也扛不住。更关键的是如果数据分散在多台机器上你只有一台算力强的机器数据还是得先拷过来才能算网络传输又成了瓶颈。这两个天花板指向同一个解决方案把数据分散存在多台机器上同时用多台机器的CPU一起算。听上去很简单但分散存和一起算各有各的坑。1.2 分布式带来的三个新问题把数据存到多台机器上之后你马上面临三个问题第一某台机器突然坏了怎么办单机时代硬盘坏了数据就没了分布式环境下100台机器里有一台坏掉几乎是常态如果不做冗余数据照样丢。第二文件到底存在哪台机器上用户访问一个文件时系统怎么知道去哪个节点找需要一个维护文件路径到物理位置映射的元数据服务。第三计算任务怎么切分和调度一个任务要跑在100台机器上谁负责把任务拆成小份谁决定哪份任务跑在哪个节点某个节点跑挂了任务要不要重试Hadoop的三个核心组件本质上就是这三个问题的答案HDFS负责解决存储和冗余YARN负责解决计算资源的统一管理和调度MapReduce负责把计算任务拆成可以在集群上并行跑的模型。三者各管一段合起来才是一个完整的分布式数据处理系统。1.3 为什么不把三件事做成一个东西这里有个值得琢磨的设计决策很多分布式系统比如早期的一些MPP数据库是把存储和计算紧耦合在一起的Hadoop却硬生生拆成了三层。原因其实很务实——解耦带来的灵活性远大于耦合带来的性能收益。存储和计算分离之后你可以对一份数据跑不同的计算框架。今天用MapReduce跑批处理明天换Spark跑内存计算后天再用Tez跑交互式查询数据都在HDFS里躺着不用重新拷贝。YARN作为统一的资源管理层让多种计算框架可以共享同一套集群资源。这个架构思路后来被证明极其成功现在几乎成了大数据平台的标配设计。2. HDFS把文件拆成块散落在集群里还不怕机器坏HDFS全称Hadoop Distributed File System是Hadoop的存储底座。它的目标只有一个用一堆廉价服务器给你提供一个容量巨大、且能容忍节点故障的分布式文件系统。2.1 核心思想块存储 副本冗余先理解两个最基础的概念。HDFS把文件切分成固定大小的数据块Block默认大小是128MB。比如一个300MB的文件会被切成3个块128MB、128MB、44MB。每个块独立存储在不同节点上这样文件就散落在集群各处。每个块默认存3个副本。三个副本不是放在同一台机器上而是遵循一个叫**机架感知Rack Awareness**的放置策略第一个副本放在客户端所在节点如果客户端不在集群内则随机选一个节点第二个副本放在不同机架的某个节点上第三个副本放在第二个副本同机架的另一台节点上。为什么要这么放简单说就是同时防机器故障和防机架故障。如果一台机器硬盘坏了你还有其他两个副本能顶上如果整个机架断电或交换机挂了至少还有一个副本在别的机架数据不会全丢。机架内副本放两个是因为同机架内节点间网络带宽大、延迟低日常的数据校验和修复操作成本会更低。提示这个副本放置策略是Hadoop 2.x之后的默认实现。早期版本是两个副本同机架、一个副本不同机架后来的策略在保证容错的前提下优化了写操作的网络开销。了解默认策略背后的权衡比死记结论有用得多。2.2 NameNode和DataNode管理者和仓库管理员HDFS是典型的主从架构节点分成两类角色。NameNode是元数据节点负责管理文件系统的命名空间——哪个文件在哪个目录、文件由哪些块组成、每个块在哪些DataNode上。它不存实际数据只存地图。实际读写数据时客户端先问NameNode要地图再拿着地址直接找DataNode读写。这里有个关键点实际数据流不经过NameNode。NameNode只负责告诉客户端块1在node1和node2上然后客户端直接跟node1、node2建立连接传输数据。这个设计保证了NameNode不会成为数据传输的瓶颈。DataNode是实际数据节点负责真正存储数据块、执行块的新增、删除、复制等操作。它会定期向NameNode发送心跳和块报告告诉NameNode我还活着我这边的块有哪些。如果某个DataNode长时间不发心跳NameNode就会把它标记为宕机并把它的副本补到其他节点上保持副本数始终满足配置要求。2.3 一次文件写入客户端经历了什么写文件比读文件复杂因为要保证三个副本的一致性和容错。流程大致是这样客户端调用create接口向NameNode发起创建文件的请求。NameNode检查文件路径是否存在、权限是否允许然后在命名空间里注册一个新文件返回一个可写的输出流。客户端开始写数据先把数据写入本地缓冲区攒够一个Block大小128MB后向NameNode申请可用的DataNode列表。NameNode根据副本策略返回一组DataNode比如3个节点按顺序排好。客户端把数据分批次推给第一个DataNode第一个节点收到一批就转发给第二个第二个转发给第三个——形成一条流水线Pipeline。每批数据都流水式地落盘到三个节点上。最后一个节点写完一批数据后逐级返回确认信息客户端收到确认后再推下一批。所有数据块写完后客户端通知NameNode关闭文件NameNode更新元数据。这个Pipeline设计省了什么呢如果客户端分别给三个节点各传一份数据网络开销是3份用流水线方式客户端只需要传1份由节点之间接力复制总网络开销依然是3份但客户端和NameNode的压力都小了很多。2.4 读文件的就近原则读文件相对简单客户端向NameNode请求文件清单拿到文件每个块的副本所在节点列表然后根据网络距离就近读取。HDFS会把节点按拓扑结构计算距离——本机距离为0同机架内距离为2跨机架距离为4——优先选择距离最近的副本。这个就近读原则直接决定了你的任务跑得快不快。如果计算任务被调度到了存有数据的节点上数据本地读取速度极快如果调度到了别的节点就要通过网络拉数据慢一个数量级。后面讲MapReduce时你会看到数据本地性是任务调度的一个核心考量。2.5 实操中绕不开的坑HDFS用多了有几个问题几乎人人都会遇到。小文件问题。HDFS的块是128MB但如果你存的是几万个几十KB的小日志文件每个文件至少占一个BlockNameNode的元数据内存会暴涨同时读写效率极低。解决思路通常是先合并小文件或者用HBase这类面向小文件的存储系统。副本数不是越多越好。3副本是默认值但副本多了存储成本线性上升写入开销也随之增加。冷数据完全可以设成2副本甚至1副本热数据可以临时调到3以上一切取决于你对数据丢失风险的承受度。NameNode单点问题。虽然Hadoop 2.x之后有了NameNode HA高可用用两个NameNode做Active/Standby切换但实际运维中NameNode的GC垃圾回收停顿、元数据目录损坏等问题依然需要高度重视。定期备份fsimage和edits日志比任何高可用配置都重要。3. YARN集群资源的调度中心和多租户管家YARN全称Yet Another Resource Negotiator是Hadoop 2.x引入的资源管理系统。它的出现解决了一个很实际的问题Hadoop 1.x时代计算框架和资源管理绑得太死想换一个计算引擎非常痛苦。3.1 为什么要单独搞一个资源管理层Hadoop 1.x里MapReduce的JobTracker既要负责任务调度又要管理集群资源还要做任务监控。这个设计在集群规模小的时候没问题但集群一旦超过几百台节点JobTracker就成了瓶颈和单点故障源。更麻烦的是如果我想在同一个集群上跑MapReduce和Spark两种任务资源怎么划分1.x做不到你得搭两套集群数据还得拷贝来拷贝去。YARN的定位就是把资源管理从计算框架中抽离出来变成一层独立的服务。MapReduce、Spark、Flink、Tez都只是YARN上的应用大家共用一套集群资源互不干扰又高效共享。3.2 YARN的三个核心角色YARN也是主从架构核心角色有三个。ResourceManagerRM全局资源管理者负责接收客户端的作业提交请求、为作业分配第一个容器用来跑ApplicationMaster、监控所有NodeManager的状态、并进行资源调度。RM是YARN的大脑通常配HA。NodeManagerNM每台机器上的代理负责启动和管理该机器上的容器Container监控容器使用的CPU、内存、磁盘等资源并定期向RM汇报本节点的资源使用情况。ApplicationMasterAM每个应用一个AM是应用级别的项目经理。它负责向RM申请所需的容器资源、在获得的容器里启动任务、监控任务进度、处理任务失败重试。MapReduce作业运行时会有一个MapReduce的AM来协调整个作业的所有Map和Reduce任务。这里的核心流程是客户端向RM提交作业。RM找到一个空闲节点启动一个容器在里面运行该作业的AM。AM启动后向RM提交资源申请说明我需要多少容器每个容器要多少CPU和内存。RM根据集群资源情况告诉AM哪些节点可以分配容器。AM拿着这些资源去向对应节点的NM请求启动容器任务在容器里运行。任务运行期间AM持续监控进度任务失败则重新申请资源重跑。3.3 三种调度器按场景选别盲目跟风YARN的RM内部集成了调度器Scheduler它只负责分配资源不负责管理任务状态。默认有三种调度器FIFO、Capacity和Fair。FIFO Scheduler先进先出把所有作业排成一个队列先来先服务。实现最简单但问题也明显——一个跑很久的大作业会阻塞后面所有小作业。只适合单用户测试环境生产环境基本没人用。Capacity Scheduler容量调度器把集群资源划分成多个队列比如开发队列、生产队列每个队列有资源上限队列内部按FIFO或优先级调度。优点是保证每个队列都有资源可用适合多团队共享集群的场景。这是当前Hadoop默认使用的调度器。Fair Scheduler公平调度器目标是让所有正在运行的作业平均分享集群资源。新作业提交后系统会动态地给运行中但资源占用较多的作业回收一部分资源给新作业。优点是响应时间稳定适合交互式查询和延迟敏感的场景。我实际用过Fair和Capacity。如果你手底下同时有批处理大作业和实时小查询两类任务Capacity 队列划分是更稳妥的选择——大作业再满也不会饿死小查询如果一天几十上百个作业、谁也别想独占资源Fair的体验会更平滑一些。记住调度器没有谁绝对好关键看你的作业形态和SLA要求。3.4 容器Container是什么YARN里的Container是一个逻辑概念它代表一段被隔离的资源集合——指定大小的内存、CPU核数可能还有磁盘和网络维度的限制。Container不是虚拟机它本质上是NodeManager在一个目录下创建的一个工作目录并设定资源限制通过cgroup等手段来运行一个进程。NodeManager启动Container时会做这样几件事在本地目录创建Container的工作目录存放运行日志和临时文件。通过操作系统用户启动进程并设置环境变量。通过cgroup限制进程对CPU、内存的使用上限。进程退出后清理工作目录回收资源。AM申请到的Container越多能并行跑的任务数就越多但单Container的CPU和内存配置要跟节点实际资源匹配。配置过高会导致容器启动失败配置过低会导致任务OOM内存溢出。这部分后面实战时再展开。4. MapReduce把复杂计算拆成分而治之的编程模型有了HDFS存数据、YARN管资源还缺一个东西怎么把计算逻辑描述出来并且能让集群并行执行。MapReduce就是Hadoop给出的答案它既是一个编程模型也是一套执行引擎。4.1 模型本质Map和Reduce两句话讲完MapReduce的核心思想可以用一个词概括分而治之。Map阶段把输入数据拆成一个个独立的记录对每条记录做同样的处理输出一批中间的键值对key-value。Reduce阶段把所有Map输出中key相同的值聚合在一起做归并计算得到最终结果。中间还有一层Shuffle它负责把Map输出的数据按照key进行排序、分区、分组然后传输给Reduce任务。Shuffle是MapReduce里最复杂、也最影响性能的一环。举个例子。统计一批文本文件里每个单词出现的次数Map阶段每读到一个单词就输出(单词, 1)Reduce阶段把相同单词的所有1加起来输出(单词, 总次数)。这个例子里Map输出和Reduce输入的key都是单词本身。4.2 深入Shuffle理解MapReduce性能的钥匙无数人对MapReduce的理解卡在Shuffle上其实Shuffle可以拆成Map端和Reduce端两段看。Map端Shuffle发生在Map任务运行过程中Map任务每条记录调用一次用户写的map函数输出的键值对先写入一个环形缓冲区默认100MB。缓冲区写到一定比例默认80%时后台线程开始把缓冲区内容分区排序——按分区号分分区内再按key排序。排序后的数据**溢写Spill**到本地磁盘生成一个临时文件。如果有多次溢写最后会把这些临时文件归并成一个大的有序文件同时生成一个索引文件记录每个分区在文件中的偏移量。Map任务结束后通知Reduce任务我的输出准备好了你可以来拉数据了。每个Map任务最终在本地磁盘留下一个分区有序的大文件分区的数量就是Reduce任务的数量。Reduce端Shuffle发生在Reduce任务启动后Reduce任务从各个Map任务的输出中拉取属于自己那个分区的数据所谓拉而不是推是为了避免Map程序需要知道Reduce位置的耦合。数据边拉边归并如果数据量超过内存阈值就溢写到磁盘。所有Map任务的数据都拉完并归并成一个有序的大文件后进入Reduce函数的输入阶段——按key分组相同key的所有value一起传给reduce函数处理。这里有一个非常反直觉的点Shuffle阶段不执行任何用户逻辑却占了作业执行时间的大半。一个典型的MapReduce作业60%-80%的时间都花在Shuffle的排序、传输、归并上。所以优化MapReduce作业本质上是优化Shuffle。4.3 一个WordCount作业的完整执行过程把前面的组件串起来看一个完整的WordCount作业从提交到结束到底经历了什么。假设集群有3台机器每个机器上都装了HDFS的DataNode和YARN的NodeManager输入文件在HDFS上有3个Block分布在3台机器上。**第一步作业提交。**客户端把作业的jar包、配置、输入输出路径提交给RMRM创建作业ID并把作业放在调度队列里。**第二步启动AM。**RM选择一个节点启动一个Container运行MapReduce的AM。AM向RM注册自己并开始申请资源。第三步分配Map任务。AM向RM申请3个Map任务的Container。RM根据数据本地性优先把Container分配到存放对应Block的3台机器上。这就是前面说的数据本地性——Map任务尽量跟数据待在同一个节点避免网络传输。**第四步执行Map任务。**每个Map任务读取对应的Block比如第0-128MB逐行解析对每个词输出(word, 1)。Map输出经过Shuffle写到本地磁盘。**第五步启动Reduce任务。**AM再申请Reduce任务的Container假定1个。Reduce任务从所有Map任务本地拉取自己分区的数据归并排序后对相同单词的1求和把结果写到HDFS的某个Block上写副本的过程跟前面讲的HDFS写入一样。**第六步作业完成。**Reduce任务写完结果后告诉AM我完成了AM向RM注销自己RM更新作业状态为SUCCEEDED。整个过程里HDFS管文件在哪、块在哪YARN管容器建在哪、资源给多少MapReduce管任务拆成几份、每份干什么、最后怎么合起来——三层各司其职界面清晰。5. 三个组件如何协作用任务调度视角拆解一次提交到完成前面讲的流程是宏观路径这一节我想再往深挖一层把协作中发现的问题和涉及的关键参数讲清楚。5.1 数据本地性是如何被保证的刚才提到RM会把Map任务优先分配到存有数据的节点但你真的去翻YARN源码或日志会发现这个优先有三个层级Node LocalContainer分配到了存有目标Block的那个节点数据直接读取最快。Rack LocalContainer分配到了同机架的其他节点数据要通过机架内交换机传输速度略慢。Off RackContainer分配到了别的机架数据要跨机架传输最慢。实际调度时AM会先等一会儿看能不能拿到Node Local的容器——这个等待时间由mapreduce.job.reduce.slowstart.completedmaps等参数控制。如果等不到就退而求其次拿Rack Local再不行才拿Off Rack。我见过有人把mapreduce.reduce.slowstart.completedmaps设成0.05即Map完成5%就开始启动Reduce本意是缩短作业总时间结果Reduce任务一启动就疯狂拉取还没完成的Map输出网络IO被打满Map任务反而变慢。真实集群上这个参数一般保持默认0.05不是不能用但要根据数据量和网络带宽调更保险的做法是等Map完成80%以上再让Reduce启动。5.2 资源不足时的排队与抢占集群资源是有限的两个作业同时提交后面的就必须等。YARN的做法是作业提交后先进队列AM等RM分配第一个Container。这个等待时间取决于作业优先级、队列容量、当前资源使用情况。一个典型场景集群上有3个任务各申请了10个Container把资源占满了第4个作业提交后AM一直申请不到资源。这时如果你用的是Fair调度器新作业会在运行中的作业让步一部分资源后启动如果你用的是Capacity调度器且新作业的队列有预留容量也会有机会。所以生产环境建议按作业优先级给不同团队分配不同的队列容量别让一个团队的大作业占满全部资源。5.3 任务失败时谁来兜底分布式环境下任务失败是常态。HDFS的副本机制解决了数据不丢但计算任务失败由谁管Map或Reduce任务失败AM会重新申请一个Container重跑失败任务默认重试4次。如果某个节点的任务反复失败AM会把后续任务调度到别的节点并把该节点加入黑名单。AM本身失败RM会重新启动一个AM新AM从上次的检查点恢复前提是作业支持比如Spark on YARN支持AM重启作业继续跑。NodeManager失败NM长时间不心跳RM会把它标记为失效并把运行在它上面的所有Container标记为失败由AM重新调度。ResourceManager失败依赖YARN的HA机制Active RM宕机后Standby RM接管。中间会有一段恢复时间运行中的作业会受影响。这个谁失败、谁负责的层次划分是分布式系统的经典设计——每层只兜底自己能力范围内的故障不越级也不甩锅。6. MapReduce过时了吗它的劣势、适用场景与生态现状聊到这儿很多人会问现在Spark、Flink这么火MapReduce是不是已经没用了这个问题其实问错了方向。MapReduce的价值不在于它多快而在于它定义了分布式批处理的范式后来的Spark、Flink都受它启发。但作为具体实现它确实有硬伤。6.1 MapReduce的三宗罪第一中间结果落盘。Map输出必须先写本地磁盘Reduce拉取后再写一次磁盘两次落盘带来大量IO开销。Spark的核心改进之一就是把中间结果尽量留在内存。第二执行模型太死板。MapReduce把每个作业都强制拆成Map Shuffle Reduce三段但并不是所有计算都适合这个模型。比如复杂的多阶段迭代计算机器学习算法MapReduce每迭代一次就要重新读一次HDFS、写一次HDFS效率极低。第三延迟高。MapReduce是为离线批处理设计的一个作业分钟级启动、小时级运行很正常没法做秒级的交互式查询或实时流处理。6.2 但MapReduce远没有死掉换个角度看MapReduce至今仍有它的位置超大规模批处理仍然可靠。TB级以上数据的ETLMapReduce的稳定性和成熟度依然是第一梯队很多公司的离线数仓仍然跑在上面。作为学习分布式计算的教材。MapReduce的代码逻辑直白你写一个WordCount就能完全理解分而治之是怎么落地的。Spark的RDD、DataFrame封装程度高反而不容易看到底层的数据流转。兼容性。Hive on MapReduce、Pig等老一代工具仍然有存量用户升级迁移需要成本和风险。6.3 生态现状谁在替代MapReduceMapReduce的生态位正在被其他框架逐步替换这点不用回避Spark用内存计算大幅减少磁盘IO迭代计算场景比MapReduce快10倍以上。目前是离线批处理的事实标准。TezHive的默认执行引擎已经换成了Tez它的DAG有向无环图执行模型比MapReduce更灵活避免了多阶段作业的重复落盘。Flink流批一体实时场景首选。批处理性能也相当不错。但这不意味着你要跳过MapReduce直接学Spark。我反而建议先把MapReduce的Shuffle机制吃透因为Spark的Shuffle在原理上和MapReduce几乎一模一样——分区、排序、溢写、拉取、归并这些概念是贯通的。你理解了MapReduce的Shuffle学Spark的Shuffle基本是降维打击。7. 给初学者的实操建议从搭集群到跑通第一个作业最后分享一点实操层面的体会都是我从反复踩坑里总结出来的。7.1 学习路径怎么安排如果你完全没接触过Hadoop我的建议路径是先在单机伪分布式模式下跑通HDFS和YARN用start-dfs.sh和start-yarn.sh把进程拉起来用hdfs dfs -put上传一个文件再用hadoop jar跑一个自带的WordCount例子。不要一上来就追求三台真集群单机模式能让你把组件日志看明白这是最宝贵的学习资源。然后开始看日志。任务跑慢的时候去ResourceManager的UI页面看每个任务的执行情况点进ApplicationMaster的界面看Container分布再翻NodeManager的日志看GC和磁盘情况。日志比任何教程都诚实。再之后可以试着给集群多加一个节点观察HDFS的块是如何重新平衡的YARN的资源是怎么变多的MapReduce任务是否因为多了节点而变化。这个加节点然后观察的过程能帮你把三组件的关系彻底打通。7.2 常见参数调优的一句话心得dfs.blocksize默认128MB如果跑大批量顺序读的作业可以调大为256MB减少元数据压力小文件多的场景调小没有任何帮助应该先合并文件。mapreduce.map.memory.mb和mapreduce.reduce.memory.mb默认1GB左右要根据实际数据量调设太小会频繁OOM设太大会浪费集群资源且容器启动失败。稳妥的办法是先用小数据集跑一遍看监控里的内存峰值。yarn.nodemanager.resource.memory-mb每台节点分配给YARN的总内存。别把机器内存全分出去要给操作系统留至少2-4GB否则节点会直接卡死。7.3 最值得养成的思维习惯我觉得学Hadoop最有价值的收获不是会用几个命令而是建立一种**分层思考的分布式系统思维**。遇到一个大数据问题你自然会去问数据存哪里、怎么保证不丢计算拆成几份、跑在哪台机器哪个环节是瓶颈、能不能并行这套思维方式换到Spark、Flink、Kafka任何分布式系统上都适用。我在实际使用中还有一个体会不要急于上生产集群先在单机/小集群上把每个组件的日志看明白。很多人会跳过这一步直接租三台云服务器开始搭集群结果出了问题对着几百MB的日志一头雾水。花一周时间把单机版的HDFS读写过程、YARN的资源分配日志、MapReduce的Shuffle日志逐段读一遍后面带真实集群时会有底气很多。Hadoop这套东西说复杂也复杂说简单也简单——HDFS管存、YARN管资源、MapReduce管计算三者围着一个目标转让分散的机器像一台超级计算机一样工作。把这条主线抓住了剩下的都是细节。