资讯详情

资讯详情

Hadoop核心节点与HDFS存储原理:从NameNode到数据读写全解析

1. 写在前面为什么“节点”和“HDFS”是绕不开的两块基石做大数据绕不开Hadoop。很多初学者一开始就扎进MapReduce或者Spark的代码里写几个WordCount就觉得自己入门了结果一到真正部署集群、调优性能、排查数据丢失问题的时候就彻底懵了。原因很简单底层那套分布式存储和调度机制没吃透而上层所有计算框架都是跑在这套机制之上的。这篇博客我想集中聊聊两件事Hadoop集群里的核心节点以及HDFS文件系统的运行原理。这两块是整个Hadoop体系的骨架。你知道了每个节点在集群里到底干了什么活、谁管谁、数据是怎么被拆散再拼回来的再去理解YARN调度、Spark的Shuffle、Hive的存储规划就完全是降维打击。这篇文章适合谁准备搭建或维护Hadoop集群的工程师、正在学大数据但一直停留在“名词解释”阶段的同学以及那些被面试官追问过“HDFS写一个文件数据到底经历了什么”的人。我不打算给你堆概念而是把每个节点、每个机制背后“为什么要这么设计”讲清楚。2. Hadoop集群里的核心节点到底都在干什么2.1 NameNode和DataNode一对“管台账”和“管货仓”的组合很多人第一次看到NameNode和DataNode会以为它俩是某种主从复制关系。其实完全不是它俩的分工是截然不同的。NameNode不存文件数据本身它存的是整个HDFS的元数据。什么叫元数据简单说就是“谁在哪”的目录信息一个文件名在哪个路径下、它被拆成了多少块、每个块存在了哪些DataNode上、每个块当前有几个副本、每个副本是否健康。你可以把它理解成仓库的台账管理员手里攥着一本总账本所有货在哪、进了多少、出了多少全记在账上但账本里不放真正的货。DataNode才是真正“存货”的地方。文件被拆成一个一个的block默认128MB一个这些block被分散存储在多个DataNode的本地磁盘上。DataNode启动后会周期性地向NameNode上报自己存了哪些块、块的大小、副本状态等信息这叫块报告Block Report。此外DataNode还要每3秒发送一次心跳告诉NameNode“我还活着”。这里有个很多人容易混淆的地方NameNode到底存不存block和DataNode的映射关系存的而且是在内存里动态维护的。在旧版本里这个映射不落盘所以NameNode一重启就得靠DataNode重新上报块信息来重建这份映射表。我是建议你把NameNode当成一个纯内存服务来理解它的性能瓶颈和调优思路全部基于这一点展开。2.2 SecondaryNameNode到底是不是NameNode的“备胎”这是我见过被误解最深的角色之一。从名字看SecondaryNameNode很像“第二个NameNode”很多人以为它是用来做热备的NameNode挂了它立刻顶上。这是完全错误的定位。SecondaryNameNode的工作是定期从NameNode拉取当前的元数据镜像文件FSImage和编辑日志EditLog在本地做合并生成一个新的FSImage后再推回NameNode同时清空旧的EditLog。这个动作在Hadoop里叫Checkpoint。它存在的意义是防止EditLog无限膨胀以及帮助NameNode在重启时能快速恢复元数据状态而不是当故障转移的备用节点。所以如果你在单节点伪分布式模式下看到SecondaryNameNode不要以为集群有了高可用。它顶多算一个“定期做账本归档”的辅助角色。真做到故障自动切换需要的是NameNode HA架构即Active/Standby两个NameNode配合JournalNodes这部分我在后面节点规划里会细说。2.3 ResourceManager和NodeManager集群资源的“房东”和“二房东”Hadoop 2.0之后MapReduce的计算框架和资源调度被拆出来独立成了YARN。ResourceManagerRM负责整个集群资源的统筹和调度NodeManagerNM负责单台机器的资源管理和具体任务容器的启动。这两者的关系也很有意思。RM不直接管任务它只管一个抽象概念容器Container也就是一台机器上被分配的CPU核数、内存大小等资源。NM向RM定时心跳把自己所在节点的可用资源情况上报上去。当你提交一个应用时RM会找到一台有资源的节点通知该节点上的NM启动一个ApplicationMasterAM由AM负责跟RM继续申请该应用的后续资源容器然后向各NM下发具体任务。我建议你把RM和NM的关系类比成“总房东”和“二房东”RM知道整栋楼哪间房空着、多大面积应用申请房间后RM指定某个二房东去具体开门、布置房间、给租客办入住。真正落地干活的是NM上面的容器进程。2.4 高可用场景下那几个“配角”不能被忽略生产上真正跑单NameNode的集群几乎没有因为单点故障风险太大。这里就引出了一堆配角角色。JournalNode是NameNode HA的关键组件我们可以把它理解成“元数据操作日志的共享存储地”。Active NameNode会把每次元数据修改操作写进EditLog同时推送给多个JournalNodeStandby NameNode则持续从JournalNode读取EditLog并重放让自己内存里的元数据状态始终跟Active保持一致。这里要重点留意JournalNode正常需要奇数台推荐3台或5台因为写入EditLog要求“多数派写入成功”才算提交这个原理跟ZooKeeper选主是一样的。还有ZKFCDFSZKFailoverController它更像一个“哨兵”监视本机NameNode的健康状态并跟ZooKeeper保持会话。如果Active NameNode发生故障ZKFC会通过ZooKeeper触发自动切换把Standby提升为Active。实际运维中我见过不少人在高可用配置里漏掉ZKFC或者把JournalNode当成可有可无的组件集群正常时一切正常一旦主NameNode宕机就会发现整个HDFS“卡死”Standby根本升不上来。原因多半是ZKFC没配好或者JournalNode故障了。所以我要强调如果是为了学原理那单NameNode没毛病如果是生产部署节点角色必须一次上齐。3. HDFS的设计哲学为什么文件要被“大卸八块”3.1 “块”和“副本”其实是在帮你不是在增加负担HDFS存储文件时会把文件拆成固定大小的块默认是128MB。可能有人一开始会觉得拆成那么多块岂不是更碎片化、更麻烦这个想法恰恰跟分布式存储的初衷搞反了。块机制最大的好处是让数据可以并行读写和分散存储。一个大文件拆成N个块这些块就能被分散到N个节点上读取时多个节点同时往客户端传数据整体吞吐量大幅提升。另一方面块这个粒度让系统可以做“冗余备份”每个块默认保存3份副本分别放在不同节点上任何一个节点磁盘坏了其他副本不耽误整体数据读取和服务。还有一个容易被忽略的好处块的大小天然决定了能管理的文件数量上限。NameNode的元数据是存在内存里的每个文件、目录、块都要占用大约150至200字节的内存。如果集群存的全是小文件比如几KB一个那NameNode内存大部分都花在了“目录和文件名”上真正用于块数据的元数据占比很低。大块机制本质上是在“用更少的元数据去管更大的数据”。所以块默认调到128MB甚至256MB不是为了难为你而是为了在元数据开销、磁盘寻道、网络传输之间找一个平衡点。3.2 元数据“镜像日志”模式答案全在崩溃恢复里HDFS的元数据管理策略一向被当作“容灾设计”的典型样本FSImage EditLog。FSImage是某一时刻HDFS元数据的一份完整快照或者说“历史账本的定妆照”。EditLog是自上次快照以来所有元数据操作的流水账。NameNode运行时会先把元数据变化写到EditLog写日志再更新内存状态。这样一旦NameNode进程崩溃内存里的最新状态丢了也不怕重启时只需要把FSImage载入内存然后按顺序把EditLog里的操作重放一遍就能恢复到崩溃前的状态。理解了这对组合你就能明白为什么SecondaryNameNode或HA模式下的Standby NameNode那么重要如果EditLog长期不合并日志文件会越来越大NameNode重启时的恢复时间就会长到不可接受。定期做Checkpoint本质上就是给“账本归档”让重启恢复时只载入一个相对较小的快照加一小段增量日志就够了。这里强烈建议你改一个习惯不要手动删除或者清零EditLog很多人在磁盘告警的时候会想当然去删NameNode目录里的edits文件结果就是元数据历史链断裂整个HDFS文件目录全部丢失。我见过不止一次这种事故而且几乎无法恢复。所以凡是涉及元数据文件的清理操作必须走官方命令或者程序化工具绝对不能手动rm。3.3 安全模式Safe Mode到底是“启动故障”还是“保护机制”HDFS启动时NameNode会先进入安全模式。在这个状态下NameNode只接受元数据查询不允许对文件进行写操作同时等待DataNode上报块信息检查哪些块副本数不足然后根据配置自动启动复制补副本。很多人第一次看到集群日志里的“Safe mode is ON”会以为集群出问题了。实际上这只是NameNode的自我保护机制。如果副本数不够它不会急着对外提供写服务而是等数据补全后自动退出安全模式。生产常用的操作是hdfs dfsadmin -safemode get查看状态确定真的异常卡住时才用hdfs dfsadmin -safemode leave手动退出。需要警惕的是如果集群出现大量节点宕机、大量副本丢失NameNode算比例之后发现可用副本远低于阈值它可能会迟迟无法退出安全模式。此时首先要做的是检查DataNode是否恢复上线而不是强行退出安全模式。强行退出只会让明明不健康的数据状态对外暴露本来还可以通过自动复制修复的数据如果直接写入覆盖大概率会加速损坏。4. HDFS写入和读取的完整流程实际工作原理全链路拆解4.1 写入一个文件客户端和节点如何配合先从目录树上说。客户端要向HDFS写入一个文件比如/data/logs/app.log大致要经历下面这些步骤。第一步客户端调用DistributedFileSystem.create()它会向NameNode发起RPC请求说明要在哪个路径创建文件。NameNode会检查这个路径的父目录是否存在、客户端是否有写权限、目标文件是否已经存在然后把这次创建动作记入EditLog并更新内存目录树。这一步通过后NameNode返回一个输出流对象给客户端意味着客户端可以真正开始往里写数据了。第二步真正写数据的时候客户端不是一口气把整个文件传给NameNode而是按块写入。每写一个块客户端会先向NameNode申请“往这个块放哪些DataNode”NameNode根据当前的副本放置策略返回一个DataNode列表默认是3个节点。第三步客户端把数据以数据包packet的形式沿管道Pipeline依次传给第一个DataNode第一个DataNode收到后一边持久化到本地磁盘一边把包传给第二个DataNode第二个收到后再传给第三个。整条链路是“接力”而不是“广播”。也就是说三个副本的数据都是从这个管道里流过去的客户端只发一份数据。第四步最后一个DataNode写完本地副本后会沿管道反向返回确认消息ACK从第三个节点逐级返回第一个节点最后回到客户端。客户端收到这个ACK后才算这个数据包写入完成。整个块的所有数据包都写入并确认后客户端再向NameNode汇报“这个块已经写完整了”。这里有一个初学者非常容易忽略的点客户端向DataNode管道写入时数据包并不是写一个确认一个的而是有多个数据包同时在管道里传输通过“滑动窗口”机制提高吞吐量。你可以理解为生产者消费者模式中的批量流水线第一批还在链路上跑的时候第二批已经出发了而不是等第一批完全落盘再发下一批。4.2 读文件的流程其实比写入简单不少读取流程比写入简单得多。客户端要读一个文件时先向NameNode发请求拿到目标文件对应的元数据信息有哪些块、每个块分布在哪些DataNode上、每个块的副本在哪里。然后客户端选择距离自己最近的DataNode去读取对应的块读完一个块再读下一个块。这里的“最近”在HDFS里有两种衡量维度一种是机架感知rack-aware即能不能优先读同机架的副本避免跨机架网络开销另一种是网络距离如果开启了相关特性客户端会更倾向于连延迟低的节点。如果客户端本身就在某个DataNode上而目标块恰好也有本地副本那就能实现“本地读取”速度是最快的。有一个需要重点说明的设计为什么不从NameNode直接读数据因为NameNode单节点带宽十分有限如果所有读请求都走NameNode它瞬间就会成为瓶颈。把数据通道和元数据通道分开正是HDFS能把吞吐做上去的原因之一。这也是我反复强调“NameNode只管台账不管货”的现实意义所在。4.3 副本放置策略不是随便放三份就完事HDFS默认副本数是3但这三份副本不是随便挑三个节点随便摆而是有一套机架感知策略第一份副本放在客户端所在的DataNode上第二份副本放在和第一个节点不同机架的节点上第三份副本放在跟第二个节点同一个机架、但不同机器的节点上。为什么这么放核心目标是在“容错能力”和“写入/读取开销”之间找平衡。第一份放本地写入时省去一次跨机架网络传输第二份放不同机架是为了防止整个机架断电或交换机故障导致数据全丢第三份放回第二份所在机架是为了在保证容错的同时减少跨机架读写的频率。实际生产里如果你有三台物理机在两三个机架上这个策略执行起来可能跟教科书不太一样因为所有节点可能在同一个机架上这时候副本的放置策略退化成“不同节点即可”。但如果你的集群跨了多个机架一定要正确配置机架拓扑脚本topology script否则所有副本可能都被当成本地机架的节点故障容错能力会大打折扣。5. 实际部署和运维中我反复踩过的那些坑5.1 节点角色规划小集群和大集群完全是两种思路我接触过不少人用一台8核16G的机器跑了一个“伪分布式”然后在上面跑了五六个组件内存直接拉满然后就得出结论“Hadoop真吃内存”。其实是你给了它太多角色。小规模集群比如3台到5台机器我建议这样规划NameNode和ResourceManager放在一台或者各自独立一台JournalNode部署在奇数台节点上DataNode和NodeManager尽可能铺满每台机器最大化利用存储和计算资源。这样的布局能较好地避免“管理节点争抢资源”。管理类角色通常内存消耗很大特别是NameNode的JVM堆要留给元数据足够的空间而数据节点要留内存给任务容器。大规模集群几十台以上就要严格区分管理节点和数据节点了。NameNode、ResourceManager、JournalNode、ZooKeeper这些角色专门放在管理节点上不做数据存储和计算DataNode和NodeManager只在数据节点上部署。这么做的好处是资源隔离控制面和数据面互不干扰。若是把管理角色和数据角色混在一台机器大任务跑起来时内存和CPU争抢很严重NameNode经常表现为Full GC频繁、响应超时排查起来非常痛苦。5.2 常见故障安全模式卡死、块复制卡住、磁盘写满这里整理几个我实际遇到过频次很高的故障场景做一个速查表故障现象常见根因处理方向NameNode一直处于安全模式大量DataNode离线或块副本缺失比例过高先恢复DataNode观察块复制进度不要强行退出块长时间处于复制中状态网络带宽不足、副本调度慢、源节点负载过高检查DataNode网络IO、限流配置必要时调整复制线程数DataNode磁盘写满后进程退出没有配置磁盘空间阈值告警或块分布不平衡启用磁盘均衡配置空间告警日常做rebalanceNameNode进程频繁Full GC元数据量过大堆内存不足增加NameNode堆内存或启用Federation分摊元数据压力客户端写入超时数据管道中某个DataNode磁盘性能差定位慢节点检查磁盘IO调整写入管道相关参数还有一个特别容易犯的低级错误手动修改了DataNode的存储目录路径但没有同步修改配置文件导致DataNode启动时找不到旧数据块目录启动后把所有块都当成新块重新格式化这个危险操作经常发生在“磁盘空间不足想换个挂载点”的场景。这里我建议凡是涉及DataNode数据目录变更严格按“停节点、改配置、启动节点、确认块报告”的流程来做绝不能图省事直接动目录。5.3 给新手的几个实操建议如果你刚开始搭集群我给你的第一条建议是先把网络和主机名规划好。所有节点之间要能互相通过主机名解析防火墙要放行Hadoop相关端口SSH免密要配好。很多人搭建集群后半天连不上、节点起不来90%出在主机名解析和防火墙配置上。第二条建议是务必设置好dfs.replication和块大小。学习阶段用默认参数没问题但生产环境要结合文件平均大小和数据重要性来调。如果一份文件只有几十MB你也按128MB块去存一个块只用了不到一半空间元数据开销白白浪费了如果文件很大也可以适当把块调到256MB但要注意块太大时MapReduce并行度会受影响启动多个Map任务的数量会减少。第三条建议是别忽略日志。Hadoop组件日志默认都记录了详细的过程和异常堆栈很多问题从日志里一眼就能看到答案。DataNode日志里会有块上报、副本复制、节点去活的记录NameNode日志里会有EditLog同步、安全模式切换的记录。排查问题的时候先把日志翻一遍往往比反复重启集群有效得多。6. 再聊聊我对这块内容学习顺序的建议很多人学Hadoop会卡在“节点角色这么多到底先学谁”的问题上。我个人的经验是先把NameNode和DataNode的交互读明白然后看懂一次HDFS写请求的完整链路再去碰YARN。因为几乎所有大数据组件最终都要落在“把数据写到HDFS、再从HDFS读出来”这两件事上你对这块的把握程度直接决定了后续学习Hive、Spark、Flink时的顺畅程度。我当初学的顺序是先在伪分布式上把HDFS跑起来然后故意制造故障观察现象。比如把DataNode进程杀掉看NameNode是不是会自动把副本复制到其他节点把副本数手动改成1再触发安全模式看块缺失告警。这种“主动搞破坏”的方法比单纯看文档印象深十倍。当然前提是你在测试环境操作生产集群千万别这么玩。回到本文最开始的问题为什么“节点”和“HDFS原理”是绕不开的基石因为你后面学的每个组件本质上都是在这些节点和这套文件系统之上做文章。分布式计算框架要访问数据就得知道数据在哪个节点要提交任务就得知道资源在哪里、怎么申请要保证数据不丢就得理解副本和机架策略。地基没打牢盖再高的楼都是危房。最后分享一个小习惯我每维护一个集群都会画一张“节点角色拓扑图”把每台机器的IP、主机名、部署角色、端口、数据目录、关键配置参数写清楚。这张图在外人看来可能很简陋但实际排查故障和做容量规划时比任何监控面板都管用。如果你还没有类似的东西我建议你从自己那套环境开始补上。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →