资讯详情

HDFS高可用架构详解:NameNode HA原理、QJM机制与故障转移实战

📅 2026/10/17 23:20:10 | 华诺云谱 👁 阅读
HDFS高可用架构详解:NameNode HA原理、QJM机制与故障转移实战
凌晨两点监控短信把我震醒跑批同事在群里问作业全卡住了。我打开管理页面一看NameNode进程没了整个HDFS集群所有读写全部停摆。那半小时里我能做的只有等待——等运维手工拉起进程、等fsimageedits慢慢加载回内存、等客户端各种超时重试慢慢恢复。那次之后我做了个决定把HDFS的HAHigh Availability高可用从原理到配置到故障演练彻底搞明白而不是停留在听说过这个名字的层面。这篇笔记是Hadoop基础篇的第06篇主题很集中HDFS与Hadoop的HA。我会把HA架构里的几个组件——Active/Standby NameNode、JournalNode、ZooKeeper、ZKFC——各管什么事讲清楚再把QJM机制、自动故障转移、防脑裂的fencing原理拆开揉碎最后附一份可以直接照着抄的配置清单和故障演练方法。适合正在搭高可用集群、准备相关面试、或者搭完HA但心里没底的人。1. NameNode单点问题HA到底为了防什么1.1 NameNode是整个文件系统的目录柜先回忆一下HDFS的基础角色。DataNode负责真正存数据块但一个文件被切成了哪些块、每个块分布在哪些DataNode上、目录层级长什么样这些信息全部由NameNode管理它把整个文件系统的元数据保存在内存里。可以把它理解成一个图书管理员仓库DataNode里堆着一箱箱书但哪本书在哪个箱子的哪一层只有管理员手里的索引卡知道。你要借书必须先找管理员查索引。这个设计带来的直接问题就是管理员一旦出事整座图书馆就瘫痪了。放在HDFS里就是——NameNode进程挂了客户端找不到任何文件的位置所有读写请求全部失败。数据块明明还好好躺在DataNode上但因为没有索引别人根本不知道它们拼起来是一个完整的文件。这种数据还在但系统不可用的状态比磁盘直接损坏还难受。在HA出现之前运维面对NameNode故障基本是这么个流程重启进程然后等它把fsimage元数据快照和edits操作日志重新加载到内存。数据量大一点的集群这个过程十分钟起步期间整个集群完全不可用。更麻烦的是如果edits日志也损坏了能恢复到哪个时间点全靠运气。1.2 SecondaryNameNode不是Plan B很多人刚学Hadoop时会误解不是有SecondaryNameNode吗它不就是备份吗还真不是。SecondaryNameNode干的事情是周期性把NameNode的fsimage和edits合并成新的fsimage这确实是帮NameNode减负但它不接管服务。它手里那份元数据可能是几分钟甚至更早之前的等到NameNode故障时让它顶上来期间丢失的更新和大量未合并的edits照样要慢慢恢复。说得直白点它只是个定期整理档案的助理不是随时可以顶班的管理员。真正的高可用要解决两件事NameNode故障后另一个节点能快速接管服务时间控制在秒级到分钟级。两个节点必须拥有一致的元数据避免切换后出现新主节点不知道最近几分钟发生了什么的情况。这里顺便提一个容易混淆的概念HA解决的是可用性而HDFS的Federation联邦解决的是扩展性。联邦是把元数据空间拆成多个命名空间让多组NameNode各自分管一部分用来突破单台NameNode的内存瓶颈和吞吐瓶颈HA则是让一组NameNode主备搭档保证不中断。两者解决的不是一类问题可以单独上也可以叠加着上。基础篇里先别混。2. HA架构的角色分工Active、Standby、JournalNode、ZKFC各管什么2.1 整体长什么样先给一个最小化的生产推荐布局两台NameNode一台Active、一台Standby、三台JournalNode、三台ZooKeeper然后在两台NameNode各自所在机器上各跑一个ZKFCZKFailoverControllerZooKeeper故障转移控制器进程。DataNode保持原样同时向两个NameNode上报心跳和块报告。这个布局里有几个容易让人头晕的新角色我把它们拆开讲。Active NameNode对外提供读写服务接收DataNode心跳负责把每一次元数据变更比如创建目录、删除文件、新增块写成edits日志。Standby NameNode不对外服务但它一直在后台做两件事。第一持续从JournalNode读取Active写入的edits日志并实时合并到自己的内存元数据里保证自己随时掌握最新状态第二周期性地把内存元数据落盘成新的fsimage这就是原先SecondaryNameNode的活在HA架构里由Standby顺便干了省的再单独部署一台。JournalNode可以理解成一个日志同步中转站。Active把edits日志同时发给所有JournalNodeJournalNode负责落盘保存Standby再从里面读。为什么需要多个JournalNode因为单台仍然有单点风险多台用多数派投票才能保证日志不丢、不冲突。生产里通常是奇数台比如3台。ZooKeeper负责选主和锁。它维护一个临时节点Active持有这个锁Active故障后锁释放Standby竞争获取锁从而确定谁接任。它存在的意义是让谁是主这件事有一个所有节点都认账的公共裁决者。ZKFC每个NameNode节点各一个贴身监控本机NameNode的健康状态同时维护与ZooKeeper的会话。它发现本机NameNode不行了就放掉锁发现对端不行了就抢锁并触发自己这边的切换。2.2 热备与冷备的天壤之别搞明白Standby随时在线同步你就能理解为什么HA能实现秒级切换。非HA架构下重新拉起NameNode要重新读fsimage、回放所有edits这是一个冷启动过程。而HA里的Standby在故障发生前就已经把所有已提交的edits加载进了内存它差的只是一个转正指令所以切换动作本身非常快。这里要提醒一个容易误会的点Standby的内存元数据不一定是分秒不差的最新状态它读取日志有周期性延迟但正常情况下这个延迟只有几十到几百毫秒级别。故障导致切换时可能会有极短暂的时刻Standby还没有看到最后几条edits。这就是为什么QJM要求多数派确认——真正已提交的日志必然存在于多数派JournalNode上Standby总能从某台健康的JournalNode读到它们从而保证元数据不丢。这是HA设计的核心下一节细讲。2.3 为什么不用NFS方案早期版本的HA有一个更朴素的玩法在Active和Standby之间弄一个共享存储目录比如NFS挂载Active把edits写进去Standby从里面读。这个方案确实能用但问题很实在那个共享存储自己就成了新的单点。NFS服务挂了两个NameNode谁都读写不了edits照样全集群不可用而且NFS本身的高可用多路径、主备切换需要额外的存储设备和技术方案去保障复杂度反而更高。所以现在主流的部署方案几乎都是QJMQuorum Journal Manager仲裁日志管理器。它不依赖额外共享存储硬件用一组独立的JournalNode进程靠多数派写入成功才算提交这种分布式共识的思路把edits日志的可靠性和一致性做在了协议层。3. QJM机制拆解多数派日志与epoch如何守住一致性3.1 一次edits写入到底经历了什么QJM要解决的根本问题是Active写日志、Standby读日志怎么保证两边看到的日志是一模一样的并且不会出现两个人都以为自己是Active各写各的把元数据写岔了的情况。先看正常写入路径。Active NameNode每次元数据变更会生成一条edits记录然后通过QJM协议向所有JournalNode发起写请求。每台JournalNode收到后做顺序落盘并返回我写好了。Active只有收到多数派比如3台里至少2台的成功确认才认为这条edits真的提交成功了。注意只要多数派成功就够了某台JournalNode慢一点甚至暂时挂掉并不影响Active继续工作。这样设计的目的不难理解一个写操作只要被多数派JournalNode确认了那么就算个别JournalNode坏了整个系统中依然有多数派拿着这份日志。Standby去读日志时问了一圈总能在其中某个健康节点上找到最新数据。这比只有一份共享存储坏了就大家一起抓瞎扎实得多。3.2 epoch机制防止双写的一把锁QJM能防脑裂靠的不仅仅是多数派。设想一下旧Active因为网络抖动没死透但它还认为自己活着还在向JournalNode写日志新Active已经接管也在写日志。如果JournalNode来者不拒两边各写各的元数据立刻分叉神仙都救不回来。这里的关键机制叫epoch。可以把它理解成一个门禁卡版本号。每次NameNode发生切换新Active启动或者被切换为Active时都会生成一个新的epoch编号并且这个编号是递增的旧Active用的是小编号。Active在向JournalNode发起写日志请求时会带上自己的epochJournalNode只接受epoch不小于自己当前记录的写请求一旦发现某个请求的epoch偏小直接拒绝。打个比方公司换了一代门禁卡旧卡全部作废只有拿新卡的人能进机房哪怕旧卡的主人还在门口晃荡门也已经不认识他了。就算fencing没有把旧Active进程杀掉旧Active想继续写edits也写不进去因为它的epoch比JournalNode记录的版本低。这一道防线保证了同一个时间段最多只有一个真正能写日志的Active存在。3.3 JournalNode部署的取舍JournalNode本身不是什么重量级组件它不存fsimage只存增量edits日志磁盘空间消耗不大。但它对磁盘的稳定性和IO性能要求不低因为所有edits写入都是同步落盘磁盘坏了或者IO夯住了直接影响Active提交日志的性能。生产上我个人推荐3台性价比最高能容忍1台故障。如果条件允许5台也可以能容忍2台故障但成本和维护复杂度都上去了。特别不推荐2台——2台没有多数派的概念挂掉1台就永远凑不齐2/2的确认反而比1台更容易把系统锁死。JournalNode最好分散到不同机架、不同电源域上避免一个机架断电把仲裁方全带走。如果部署条件受限至少也要保证三台JournalNode不要在同一台物理机上。4. 自动故障转移流程ZK选主、fencing隔离与客户端重试4.1 从故障发生到新主上任完整链路把自动故障转移的链路拆开看每个环节都不复杂但串起来需要一条清晰的逻辑链。第一步健康监测。两台NameNode机器上的ZKFC进程按照设定周期持续对本机NameNode发健康检查RPC调用。只要连续几次失败或者超时ZKFC就判定本机NameNode不健康了。如果病人恰好是Active那么这台ZKFC会主动断开自己的ZooKeeper会话或者删除自己持有的锁节点相当于主动把Active的身份让出来。第二步锁竞争。Standby那边的ZKFC一直在监听这个锁节点。它发现锁节点消失了就知道主位空出来了于是尝试在ZooKeeper里创建这个临时锁节点。ZooKeeper保证这个创建是原子的谁创建成功谁就赢得了接管资格。如果两台Standby同时抢比如有三台NameNode配了双备用也只有一个能成功。第三步fencing。抢到锁的ZKFC不能立刻宣布自己这边是Active它必须先确认旧Active真的退干净了。因为旧Active可能只是网络分区进程还活着还在傻乎乎地对外宣称自己是主。ZKFC会执行配置好的fencing方法把旧Active强制隔离。默认的sshfence方法是SSH登录到旧Active的机器上用fuser命令杀掉NameNode进程也可以用shell脚本方式脚本里可以写任何你需要的隔离动作比如隔断网络、关掉进程、调用BMC远程管理口给服务器断电视环境而定。第四步晋升。fencing成功后ZKFC通过RPC把本机NameNode切换为Active状态。新Active开始对外服务时会在第一次写edits时带上更高的epochJournalNode随后会拒绝所有旧epoch的写请求。到这里新旧交替才算真正闭环。4.2 脑裂和fencing到底在防什么脑裂这个词在HA里经常被提到指的是系统里出现了两个节点都认为自己是Active的情况。为什么危险因为两边都会接收客户端请求、各自写各自的edits两份元数据再也无法对齐。哪怕设计再好只要旧Active进程还活着理论上就有脑裂的可能性。所以fencing不是可选项它是每一套自动故障转移都必须认真对待的环节。提醒一句如果你配置了sshfence一定要提前确认本机到旧Active机器的SSH免密登录是通的而且免密的账号有权限执行杀进程。很多人第一次演练切换失败十有八九是卡在这一步——新主那边锁也抢到了但杀不掉旧主的进程切换流程直接卡死报错。4.3 客户端怎么跟着切过去集群里跑着的作业不会自动知道NameNode换了地址它们靠的是配置里的failover proxy provider。客户端会配置整个nameservice的地址列表通过ConfiguredFailoverProxyProvider这个类在请求失败时自动按列表尝试下一个NameNode。相关参数控制着重试的耐心程度重试次数设得太少NameNode切换过程中客户端直接放弃报错设得太多节点一抖客户端就傻等半天。具体数值要看业务容忍度一般把连接超时和重试次数配到故障切换大约需要10秒这个量级上相应放大一些。这里还要多说一句Hadoop的高可用不止HDFS这一层。YARN的ResourceManager同样支持高可用架构上用的是ZooKeeper加状态存储逻辑和HDFS的HA是近亲。如果你搭了一套带计算任务的大数据平台只把HDFS做成高可用、RM还是单点那作业提交照样会断这点别忽略。5. 从配置到启动HA集群落地清单与常见坑5.1 核心配置项逐个过HA的配置项看着多但核心逻辑就那么几条告诉系统nameservice叫什么、两个NameNode各在哪、共享edits从哪儿读、ZK在哪儿、fencing怎么隔离。下面的配置集中在hdfs-site.xml和core-site.xml里示例里的机器名、路径按自己环境替换。hdfs-site.xml里最关键的一组property namedfs.nameservices/name valuemycluster/value /property property namedfs.ha.namenodes.mycluster/name valuenn1,nn2/value /property property namedfs.namenode.rpc-address.mycluster.nn1/name valuenode1:8020/value /property property namedfs.namenode.rpc-address.mycluster.nn2/name valuenode2:8020/value /property property namedfs.namenode.http-address.mycluster.nn1/name valuenode1:9870/value /property property namedfs.namenode.http-address.mycluster.nn2/name valuenode2:9870/value /property共享edits目录指向所有JournalNode写成qjournal协议property namedfs.namenode.shared.edits.dir/name valueqjournal://jn1:8485;jn2:8485;jn3:8485/mycluster/value /property property namedfs.journalnode.edits.dir/name value/data/hadoop/journalnode/value /property客户端自动故障转移和ZKFC相关的配置property namedfs.client.failover.proxy.provider.mycluster/name valueorg.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvider/value /property property namedfs.ha.automatic-failover.enabled/name valuetrue/value /property property namedfs.ha.fencing.methods/name valuesshfence/value /property property namedfs.ha.fencing.ssh.private-key-files/name value/home/hadoop/.ssh/id_rsa/value /property property namedfs.ha.fencing.ssh.connect-timeout/name value30000/value /propertycore-site.xml里配置ZooKeeper集群地址property nameha.zookeeper.quorum/name valuezk1:2181,zk2:2181,zk3:2181/value /property几个参数的含义可以对照下表快速理解配置项位置作用dfs.nameserviceshdfs-site.xml给整个HA逻辑集群起名客户端统一用这个名字访问dfs.ha.namenodes.nameservicehdfs-site.xml声明该nameservice下有几个NameNode iddfs.namenode.shared.edits.dirhdfs-site.xml指定QJM的地址Active写、Standby读都走这里dfs.journalnode.edits.dirhdfs-site.xmlJournalNode本地保存edits的路径各JN机器上配置dfs.ha.automatic-failover.enabledhdfs-site.xml开启自动故障转移不开就只能手动用haadmin切换ha.zookeeper.quorumcore-site.xmlZooKeeper集群地址ZKFC靠它选主dfs.ha.fencing.methodshdfs-site.xml隔离旧Active的方式sshfence或shelldfs.client.failover.proxy.providerhdfs-site.xml客户端自动切换NameNode的实现类5.2 首次初始化的启动顺序第一次搭建HA集群初始化顺序错了会走很多弯路。按这个顺序来基本不会出大问题先把ZooKeeper集群起好确认能正常选主随便建个临时节点测一下。在三台JournalNode机器上分别启动JournalNode进程hdfs --daemon start journalnode。在nn1这台机器上执行格式化hdfs namenode -format然后启动nn1的NameNode。到nn2机器上执行hdfs namenode -bootstrapStandby它会自动从nn1同步fsimage元数据。看到提示成功后启动nn2的NameNode。在其中一台NameNode机器上执行hdfs zkfc -formatZK初始化ZooKeeper里的HA状态这一步只需要做一次。在两台NameNode机器上分别启动ZKFC进程hdfs --daemon start zkfc。启动所有DataNode用hdfs haadmin -getAllServiceState查看当前各NameNode的状态。这里有一个从报错现场学到的教训-bootstrapStandby必须在nn1已经格式化并成功启动之后再做否则它连不上nn1拉取元数据会报连接拒绝。别图省事跳过。5.3 配置和初始化阶段最容易踩的坑这些坑我基本都在测试环境里亲自踩过一遍提前列出来帮你免掉一个名字服务下的NameNode id列表不要加空格。nn1,nn2是对的nn1, nn2在解析时会多出空格然后各种地址匹配不上错误信息还不直观。hdfs zkfc -formatZK执行一次就行。执行第二次会把ZooKeeper里现有状态清掉重来如果集群已经在运行可能引发不必要的切换。集群有存量数据时不要轻易对NameNode执行-format。HA初始化讲究一个真格式化一个bootstrapStandby两个都格式化等于同时从空元数据起步直接脑裂。Hadoop 3.x里老的hadoop-daemon.sh脚本已经废弃统一用hdfs --daemon否则会有各种脚本兼容性告警。JournalNode的数据目录、NameNode元数据目录都建议用独立磁盘别和系统盘、日志盘挤在一起。edits是同步落盘磁盘IO一旦被别的负载干扰整个集群的写入延迟都会跟着抖。6. 故障演练才有底气切换实测、运维命令与排错思路6.1 高可用不是配出来的是练出来的配置都搞定、状态显示正常之后很多人会觉得HA已经生效了。但真正到了凌晨三点告警响起时你指望的到底是配置还是平时练过的手感我强烈建议做一次有意识的故障演练最好在测试集群上先来生产集群挑业务低峰期演练前做好预案。演练动作按风险从低到高可以这么做在Active NameNode机器上执行kill -9杀掉Java进程模拟进程崩溃。把Active NameNode机器的网卡拔掉或者停掉网络服务模拟网络分区。多台JournalNode里停掉一台观察写入是否还能正常提交。每次演练结束后跑一遍hdfs haadmin -getAllServiceState确认状态从nn1:active / nn2:standby切换成了nn1:standby / nn2:active或者反过来。同时观察客户端写入是否中断。我们的目标是从故障发生到新主对外服务耗时控制在10秒左右。另外建议专门用一个脚本以固定间隔向HDFS写入一个带时间戳的小文件。故障演练前后查看文件记录的连续性就能直观判断切换过程丢没丢数据。这个脚本很简单但价值非常高平时也可以当探活脚本用。6.2 运维命里表常用操作速查HA相关的日常操作其实不多但每一条都要记准确命令作用hdfs haadmin -getAllServiceState查看所有NameNode的角色状态hdfs haadmin -transitionToActive nn1手动把nn1切换为Activehdfs haadmin -transitionToStandby nn2手动把nn2切换为Standbyhdfs --daemon start zkfc/stop zkfc启动/停止ZKFC进程hdfs --daemon start journalnode启动JournalNode进程注意手动切换时如果自动故障转移是开着的ZKFC会认为是异常状态然后把状态又争回来。这种手动操作触顶自动机制的场景最典型的错误就是你刚把nn1切成standby过几秒它又被ZKFC切回active看起来像配置出鬼了。其实不是是ZKFC认为当前主节点不健康按流程又重新选了一次主。所以生产环境手动变更状态前先想清楚自动机制会怎么反应。6.3 常见的异常症状和排查方向排错时先别慌大多数HA不工作的现场都跑不出这几类一个典型的症状是两台NameNode都显示standby没人当主。这种情况通常是ZooKeeper里的锁丢了两边ZKFC在竞争锁但谁都没胜出或者互相僵持。先去ZK里看/hadoop-ha/目录下的节点是否存在再翻ZKFC日志。ZKFC的日志在$HADOOP_LOG_DIR里文件名类似hadoop-hdfs-zkfc-*.log里面会直白地写HA State: ...和竞争锁的过程。另一种更危险的状态是两台都显示active。这说明fencing可能失效了旧主没有被真正隔离。马上检查JournalNode的日志看epoch有没有被新主刷新以及旧主的写请求有没有被拒绝。这不是看热闹的时候要立刻手动把其中一台降级别让两套元数据继续分叉。还有一种常见情况切换到是没有问题但切换时间特别长。依次排查三件事——ZKFC的会话超时是不是配得过大、sshfence的SSH免密通不通、JournalNode的IO响应是否正常。我遇到过一台JournalNode磁盘IO卡死导致整个日志提交变慢的场景表面上像切换慢实际是仲裁集群里有个节点拖后腿。6.4 日常巡检别只盯着进程HA集群最怕的不是进程挂而是看着在跑其实要出事了。我建议把下面几项纳入日常巡检脚本JournalNode的edits目录磁盘剩余空间和IO延迟。ZK的节点状态和会话数是否正常。两个NameNode的Last Checkpoint时间间隔Standby做checkpoint如果长期不成功占用的内存和磁盘会悄悄失控。hdfs haadmin -getAllServiceState的输出是否一直保持一主一备。还有个容易忽略的点给集群做好时钟同步。ZooKeeper的会话和JournalNode的日志校验都对时间敏感各节点时间差得离谱的时候会出现莫名其妙的超时和会话失效。最后提醒一句别的个人最大的体会是看完文档和真正跑通一次HA中间差着至少三个小时。建议你无论如何先在测试环境把所有角色搭起来然后认认真真做一次kill -9演练、一次断网演练、一次JournalNode宕机演练。等到你亲眼看到Standby在几秒内接替Active、客户端作业自动恢复时再谈我们集群是高可用的这句话底气才足。真正的高可用是故障发生后的冷静而不是纸面上的配置。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑