
凌晨三点被告警电话叫醒主NameNode宕机备节点却迟迟没有自动接管。本来想着连上去执行一条hdfs haadmin -transitionToActive nn2手工切过去结果命令直接抛异常二次故障。这个场景干过Hadoop运维的人应该都不陌生——HA集群部署得很标准ZKFC也配了自动故障转移平时看着挺稳结果真到了要用的时候手工切换都切不过去。这篇文章会把这类失败问题的排查思路完整走一遍transitionToActive底层到底干了什么事、为什么它会失败、日志里的关键报错怎么解读、哪些情况可以加-force强切、切过去之后还要做什么验证。不管你是被生产事故按在地上摩擦的运维还是在搭HA环境时想搞明白原理的开发这篇都能派上用场。1. 什么情况下需要手工执行 transitionToActive1.1 自动故障转移失灵后的手工干预Hadoop HA集群正常运行时NameNode的主备切换由ZKFCDFSZKFailoverController自动完成。它同时盯着两个NameNode的健康状态通过ZooKeeper协调一旦主NameNode的进程挂掉或健康检查失败ZKFC会自动把备节点提升为active。这套机制解决的是“单一NameNode进程崩溃”这种最常见的故障。但实际情况远不止这一种ZKFC进程本身挂了自动转移直接失效ZooKeeper会话发生网络分区ZKFC短暂失联等恢复后发现角色状态已经错乱两个NameNode都处于standby这种情况下ZKFC不会自动去抢主因为没有任何一方主动发起选举做版本升级、配置变更、机房迁移时手动停掉主NameNode再手工把角色切回来。所有这些场景都需要管理员手动执行hdfs haadmin -transitionToActive来干预角色状态。问题在于很多团队只在部署测试时点过一次这个命令真到出故障时才发现一切都不像脚本里写得那么顺滑。1.2 常见的手工切换触发场景拿我实际遇到的场景举例。第一次踩这个坑是在做一次NameNode的滚动升级——两台机器计划先升级备节点再切主再升级原主节点。备节点升级完成之后我执行hdfs haadmin -transitionToActive nn2结果命令卡了一会儿直接报错日志里出现Unable to transition to active within ... ms。当时第一反应是懵的因为这台节点刚升级完进程都正常jps里NameNode和ZKFC都在跑。后来排查才发现问题出在edits log的同步滞后上——备节点升级重启后从JournalNode追日志需要时间而切换命令有超时限制追平之前直接判定失败。还有一种高频场景是机房断网恢复后ZKFC和ZooKeeper的会话断开重连两个NameNode虽然进程都在但角色状态要么都是standby要么出现一主一备的错乱。这时候手工切换就变成了唯一出路。所以别把transitionToActive想成一条“用一下就好”的运维命令它实际上是整个HA故障恢复链路里最后一道手工保险。理解它为什么失败必须先知道它内部做了什么。2. 一条切换命令背后的分布式协调机制2.1 命令执行时的内部动作拆解执行hdfs haadmin -transitionToActive nn2时命令本身不会直接操作NameNode的进程空间而是通过RPC调用目标NameNode上的一个接口要求它从standby状态转换为active状态。整个切换过程内部要完成几个关键步骤。第一步是前置状态校验。NameNode会检查自身HA状态、元数据加载情况、安全模式状态、本次转换涉及的epoch号代际编号是否合法。任何一个前置条件不满足切换直接中止。第二步是写入JournalNode事务日志。要让一个standby节点变成active必须先确保它读到了最新的edits日志并且能和JournalNode集群正常通信。QJM模式下NameNode需要向JournalNode集群发起一个新的写入事务拿到足够多的确认多数派才能认为自己有资格成为唯一的active写入者。第三步是与ZooKeeper协调。ZKFC在切换过程中会尝试获取或持有active锁。如果另一个节点还持有active锁或者ZooKeeper上锁信息异常切换会被拒绝。这也是防止两个NameNode同时写入同一份元数据的关键保障。第四步是启用ActiveNameNode的完整能力。切换成功后NameNode开始正式接收DataNode的块报告开放客户端读写通道启动edit log的流式推送等。我用一个不恰当的类比这个过程不像断电后按一下总开关那么简单更像热插拔一块正在被RAID卡管理的硬盘——不仅要让系统识别新硬盘还要保证旧硬盘彻底退出数据链路没有中断。2.2 依赖组件一旦异常会怎样理解了内部动作就能推演出失败模式。每一步都有一个或几个依赖组件内部步骤主要依赖组件依赖异常时的典型失败表现前置状态校验NameNode自身状态、元数据safe mode报错、namespace未初始化写入JournalNode事务JournalNode集群、edits日志目录Couldnt write to quorum journalZooKeeper锁协调ZooKeeper、ZKFC无法获取锁、连接超时启用active完整能力DataNode网络、RPC端口切换成功但DataNode不报告这也是为什么transitionToActive的报错五花八门——因为它不是本地操作而是牵涉到“NameNode JournalNode ZooKeeper ZKFC”四组组件的一套分布式协商流程。任何一环不稳命令就会失败。记住这个结论切换失败不是命令本身的问题而是它依赖的某个环节已经出了问题。所以排查的核心不是盯着命令看而是去看它沿途经过了哪些组件、哪个组件此时不健康。3. 失败排查链路按顺序检查状态、进程与日志3.1 第一板斧看清集群当前角色状态接到切换失败的反馈我强烈建议先别急着翻日志先把整个集群的角色状态抓出来看。这一步只需要几条命令信息密度却极高。在所有节点上依次执行hdfs haadmin -getServiceState nn1 hdfs haadmin -getServiceState nn2正常情况下应该一个是active一个是standby。如果两台都显示standby说明此前自动转移根本没有生效当前集群处于无主状态。如果两台都显示active说明已经发生脑裂split-brain这是最危险的状况需要立刻处理——先停掉其中一个NameNode进程绝对不要在双active状态下执行切换。再看进程状态确保NameNode和ZKFC都活着jps正常情况下每个NameNode节点上应该能看到NameNode和DFSZKFailoverController两个进程。如果ZKFC不在那就别急着切角色先把ZKFC拉起来再说。另外两台节点之间的网络连通性包括8020NameNode RPC、50470HTTPS、8485JournalNode RPC端口也要确认防火墙和ACL没有拦截。3.2 第二板斧盯住NameNode与ZKFC日志状态和进程看完接下来才是查日志。生产集群的NameNode日志一般位于$HADOOP_LOG_DIR下默认是$HADOOP_HOME/logs命名类似hadoop-hdfs-namenode-{hostname}.logNameNode主日志hadoop-hdfs-namenode-{hostname}.outNameNode标准输出hadoop-hdfs-zkfc-{hostname}.logZKFC日志hadoop-hdfs-journalnode-{hostname}.logJournalNode日志排查时先看目标节点你要切到active的那台的NameNode日志找切换时间点附近的关键报错。几个常见的高频错误我在下节细说这里先提一个容易被忽视的细节切换失败后ZKFC日志里往往比NameNode日志更早暴露真实原因。因为ZKFC负责和ZooKeeper通信、抢锁、发起fencing一旦锁获取失败NameNode那边只会收到一个笼统的“transition failed”而真正的原因在ZKFC日志里。所以顺序可以总结为先看目标节点NameNode日志再对照看ZKFC日志最后才轮到JournalNode日志。日志量大的时候用grep按时间窗口过滤或者按ERROR级别过滤效率高很多。3.3 第三板斧确认JournalNode与元数据同步情况JournalNode这块是很多人会漏掉的地方。举一个我处理过的案例某次磁盘空间告警后journalnode的edits目录所在磁盘满了standby节点无法继续从JournalNode拉取edits日志。这时候执行transitionToActive表面报错是“切换超时”实际原因是新active节点写入JournalNode时无法落盘。检查JournalNode可以从三方面入手用HTTP接口访问JournalNode的JMX指标查看EditsLogWritten、CurrentLagTxns等指标是否存在异常到JournalNode数据目录由dfs.journalnode.edits.dir配置默认类似/hadoop/journal/data确认磁盘空间是否充足直接用hdfs journalnode查看是否有JournalNode is not formatted之类提醒JournalNode的数据目录必须在第一次格式化后才会正常对外服务。还有一点新搭建HA环境时最容易踩坑两台NameNode的namespace ID不一致或者JournalNode没格式化就直接启动导致edits日志永远对不上。判断方法是看日志里有没有namespaceID mismatch或Storage directory ... is not formatted。这“三板斧”走完问题基本能定位到具体组件了。下面把最常见的失败原因按场景拆开讲对照着看会更加直观。4. 典型失败场景与修复操作对照4.1 安全模式挡住了切换报错特征NameNode日志提示NameNode is in safe mode或类似Cannot transition to active because the namespace is not yet fully initialized。原因NameNode启动后会先进入安全模式等待DataNode上报块信息直到满足安全退出条件dfs.namenode.safemode.threshold-pct阈值默认0.999即99.9%的块上报后才自动退出。在安全模式期间NameNode不允许写操作自然也不能接管active角色。如果切换命令恰好在这段时间执行会被直接拒绝。修复操作# 查看当前是否处于安全模式 hdfs dfsadmin -safemode get # 等待自动退出或者核实集群健康后手动退出 hdfs dfsadmin -safemode leave这里有个经验如果集群本身是正常的只是重启后短暂进入安全模式一般等一两分钟就会自动退出不急着手动leave。如果超过十几分钟还没退出就要检查是否有大量block缺失或者DataNode没有正常注册。这种情况下强制退出安全模式是治标不治本必须先解决DataNode注册问题。4.2 edits 日志落后导致切换失败报错特征NameNode日志出现Unable to read from ...、There have been N failed transactions或切换命令长时间无响应后报超时。原因切换前目标NameNode作为standby会一直从JournalNode读取edits并回放。如果它停机时间较长或者JournalNode积压了大量edits没同步standby需要时间追赶。而transitionToActive内部有超时限制迁移过程不能无限等待如果没追平就强行切换就会出现失败。还有一种隐蔽情况目标节点虽然重启过但配置的nameservice ID或存储目录不对导致它读到的JournalNode地址集群不匹配同步一直建立不起来。修复操作没有银弹核心是两句话先让standby通过JournalNode把所有edits追平再切换。可以观察NameNode日志中“Processing edit log”的进度或者在hdfs haadmin -checkHealth nn2返回正常后再执行切换。如果JournalNode上的edits日志本身损坏或缺失只能考虑从另一个NameNode的元数据导出并重新同步操作比较重生产环境务必先备份。4.3 ZKFC 进程异常或 ZooKeeper 会话丢失报错特征ZKFC日志出现ConnectionLossException、Session expired或Failed to create ephemeral node。原因ZKFC在ZooKeeper上维护一个临时节点ephemeral node来表示“本节点是active”。临时节点的特点是ZKFC与ZooKeeper之间的session一旦断开这个节点会被自动删除。可是在分布式网络分区场景下session的过期有延迟尤其是ZooKeeper的sessionTimeout配置比较长时旧active节点会暂时“占着茅坑”新节点拿不到锁。修复操作检查ZKFC进程是否存活如果挂了直接拉起# 在对应节点上 hdfs --daemon start zkfc如果ZKFC活着但session状态异常可以尝试重启ZKFC让它重新注册。重启之前最好jps确认没有多个ZKFC实例避免重复注册导致抢锁异常。如果重启ZKFC后依然拿不到锁去ZooKeeper里看一下锁节点的情况zkCli.sh -server zk1:2181 ls /hadoop-ha/mycluster get /hadoop-ha/mycluster/ActiveStandbyElectorLock把锁节点的持有者信息和当前集群状态做对照确认是不是旧active的会话没有及时过期。4.4 fencing 旧主失败报错特征日志出现Failover failed、Fencing failed或Unable to fence service by any configured method。原因在切换过程中新active节点必须确保旧active节点已经被“踢出局”——这一步叫fencing。默认配置下Hadoop会尝试SSH登录旧active节点执行命令杀掉NameNode进程。如果SSH免密失效、目标端口不通、或者旧节点上的进程状态异常fencing就会失败新节点为了安全起见会放弃切换避免双写。修复操作先检查fencing配置property namedfs.ha.fencing.methods/name valuesshfence/value /property property namedfs.ha.fencing.ssh.connect-timeout/name value30000/value /property property namedfs.ha.fencing.ssh.private-key-files/name value/home/hdfs/.ssh/id_rsa/value /property确认两个NameNode节点之间SSH免密是否正常手动ssh试一下。如果旧active机器已经完全宕机ping不通那fencing没必要执行因为旧的已经不在了但sshfence可能因为连不上而超时。可以适当调大dfs.ha.fencing.ssh.connect-timeout或在确认旧节点已宕机后直接考虑用带-force的方式处理。到这里已经把最常见的四类失败原因讲完了。每次排查我建议都按“安全模式→edits同步→ZKFC/锁→fencing”的顺序过一遍基本不会跑偏。5. force 强制切换的边界条件与切换后的健康检查5.1 -force 能解决的问题和不能解决的问题当常规切换反复失败很多人的第一反应是加-force强制切换hdfs haadmin -transitionToActive -force nn2这个参数确实能绕过一部分前置检查但它有严格的适用边界。用错地方后果可能比切换失败严重得多。-force适合的场景是确认旧active节点已经彻底失联或宕机但ZKFC的临时锁还没过期或者某节点状态半死不活常规切换因为超时等原因被拒绝。换句话说你能明确回答“旧主已经不在服务了”这个问题才允许强切。-force绝对不能解决且会放大问题的场景两个节点都活着但都有问题强切可能直接造成脑裂JournalNode磁盘满或损坏强切后新active写edits依然会失败元数据不一致强切后会继续往错误的状态上叠加错误。所以强切之前务必先回答三个问题旧主进程是否确定已死JournalNode集群是否健康ZooKeeper上的锁节点是否已经释放三个答案都是“是”再用-force。5.2 切换成功后的必须验证清单切换命令返回成功不代表集群真的恢复了。我以前接过一个案例命令是成功了但DataNode因为网络原因全部连不上新active整个集群处于“有主不能写”的僵尸状态。所以切换之后必须跑一轮健康检查。最基础的一组命令# 确认两个NameNode的角色符合预期 hdfs haadmin -getServiceState nn1 hdfs haadmin -getServiceState nn2 # 查看DataNode注册情况和块归属 hdfs dfsadmin -report # 上传一个小文件验证读写链路 hdfs dfs -put /etc/hosts /tmp/test_switch.txt hdfs dfs -cat /tmp/test_switch.txt # 检查文件系统是否有损坏块 hdfs fsck / -files -blocks一组验证走完读写链路、DataNode注册、block健康状态都覆盖到了。5.3 事后恢复与集群一致性确认新active正常干活之后不能把烂摊子扔在那。旧active那边如果进程还活着比如只是网络分区导致的切换必须把它降为standby或者直接重启进程恢复成干净的单主状态。两个NameNode都显示active的状态下后续的自动转移和所有涉及元数据的操作都是定时炸弹。还要回头检查这次切换的根源ZKFC为什么没有自动完成切换JournalNode为什么同步滞后SSH键盘信息为什么失效如果根因不解决下次还是会栽在同一处。我个人处理这类故障的心得是先把角色的“现场”完整留档再动手操作。虽然transitionToActive是一条运维常用命令但每次报错背后的环境差异、配置差异和组件健康状况都不同日志和状态输出才是唯一可信的判断依据。切忌一上来就杀进程、清目录、强制切换这些操作一旦在错误的时间点执行可能直接从“单点故障”升级成“集群不可用”。把上面的排查顺序多练几遍等下次半夜被叫醒你会比大多数人都更稳。