Hadoop三大核心组件详解:HDFS存储、YARN调度与MapReduce计算协同
Hadoop 这个词网上聊得最多的往往不是它本身而是围绕它的各种生态组件Hive、Spark、Flink、Kafka……很多新手顺着教程装完一套伪分布式环境跑通了一个 WordCount回头一看HDFS、YARN、MapReduce 这三个名字倒是眼熟但要问它们各自到底干了什么、为什么谁也不能少大概率会卡壳。这事我太有感触了。当年我刚接触分布式计算时最绕的一点就是明明是个“存储”怎么还有“NameNode”“DataNode”明明 MapReduce 是计算框架怎么又跑在 YARN 上YARN 自己不做计算可调度资源又离不了它。这三者的关系有点像一家公司的“仓库”“调度室”和“流水线”货放在仓库调度室安排人力和机器流水线按工序把货加工成结果。这篇就围绕 Hadoop 的三大核心组件把各自的职责、内部机制、协作方式掰开揉碎讲清楚适合正在学 Hadoop、准备面试、或者刚接触大数据但被各种概念绕晕的同学。1. 项目概述Hadoop 三大组件到底在解决什么问题1.1 从“单机瓶颈”到“三件套”的必然先退一步看问题。单台机器的磁盘空间、内存、CPU 都是有限的。一个几十 TB 的数据集塞进单机根本跑不动就算硬塞进去计算时面对几亿行数据单进程处理也得跑到天荒地老。分布式系统的基本思路很简单把数据拆开放到很多台机器上把计算也拆开让每台机器算自己那部分最后汇总结果。但“拆分”这件事听着容易落地全是麻烦数据放在哪、谁负责记目录、某台机器挂了怎么办、多个任务同时跑会不会争资源、中间数据如何传递……Hadoop 之所以能成为经典就是因为它用三个相对清晰的组件把这些麻烦逐一标准化了。HDFSHadoop Distributed FileSystem只管数据存储负责把大文件切块分散保存到集群的多台节点上。YARNYet Another Resource Negotiator只管资源管理和任务调度负责给计算任务分配 CPU、内存等资源。MapReduce基于 HDFS 数据、跑在 YARN 资源之上的计算编程模型负责把大任务拆成 Map 和 Reduce 两阶段完成。一句话概括HDFS 是“货架”YARN 是“调度台”MapReduce 是“加工程序”。三者通过接口和数据流动串起来构成了 Hadoop 最基础也最核心的闭环。1.2 为什么很多资料把 Hadoop 比作“操作系统”我后来带新人时喜欢让他们把 Hadoop 类比成一台“分布式电脑”。HDFS 对应磁盘YARN 对应操作系统里的“进程调度器 资源管理器”MapReduce 对应你写的一个个应用程序。这个类比并不严谨但对理解角色分工很有帮助。举个例子。你在单机上跑一个程序操作系统会给你分配内存、CPU 时间片程序读写磁盘文件时按路径查找。在 Hadoop 集群里YARN 负责给每个计算程序Application划分容器资源HDFS 则提供分布式的文件路径和读写能力MapReduce 则是那个被 YARN 拉起、读写 HDFS、执行具体逻辑的程序。搞懂这个分层再去看 Hadoop 生态里的 Spark、Flink就会容易很多——它们同样是“应用程序”只不过不是 MapReduce它们照样跑在 YARN 上、读写 HDFS。这也是 Hadoop 设计最聪明的一点把存储、资源、计算解耦任何计算框架只要能适配 YARN 的资源接口都能用同一套存储和调度底座。2. HDFS集群里那个看不见的“巨型网盘”2.1 主从架构NameNode 和 DataNodeHDFS 里最核心的两个角色NameNode主节点和 DataNode从节点。NameNode 管理整个文件系统的“元数据”也就是目录树、文件名、文件被切成了哪些块、每个块存放在哪几台 DataNode 上。它不存真正的文件内容只存“账本”。DataNode 才是真正存放数据块的地方响应客户端的读写请求定期向 NameNode 上报自己持有的块列表和状态。我常跟人解释NameNode 像图书馆的检索系统DataNode 是书架。你要借书先查检索系统知道书在哪个架位再去对应书架上拿书。检查系统本身不存书本内容但没有它你就不知道该去哪找。这套架构解决了一个核心问题元数据和数据的分离让数据可以水平扩展。往集群里加机器DataNode 增加存储空间变大NameNode 只需要记录新节点和块的映射关系即可。当然这也意味着 NameNode 是全集群里最不能挂的节点挂了你连文件在哪都不知道。所以生产环境一定会给它配 SecondaryNameNode、检查点机制甚至有 HA高可用双机热备。2.2 数据块和副本策略默认 128MB 与“3 副本”背后的门道HDFS 把一个大文件切分成固定大小的数据块默认块大小是 128MB老版本是 64MB。为什么块要这么大因为分布式系统里寻址开销和网络开销都很昂贵块太小会导致 NameNode 元数据爆炸块太多会让任务调度频繁块太大又会让 Map 任务处理时间过长并行度下降。128MB 是经过反复实践得出的平衡点。每个块默认保存 3 个副本。副本存放策略非常讲究第一个副本放在客户端所在节点如果客户端不在集群内则随机挑一个不太忙的节点第二个副本放在与第一个副本不同机架的节点上第三个副本放在与第二个副本同机架但不同节点的机器上。这种“111”摆放方式兼顾了可靠性与性能不同机架容灾避免整机架断电丢数据同机架内副本之间数据传输快修复副本时不用跨机架占用核心带宽。副本数也可以调整比如对非常重要的数据设 5 副本对临时中间数据设 1 副本一切以业务对可靠性的要求为准。2.3 一次完整的数据写入过程很多人对 HDFS 写文件有误解以为客户端直接往某个 DataNode 上传就行。实际上流程是这样的客户端调用create创建文件向 NameNode 发起写请求。NameNode 检查权限和目录返回允许写的响应并在元数据里注册文件。客户端开始写数据按块比如 128MB分包发送先请求 NameNode我要写第一个块该发给谁NameNode 按副本存放策略返回一个 DataNode 列表比如 dn1、dn2、dn3。客户端把数据包发给 dn1dn1 收到一份后同时转给 dn2dn2 再转给 dn3形成一条传输链。每个 DataNode 写完本地磁盘后会逐级返回确认客户端收到块的确认后再写下一个块。如果写入过程中某个 DataNode 故障客户端会收到异常并重新向 NameNode 申请新的节点已成功写入的数据块副本会保持正常下次后台由 NameNode 发起复制补副本。整个过程用户无感知只感受到写入速度可能变慢。读流程相对简单客户端先问 NameNode 文件分成哪些块、块在哪些 DataNode然后选择距离自己最近的节点读取同机架优先、同节点优先。这也是 Hadoop 推行的“数据本地性”计算的基础后面讲 MapReduce 时会再提到。2.4 实操维护里必看的 HDFS 命令很多入门教程只让你hdfs dfs -put和hdfs dfs -get但生产里我建议至少把这几个命令练熟# 查看目录和文件块分布 hdfs fsck /path/to/file -files -blocks -locations # 查看数据节点状态和容量 hdfs dfsadmin -report # 手动触发一次元数据检查点 hdfs dfsadmin -safemode enter hdfs dfsadmin -safemode leavefsck是我排障最常用的命令。当有同学问“为什么某个文件读得慢”跑一次 fsck 看块是否分布不均匀基本能定位是机架问题还是副本缺失问题。fsck输出的Replicated字段不是指副本数而是块状态健康与否这点很多人初看会误解。还有一点实操心得HDFS 不适合存大量小文件。小文件会造成 NameNode 内存膨胀因为每个文件、每个块都要在内存里占一条元数据记录。如果要处理海量小文件建议先把小文件合并成 SequenceFile 或 ORC 等格式再写进 HDFS。这个坑几乎每个新手都会踩等集群元数据上了几百万条NameNode 内存压力直接抬升GC 频繁整个集群响应变慢。3. YARN负责“资源调度”的幕后调度员3.1 YARN 的基本架构ResourceManager 与 NodeManagerYARN 的出现是为了解决 Hadoop 第一代里“资源管理和计算逻辑强耦合”的问题。第一代调度器 JobTracker 既要管资源又要管任务调度集群一大根本扛不住。YARN 把资源管理独立出来形成两个层级ResourceManagerRM全局唯一负责集群资源的统一管理和分配决定把资源给谁、给多少。NodeManagerNM每台计算节点一个负责管理本节点上的资源向 RM 心跳上报状态并启动、监控容器Container里的任务。一个计算任务比如 MapReduce 作业会先向 RM 申请一个 ApplicationMasterAMAM 负责这个作业的全生命周期管理继续向 RM 申请更多容器、分发任务到不同的 Container、监控任务进度、处理失败重试。任务结束后AM 注销并释放资源。我曾经用“租售平台”打比方RM 是房东每个 AM 是包租公Container 就是租出来的房间。MapReduce 作业来了以后包租公向房东租几间房按房间安排工人干活干完退房。这样设计的好处是每个作业都有自己独立的“包租公”上百个作业互不干扰。3.2 三种调度器FIFO、Capacity、FairYARN 支持三种资源调度策略选错会直接影响多租户场景下的集群利用率FIFO Scheduler最简单的先来先服务。作业按提交顺序排队前一个作业跑完后一个作业才能拿到资源。适合单用户、作业规模不大的环境多用户场景下一个大作业会把后面所有小作业饿死。Capacity Scheduler按队列划分资源百分比比如队列 A 占 50%、队列 B 占 30%、队列 C 占 20%。每个队列内部还是 FIFO队列之间互不影响。这是很多数仓集群的默认选择适合多团队共享集群。Fair Scheduler动态平衡资源所有作业按“缺多少补多少”的原则共享资源短作业能很快拿到资源启动长作业也不会被完全抢占。生产环境下我见得最多的还是 Capacity Scheduler。它的好处是资源配额明确队列入门门槛低且支持 ACL 权限控制可以做到“运维团队一个队列、数仓团队一个队列、临时分析一个队列”互不干扰。3.3 YARN 的容器资源到底怎么算很多同学跑 MapReduce 时被内存参数搞晕。YARN 容器分配的关键参数有四个参数默认值1.x 环境常见含义yarn.nodemanager.resource.memory-mb8192单个节点可用总内存yarn.scheduler.minimum-allocation-mb1024单容器最少内存yarn.scheduler.maximum-allocation-mb8192单容器最大内存mapreduce.map.memory.mb1024Map 任务容器内存mapreduce.reduce.memory.mb1024Reduce 任务容器内存容器不只要算基础内存还要考虑 JVM 堆外内存、脚本开销。我见过多次Container is running beyond physical memory limits报错就是因为内存参数给得太紧。实操里我都会把mapreduce.map.memory.mb稍微调大同时给容器加一定的-Xmx余量否则明明数据量不大作业却频繁被杀。YARN 的资源模型以内存为核心CPU 在旧版本里只做逻辑核配比不会真正做 CPU 隔离。新版本里支持 CGroup 隔离但默认配置下多个容器还是可能抢 CPU。对 CPU 敏感的任务不要只看内存记得确认yarn.nodemanager.resource.cpu-vcores的设置。3.4 作业是怎么被 YARN 拉起来的我用一个真实最简作业来说明hadoop jar my-job.jar com.example.WordCount /input /output这条命令提交后内部发生了什么客户端向 ResourceManager 提交作业RM 生成一个 Application ID。RM 在某一个 NodeManager 上分配一个容器用来启动 ApplicationMaster。ApplicationMaster 向 RM 注册并开始申请真正的 Map/Reduce 容器。RM 根据数据本地性尽量将容器分配到 HDFS 数据块所在的节点附近。AM 把作业的切片信息分配给各 Map 容器Map 任务开始读取 HDFS 上的数据。Map 阶段结束后中间结果落本地磁盘Shuffle 阶段将数据按 key 分区传给 Reduce 容器。Reduce 完成最终计算写结果到 HDFS。AM 向 RM 报告完成状态注销并释放所有容器。这个过程里YARN 做的不是“替我算”而是“替我安排好谁能算、在哪算、算完把资源还回来”。MapReduce 的计算逻辑始终在自己手里YARN 提供的是资源沙箱。4. MapReduce把大任务拆成“地图”和“归约”两段4.1 MapReduce 的核心抽象映射与归约MapReduce 名字本身就把原理说了一半Map映射和 Reduce归约。Map把输入数据逐条处理转换成一种或多种键值对形式。比如统计单词输入一行文本Map 输出“hello:1”“world:1”“hello:1”。Reduce把相同 key 的所有 value 汇总处理。比如把“hello:[1,1,1]”聚合成“hello:3”。这种抽象能解决问题前提是问题本身能拆成“可并行映射 可归并汇总”两个阶段。文本统计、日志聚合、倒排索引、排序归并等都是经典场景。至于复杂的迭代计算MapReduce 并不擅长因为每一步都要落盘效率低这也是后来 Spark 大行其道的原因之一。有一个关键点很多人忽略Map 的输出不是直接给 Reduce中间还有一次“Shuffle 洗牌”。Shuffle 过程是 MapReduce 性能的命门包括Map 端分区、排序、溢写、合并Reduce 端拉取、合并、再排序。常见调优手段把mapreduce.map.output.compress设为 true减少跨节点传输量调整mapreduce.reduce.shuffle.parallelcopies增加并发拉取线程。4.2 输入分片与“数据本地性”MapReduce 并行度从哪里来答案是输入分片InputSplit。分片是对输入文件的逻辑划分一个分片对应一个 Map 任务。默认情况下分片大小与 HDFS 块大小一致通常是 128MB。为什么要让分片和块大小一致为了数据本地性。如果分片横跨两个 HDFS 块那么多半要跨节点读数据网络开销立刻上来了。如果分片正好等于一个块Map 任务就可以调度到持有该块的 DataNode 上直接从本地磁盘读数据速度极快。这就是 HDFS 块设计为何直接影响到 MapReduce 性能的原因。实操里可以通过自定义InputFormat调整分片逻辑。比如对大量小文件我常用的办法是写一个 CombineFileInputFormat把小文件合并到一个分片中让一个 Map 处理多个小文件减少 Map 任务数量。否则 10000 个小文件就有 10000 个 Map 任务调度开销比计算还大。4.3 用 WordCount 拆解一次完整计算过程拿最经典的 WordCount 举例假设输入是两行文本hello hadoop hello world hadoop yarn hdfsMap 阶段第一行拆成单词输出(hello,1)(hadoop,1)(hello,1)(world,1)第二行输出(hadoop,1)(yarn,1)(hdfs,1)Map 端本地会做一个小的 combiner如果设置了的话把同一 Map 内相同 key 先合并第一行变成(hello,2)减少网络传输。Shuffle 阶段系统把相同 key 的键值对分到同一个 Reduce 分区。假设只有一个 Reduce所有 key 会集中到一起并按 key 排序。Reduce 阶段对每个 key 累加所有 value。最终输出hadoop 2 hdfs 1 hello 2 world 1 yarn 1整个过程虽然简单但落盘次数很多。Map 输出会写本地磁盘Shuffle 中间数据也会经过磁盘Reduce 最终结果再写 HDFS。这也是 MapReduce 的一大特点面向可靠性和简单性牺牲部分性能。理解这一点再去看 Spark 的“内存计算”就能明白它的优势是从何而来。4.4 自己写 MapReduce 时最容易踩的坑新手写 Mapper 和 Reducer 时常见问题其实不是语法而是逻辑边界搞不清把只能在 Reduce 里做的全局聚合放到了 Map 里。比如“求全局最大值”你在 Map 里只能算每个分片的最大值最终全局结论必须由 Reduce 汇总。改了输出类型却没改 Reducer 里的key、value泛型。MapReduce 的序列化要求必须是 Hadoop 自带的可写类型比如Text、LongWritable用 Java 原生String做 key 会直接报类型错误。认为 Map 的输出顺序有意义。实际上 Reduce 端只会保证 key 有序Map 端各分片之间没有全局顺序。如果业务要求全局有序要用 TotalOrderPartitioner不能指望默认分区。另外Reducer 数量可以手动设置比如-jobconf mapreduce.job.reduces4Reduce 数量不是越多越好。每个 Reduce 都会产生一个输出文件业务下游如果按目录读取文件数过多同样是个问题Reduce 过少又会导致数据倾斜、单点压力大。一般经验是Reduce 数量取集群可用 CPU 核数的 1/3 到 1/2或按输出数据量估算单文件控制在 128MB 到 512MB 之间比较舒适。5. 三者如何串联成一条完整的流水线5.1 一次查询背后的“协同工作”把三者的协作整合起来看一次 Hadoop 作业的完整生命周期是这样的数据存放文件通过 HDFS 客户端写入被切块并分散保存到多台 DataNode。作业提交用户提交 MapReduce 任务任务被提交到 YARN。资源申请YARN 的 ResourceManager 为任务启动 ApplicationMasterAM 继续申请容器资源。计算调度MapReduce 的每个 Map 容器尽可能调度到数据所在的 DataNode 节点上读取本地 HDFS 块。中间数据处理Map 结果经过 Shuffle传给 Reduce 容器。结果回写Reduce 把最终结果写回 HDFS新文件形成用户可以继续用 HDFS 命令读取。这个流程里HDFS 提供数据并不“主动”配合计算它只负责存储和提供访问接口YARN 也不知道计算逻辑是什么它只关心资源分配MapReduce 不知道自己处理的数据存在哪几个节点它只按接口向 NameNode 要文件信息、读数据、算结果。正因如此三者可以独立演进。比如你可以用 Spark 替代 MapReduceSpark 作业同样跑在 YARN 上、读 HDFS你也可以换掉 HDFS 用其他兼容文件系统只要提供相应的接口实现。这种降低耦合的设计是 Hadoop 生态长盛不衰的重要原因。5.2 数据倾斜场景下三者如何共同排查我在生产里排查最多的性能问题就是数据倾斜。表现是大部分 Map 或 Reduce 任务很快跑完个别任务跑特别久甚至直接 OOM。数据倾斜的根源通常出在 Map 端或 Shuffle 端某个 key 的 value 数量远大于其他 key比如用户维度表里“未知用户”占比极高。这个 key 被分到同一个 Reduce导致单一 Reduce 压力巨大。排查时需要三组件联动HDFS 侧看输出文件大小是否明显不均匀用hdfs fsck或hdfs dfs -du -h检查各分区文件大小。YARN 侧看 YARN 资源监控面板各容器内存使用是否差异巨大找出长时间运行的长尾容器。MapReduce 侧看任务的计数器和 Shuffle 指标确认是 Map 输出倾斜还是 Reduce 输入倾斜。解决手段包括加盐给 key 拼接随机后缀、两阶段聚合先局部聚合再全局聚合、把倾斜 key 单独处理、调整分区器自定义分区逻辑。这些方法不发散但需要先定位是哪一层的问题。三者的日志和指标正好各管一段配合起来才能快速缩小排查范围。5.3 性能调优时的“分层思维”日常调优我也习惯按组件逐层检查而不是一上来就改参数存储层HDFS检查文件的块数、小文件比例、副本是否缺失、数据是否均匀。数据分布不合理后续计算再优化也白搭。资源层YARN检查队列配置、容器大小、集群利用率和排队作业。资源不足时计算任务再多也只能等待。计算层MapReduce检查 Map/Reduce 数量、Shuffle 压缩、Combiner 设置。这层只影响单个作业效率。一个经典的低级问题是集群明明很空闲作业却在排队。此时问题往往不在地板计算而在 YARN 队列配置。有些默认队列只有一个最大可用资源受限怎么看都是集群“没资源”。调整队列资源比例立刻就能解决。这也是我特别强调“先分组件排查”的原因因为每个组件负责的责任边界完全不一样混在一起排查等于盲人摸象。6. 常见问题速查与排障心得6.1 高频问题对照表现象可能原因排查方向NameNode 无法启动元数据目录损坏 / 磁盘空间不足检查dfs.namenode.name.dir对应目录看日志中 Block 恢复情况DataNode 注册不上版本不一致 / namespace ID 不一致检查hdfs-site.xml的dfs.namenode.namespace配置文件只读不能写集群处于安全模式执行hdfs dfsadmin -safemode leave并检查块缺失比例容器被 kill报内存超限mapreduce.map.memory.mb设置过小结合yarn.nodemanager.vmem-pmem-ratio调大容器内存Reduce 任务极慢数据倾斜 / Reducer 数量不足查看任务日志中各 Reduce 处理数据量Map 任务数量爆炸小文件过多改用 CombineFileInputFormat或先合并小文件HDFS 写文件总是最后一步超时网络抖动机架间传输问题用dfsadmin -report看 datanode 状态关注写入链路中节点的 uptime这个表是我平时排障最常用的一份几乎每个场景都踩过。最经典的一个例子某次凌晨 ETL 作业大面积失败日志全指向“写文件超时”排查了半天才发现是某个 DataNode 所在服务器磁盘满了。DataNode 没挂但该节点上的磁盘无法写入新块NameNode 还在正常分配这个节点导致客户端写链路不断超时。遇到类似情况先看磁盘再看网络最后才查配置顺序很重要。6.2 一些也许不会写进文档的土经验第一NameNode 的内存不要省。默认情况下每个文件、每个块的元数据约占用 150-200 字节仅从经验看上千万个文件就能把 JVM 堆撑得很难受。给 NameNode 分配足够大的堆并且开启-XX:UseG1GC能稳定很多。但堆内存也不是越大越好GC 停顿同样会拖垮集群通常压到总内存的一半附近比较合适。第二HDFS 的块大小不是“配置完整就能改”的事。块大小是在文件写入时决定的存量文件的块大小不会根据配置变化。想验证新配置效果必须新建文件再写入。这个坑我踩过好多次改完块大小跑作业看不出变化就是因为读的全是旧文件。第三YARN 的 ApplicationMaster 本身也占用资源。作业规模特别多时比如几百上千条任务AM 容器会挤占大量内存。这时可以适当调大 AM 的最大资源或者设置 AM 的最大重试次数避免因 AM 频繁重启导致集群雪崩。第四MapReduce 的 Combiner 不是万能安全牌。Combiner 的输入输出类型必须和 Reducer 相同且函数必须满足交换律和结合律才有意义。曾经有人在 Combiner 里做求平均值的中间结果导致最终结果偏大。只有真正理解 Combiner 是“Map 端的局部 Reducer”才敢放心用。第五日志永远是第一手真相。HDFS 看NameNode和DataNode日志YARN 看ResourceManager和NodeManager日志MapReduce 看 ApplicationMaster 里各容器的 stdout/stderr。很多“灵异现象”其实只是日志没看全。比如我印象最深的一次任务一直显示 RUNNING但根本没有进度最后发现是 AM 线程卡在 DNS 反查上——这种问题不看日志根本猜不到。写在最后Hadoop 这套三组件设计放到今天的视角看确实不够“潮”但它的价值在于把分布式系统里最基础也是最难解决的三个问题——存储、资源、计算——用非常清晰的标准切分了出来。搞懂 HDFS、YARN、MapReduce 各司其职的边界就等于握住了大数据平台入门的钥匙。后续学 Spark、Flink、Hive无非是在不同层级上替换或优化其中的某一块。我个人的体会是真正让你“会了”的时刻不是你跑通了 WordCount而是遇到集群故障时你能第一眼判断出问题是在存储、资源还是计算这一层。希望这篇能帮你少走点弯路。