十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

HDFS高可用之JournalNode连接失败排查与自动故障转移配置实战

HDFS高可用之JournalNode连接失败排查与自动故障转移配置实战 说实话但凡维护过HDFS高可用集群的人看到“JournalNode连接失败”这几个字心里多少都会咯噔一下。我去年在生产环境就实打实踩过一次巡检时发现两个NameNode全部处于standby状态HDFS完全不可写跑在上面的Hive任务和定时调度瞬间全挂。查了半天日志里反复出现“JournalNode connection failed”相关的报错最后定位到根因是JournalNode所在节点的磁盘写满导致共享编辑日志写不进去Active NameNode在等待多数派确认时持续超时最后放弃active身份Standby又没能及时接管整个集群卡死在“双Standby”的僵尸状态。这篇文章就把这次故障的完整复盘、JournalNode在高可用架构里的作用、从手工恢复到自动故障转移的配置全过程都梳理一遍最后附上我踩过的坑和问题速查表。不管你是正在搭建HA集群还是已经在维护生产环境这篇应该都能帮你少走不少弯路。1. 故障复盘一次JournalNode连接失败引发的HDFS写入瘫痪1.1 现场现象双Standby的诡异状态先说当时的现场。集群是标准的HA架构两台NameNodenn1、nn2三个JournalNodejn1、jn2、jn3三台ZooKeeper后端挂了几百个DataNode规模不算小日常跑着数仓任务和离线计算。故障第一时间的表现非常典型客户端执行hdfs dfs -ls、hdfs dfs -put等命令全部超时报Cannot connect to the NameNode或Operation timed out。执行hdfs haadmin -getAllServiceState发现两个NameNode都是standby。nn1的日志里大量出现JournalNode connection failed、Operation category EDIT_LOG is not supported之类的异常。JournalNode所在节点的系统日志里出现磁盘空间告警还能看到文件句柄耗尽的报错。这个状态是最让人头疼的。正常情况下Active NameNode挂了之后Standby应该自动接管但这里两边都处于“观望”状态没有谁愿意当Active。原因就出在JournalNode这条命脉上——Active NameNode写编辑日志edit log需要多数派JournalNode确认当这个确认迟迟回不来Active会持续重试直到超时然后放弃active身份。而Standby看到的是“当前Active状态不明确”在没有确定旧Active被隔离之前它也不敢贸然上位于是整个集群就成了“双Standby”。1.2 影响评估为什么NameNode不可用等于整个HDFS不可用很多刚接触HDFS的同事会有一个疑问NameNode挂了数据不都还在DataNode上吗为什么读也读不了这里必须把HDFS的读写流程说清楚。写文件时客户端要先去Active NameNode申请文件块分配信息拿到“往哪几个DataNode写”的指令后才开始写数据写完之后DataNode要向NameNode汇报块信息。读文件也一样客户端需要从NameNode获取文件块的副本位置列表才能去对应的DataNode拉数据。整个过程里NameNode是一个绕不开的“中央路由表”它挂了整个集群就变成了“有数据但找不到路”的状态。我见过不少同学用hdfs dfs -put传文件失败时只盯着DataNode排查其实在HA架构里第一步必须确认NameNode状态。那次故障影响的不只是存储层Hive数仓、定时调度、数据同步任务全部连带失败因为它们的入口全部依赖HDFS客户端的NameNode连接。这也是为什么要花大力气做高可用并且认真对待JournalNode这种“看起来不起眼”的组件——它恰恰是整个高可用链路里最容易被忽视的薄弱点。2. HDFS高可用架构拆解JournalNode、ZKFC与ZooKeeper各司其职2.1 共享编辑日志JournalNode的定位与工作原理HDFS高可用的核心思路其实很朴素一份元数据两套NameNode进程。Active NameNode对外服务Standby NameNode持续同步元数据一旦Active故障Standby快速接管。关键问题是Standby怎么知道Active改了什么答案就是JournalNode。JournalNode是一个轻量级进程干的事其实非常聚焦存储和分发编辑日志。Active NameNode每做一次元数据操作创建文件、删除目录、修改副本数等都会把操作记录成一条edit log通过QJMQuorum Journal Manager协议并行发送给所有JournalNode只要多数派确认写成功这次操作就算提交了。Standby NameNode则不断地从JournalNode拉取edit log回放到自己的内存命名空间里保证和Active的元数据保持同步。这里有一个特别容易混淆的点JournalNode只存储edit log不存储文件块的元数据block map。文件块和DataNode的对应关系是Active和Standby各自在内存中自行维护的JournalNode完全不参与数据块的读写。所以它本身很轻但一旦出问题影响的是整个HA的“元数据同步链路”杀伤力极大。生产环境建议用3个或5个JournalNode必须是奇数。原因和ZooKeeper一样QJM需要多数派确认3个节点允许坏1个5个节点允许坏2个。如果用了偶数个会出现“平票”无法选出多数派的尴尬局面比如4个节点挂了2个剩2个各执一词整个服务直接不可用。这一点在规划集群规模时必须想清楚。JournalNode默认端口有两个8485RPC端口Active NameNode写edit log、Standby NameNode读edit log都用它。8480HTTP UI端口可以看JournalNode的运行状态和最近同步的edit log事务ID。我平时排查JournalNode问题时第一件事就是先打开http://jn-host:8480看它显示的Last Journal Id和Writer信息能快速判断它是否还在正常接收Active NameNode写入。2.2 ZKFC自动故障转移从监控到切换的完整链路有了JournalNode两个NameNode的元数据可以保持同步。但故障切换谁来触发早期版本需要运维手工执行hdfs haadmin -failover命令现在生产环境基本都用自动故障转移。自动故障转移的核心是ZKFCZooKeeper Failover Controller进程。每个NameNode所在节点上都会跑一个ZKFC它干三件事健康监控周期性地向本机的NameNode进程发送健康检查请求确认NameNode是活着的、能正常响应RPC。ZooKeeper会话管理Active NameNode的ZKFC会在ZooKeeper里创建一个锁节点路径形如/hdfs-ha/nameservice持有锁的NameNode才是合法ActiveStandby的ZKFC不持锁只是监听这个节点的存在。故障处理当Standby的ZKFC发现持有锁的NameNode失联会发起竞选抢锁竞选成功后把本机NameNode切换为Active同时触发对旧Active的隔离fencing。完整的切换链路是这样的ZooKeeper会话超时或心跳失败 → ZKFC判定Active异常 → Standby的ZKFC尝试创建锁节点并竞选 → 对旧Active执行fencing隔离 → 新Active完成状态转换并对外服务 → 客户端通过failover proxy自动感知并重连新Active。这里必须强调fencing这一步。如果不做fencing可能出现“旧Active进程还活着、新Active也在向外服务”的双主脑裂两个NameNode同时写edit log元数据直接分叉整个集群数据完整性就毁了。fencing相当于“先确认旧老大真的死了新老大才敢上台”。这个“确认”比想象中困难因为不能只靠“它没响应了”来判断必须主动隔离比如SSH过去把进程杀掉或者用shell命令做进一步验证。2.3 三种高可用方案对比为什么QJM是首选并不是所有HDFS高可用都用JournalNode我在规划集群时也认真对比过几种主流做法方案共享存储需要组件优点缺点QJMJournalNode无自身分布式至少3个JournalNode无单点故障、部署简单、社区主流元数据同步有额外网络开销NFS共享目录外部NFS存储共享文件系统部署最简单不需要额外进程NFS本身是单点网络抖动会导致双NameNode不可用Observer NameNode无Observer节点支持强一致读、扩展读能力配置复杂版本要求高生产落地案例少实际生产我首选QJM方案。NFS方案看着简单但我见过不止一次因为NFS挂载抖动导致两个NameNode同时进入standby的事故而且NFS本身就是单点运维起来并不省心。QJM虽然多维护几个JournalNode进程但从可靠性和故障域的隔离角度都非常值得。3. JournalNode连接失败排查实录从表象到根因3.1 第一轮排查进程与端口检查故障发生后我的第一反应是看最基本的进程还在不在、端口通不通。先在三个JournalNode节点上执行jps确认JournalNode进程都还在。然后检查8485端口的监听状态ss -lntp | grep 8485 ss -lntp | grep 8480结果发现jn3节点的8485端口正常监听但从nn1上telnet过去时能明显感觉到延迟telnet jn3 8485 Trying 10.10.x.x... Connected to jn3. Escape character is ^].连接能建立但响应很慢。这种“连接通但响应慢”的case最讨厌因为表面看起来一切正常实际已经埋了雷。接着去看JournalNode日志默认在$HADOOP_LOG_DIR目录下文件名类似hadoop-hdfs-journalnode-hostname.log。jn3日志里大量刷IOException和SocketTimeoutException还能看到不少线程blocked的堆栈信息。看到这些初步判断方向已经从“网络不通”转向“JournalNode自身处理能力出问题”。3.2 第二轮排查网络连通性与RPC超时第一轮排除了“进程挂了”和“防火墙挡端口”这两个最基础的原因接下来要确认网络链路质量。我用ping和iperf简单测了nn1到jn3的延迟和丢包结果延迟不高、丢包也不明显。这说明问题大概率不在网络链路上而在于JournalNode自身。这时候要引入一个关键参数dfs.qjournal.write-txns.timeout.ms默认是20000毫秒。Active NameNode向JournalNode写入一组edit log时如果超过这个时间还没收到多数派确认就会判定写失败。当JournalNode本身出问题线程阻塞、磁盘IO慢、GC停顿时即使网络是通的RPC响应也会超出这个超时时间上层看到的就是“connection failed”或“timed out”。这个超时参数是可以调大的但调大有副作用的Active NameNode感知故障的时间变长整体故障切换时间会增加。我生产环境一般保持默认除非确认网络确实差到不可理喻。3.3 根因定位磁盘写满与RPC线程阻塞继续深挖jn3的系统指标。用df -h看了磁盘发现JournalNode数据目录所在挂载点使用率已经100%。再用iostat看那块盘的util长期在95%以上基本是“盘都转不动了”的状态。到这里根因基本清楚了jn3的JournalNode数据目录所在磁盘被写满edit log落盘失败磁盘接近写满时文件系统元数据操作和flush操作都极慢RPC请求全部堆积在队列里线程阻塞Active NameNode向jn3发起的写日志请求迟迟得不到响应多数派确认失败最终Active NameNode的edit log写入链路整体断裂。为什么磁盘会写满追查后发现是同一台机器上的其他业务组件把磁盘占满了JournalNode只是“受灾户”。这是一个很典型的运维教训JournalNode最好不要和其他吃磁盘、吃CPU的业务组件混布在同一台机器上。生产环境一定要为JournalNode数据目录做独立挂载并且加上空间监控告警。3.4 恢复操作与验证清理磁盘空间后jn3的JournalNode日志逐渐恢复正常。但这时不能直接认为集群就恢复了因为两个NameNode都处于standby需要手动恢复Active。我的恢复步骤是清理jn3的磁盘垃圾确保数据目录所在分区至少20%以上空闲。确认三个JournalNode日志都不再报错HTTP UI都能正常打开且它们的Last Journal Id保持一致。在nn1上执行hdfs haadmin -transitionToActive nn1手动切换。执行hdfs haadmin -getAllServiceState确认nn1为active、nn2为standby。用hdfs dfs -ls /和hdfs dfsadmin -report验证读写恢复正常。这里有个细节手动切换前一定要确认standby NameNode已经把edit log追平了。如果standby落后太多切换过去可能会丢元数据甚至拒绝转为active。可以看nn2日志里的replaying edit log相关记录等追平后再切换。这次故障的应急处理还算快但它暴露了一个更深层的问题整个集群的故障切换还是依赖人工。如果当时是半夜、值班同学不在现场故障时间会成倍拉长。这就是我后来下定决心把自动故障转移彻底配置好的直接原因。4. 自动故障转移完整配置从零到生产可用的实操路径4.1 环境规划与版本说明先交代一下实验环境。我以一套三节点测试集群为例说明生产环境可以直接套同样的配置只是节点规模更大、主机名和IP不同。角色主机名IPNameNode ZKFCnn110.10.0.11NameNode ZKFCnn210.10.0.12JournalNode ZooKeeperjn110.10.0.21JournalNode ZooKeeperjn210.10.0.22JournalNode ZooKeeperjn310.10.0.23DataNodedn1/dn2/dn310.10.0.31-33版本用的是Apache Hadoop 3.3.xZooKeeper 3.6.xJDK 8或11都可以。这里为了演示把ZooKeeper和JournalNode放在同一批节点上生产上可以分开也可以合并核心原则是JournalNode和ZooKeeper都必须保证奇数个。4.2 core-site.xml与hdfs-site.xml关键参数自动故障转移的配置核心在两个文件里core-site.xml和hdfs-site.xml。下面是我在生产验证过的配置逐段注释关键参数含义。先看core-site.xmlconfiguration property namefs.defaultFS/name valuehdfs://mycluster/value /property property nameha.zookeeper.quorum/name valuejn1:2181,jn2:2181,jn3:2181/value /property /configurationha.zookeeper.quorum是ZooKeeper集群地址列表只写ZooKeeper的地址和端口不要跟JournalNode的8485端口混在一起。再看hdfs-site.xml这是核心中的核心configuration !-- 命名服务ID -- property namedfs.nameservices/name valuemycluster/value /property !-- 该命名服务下有哪些NameNode -- property namedfs.ha.namenodes.mycluster/name valuenn1,nn2/value /property !-- nn1和nn2的RPC地址 -- property namedfs.namenode.rpc-address.mycluster.nn1/name valuenn1:8020/value /property property namedfs.namenode.rpc-address.mycluster.nn2/name valuenn2:8020/value /property !-- nn1和nn2的HTTP UI地址 -- property namedfs.namenode.http-address.mycluster.nn1/name valuenn1:9870/value /property property namedfs.namenode.http-address.mycluster.nn2/name valuenn2:9870/value /property !-- 关键共享编辑日志目录使用QJM协议 -- property namedfs.namenode.shared.edits.dir/name valueqjournal://jn1:8485;jn2:8485;jn3:8485/mycluster/value /property !-- JournalNode本地数据目录 -- property namedfs.journalnode.edits.dir/name value/data/hdfs/jn/value /property !-- 开启自动故障转移 -- property namedfs.ha.automatic-failover.enabled/name valuetrue/value /property !-- 客户端failover代理实现无缝切换 -- property namedfs.client.failover.proxy.provider.mycluster/name valueorg.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvider/value /property !-- fencing方法sshfence -- property namedfs.ha.fencing.methods/name valuesshfence/value /property !-- sshfence需要免密登录到对端NameNode -- property namedfs.ha.fencing.ssh.private-key-files/name value/home/hdfs/.ssh/id_rsa/value /property /configuration几个配置细节要特别说明。先讲dfs.namenode.shared.edits.dir。这个配置告诉Active NameNode把edit log写到哪组JournalNode上。格式是qjournal://后面跟所有JournalNode的RPC地址端口8485最后跟一个命名空间ID。这个命名空间ID可以随意起但必须和dfs.nameservices里的值一致否则NameNode启动时会报找不到对应的JournalNode目录。再讲dfs.ha.automatic-failover.enabled。这个开关必须设置为trueZKFC才会在start-dfs.sh时自动启动并接管。如果忘记开这个开关进程不会启动故障切换永远只能手动进行。然后是dfs.ha.fencing.methods。sshfence是最常用的fencing方式新Active的ZKFC通过SSH登录到旧Active所在机器执行命令把旧NameNode进程杀掉。所以必须提前配好免密登录而且要确保SSH用户有权限kill对应进程。更稳妥的方式还有shell可以自定义fence命令同时做“kill进程验证端口关闭”两步避免“进程已死但端口还被占着”的残留状态。最后是dfs.client.failover.proxy.provider。这个配置决定客户端怎么感知NameNode切换。ConfiguredFailoverProxyProvider是标准实现客户端会轮询配置里的两个NameNode地址遇到Active切换会自动重连新Active不需要修改客户端代码。4.3 ZooKeeper初始化与ZKFC启动配置写完之后不能直接启动集群需要先向ZooKeeper里初始化HA状态。这一步在新版Hadoop里start-dfs.sh会自动检查但手动执行会更可控。初始化命令hdfs zkfc -formatZK这条命令会在ZooKeeper里创建/hdfs-ha/mycluster等节点用于后续的ActiveStandbyElector锁竞争。注意只能执行一次重复执行会清空之前的HA状态生产环境要非常谨慎。然后启动JournalNode如果还没启动hdfs --daemon start journalnode三个JournalNode都要启动。之后按顺序初始化NameNode# 第一次搭建时在nn1上格式化 hdfs namenode -format # 在nn2上同步nn1的元数据 hdfs namenode -bootstrapStandby # 启动两个NameNode hdfs --daemon start namenode在nn2上执行bootstrapStandby时它会自动从nn1拉取最新的FSImage和edit log完成Standby初始化。这一步很重要省略的话nn2启动后会因为缺元数据一直处于安全模式或不健康状态。最后启动ZKFChdfs --daemon start zkfc配置正确的话在nn1和nn2上执行jps应该能看到DFSZKFailoverController进程。用hdfs haadmin -getAllServiceState确认状态hdfs haadmin -getAllServiceState输出结果类似nn1:active nn2:standby到这里自动故障转移的基础链路已经通了。生产环境建议把ZKFC和NameNode都托管给systemd或supervisor确保进程异常退出后能被自动拉起。4.4 故障切换演练kill一个Active NameNode配好之后必须做一次故障演练否则你永远不会知道配置到底能不能用。我的演练方案很直接kill掉Active NameNode进程模拟最极端的进程级故障。先确认当前Active是nn1然后执行# 在nn1上强杀NameNode进程 kill -9 $(pgrep -f namenode)正常情况下的预期切换链路是nn1上的ZKFC发现NameNode进程消失健康检查失败。nn1的ZKFC在ZooKeeper里的锁会话超时或自动释放。nn2的ZKFC监听到锁释放发起竞选获得锁。nn2的ZKFC执行fencingSSH到nn1再次确认或杀掉残留的NameNode进程。nn2切换为active开始对外服务。整个切换过程通常在几十秒内完成。验证命令hdfs haadmin -getAllServiceState输出nn1:standby nn2:active同时验证客户端读写hdfs dfs -mkdir -p /test/ha-check hdfs dfs -put /etc/hosts /test/ha-check/ hdfs dfs -cat /test/ha-check/hosts这里有个细节kill -9之后旧的NameNode进程不会自动重新启动。如果nn1本身还在线它的ZKFC会尝试在新竞选里抢锁可能出现来回切换flapping的情况这个后面在常见问题里专门讲。4.5 手工切换与管理命令汇总自动故障转移配置好不等于手工切换就没用了。日常运维里版本升级、更换硬件、迁移NameNode角色这些场景仍然需要手工干预。下面是我整理的高频管理命令命令作用hdfs haadmin -getAllServiceState查看所有NameNode状态hdfs haadmin -getServiceState nn1查看单个NameNode状态hdfs haadmin -transitionToActive nn2手工将nn2切换为activehdfs haadmin -transitionToStandby nn1手工将nn1切换为standbyhdfs haadmin -failover nn1 nn2自动完成“standby化nn1 active化nn2”hdfs zkfc -formatZK初始化ZooKeeper中的HA状态hdfs namenode -bootstrapStandby同步另一台NameNode的元数据这里必须强调transitionToActive和failover的区别transitionToActive不做fencing如果旧Active还活着并继续写入可能酿成脑裂failover内置了fencing流程会先确保旧Active退出。日常升级维护建议优先用hdfs haadmin -failover。5. 常见问题与避坑技巧实录5.1 高频问题速查表写配置和切换的过程中我把遇到过的典型问题整理成了速查表基本覆盖了90%的HDFS HA故障场景现象可能原因排查/解决两个NameNode都是standbyJournalNode写日志失败、ZKFC选举异常检查JN磁盘、日志检查ZooKeeper会话手工transitionToActiveZKFC进程启动失败ZooKeeper连不上、锁节点冲突检查ha.zookeeper.quorum查看zkfc日志启动报Address already in use端口被占用检查8020/9870/8485端口占用进程切换后客户端长时间失败客户端proxy provider配置缺失检查dfs.client.failover.proxy.providersshfence失败免密登录没配好手动测试SSH命令检查私钥路径NameNode频繁切换flapping健康检查参数过短、JVM GC过长调整健康检查频率和JVM堆参数JournalNode报Too many open files文件句柄不够调大ulimit -nJournalNode建议65535以上edits日志堆积导致磁盘写满未配置合理的checkpoint策略配置dfs.namenode.num.extra.edits.retained配合周期checkpoint5.2 我踩过的坑与独家心得最后分享几个真正让我“长记性”的细节。第一个坑JournalNode和ZooKeeper的进程数必须严格控制。JournalNode只需要在配置的那几个节点上启动多一台少一台都可能引发“看不到多数派”的诡异问题。我之前就遇到过某台机器上因为手工启动脚本重复执行起了两个JournalNode进程端口被第二个进程抢占第一个进程假死导致client写日志一直失败。第二个坑自动故障转移开启后千万不要手动去改NameNode状态。比如自动转移生效期间有人手贱执行了hdfs haadmin -transitionToActive会和ZKFC的自动逻辑打架状态在ZooKeeper和NameNode之间不一致造成切换异常。如果确实需要手动干预先把dfs.ha.automatic-failover.enabled临时关掉操作完再恢复。第三个坑ZooKeeper的session timeout设置。ZKFC默认超时参数和ZooKeeper服务端的tickTime如果差距过大容易出现“ZooKeeper还没判定Active失联但ZKFC已经急不可耐去抢锁”的混乱。我的经验是保持ZKFC的session timeout和ZooKeeper的tickTime链路上整体协调不要单独调某一个参数。第四个坑distcp、镜像同步等批量任务在HA切换时会碰到“已经建立连接的客户端重连失败”的问题。原因是hdfs dfs命令每次新建连接但长时间运行的MapReduce任务是复用连接的。解决办法是给客户端配置和服务器端一致的failover provider确保客户端能感知切换。我后来用distcp做跨集群数据同步时遇到过切换后任务卡死基本都是这个原因。第五个坑Hive、Spark、Elasticsearch的HDFS repository这些上层组件对接HA时要把fs.defaultFS指向nameservice如hdfs://mycluster而不是某个具体NameNode同时确保它们的core-site.xml里有ha.zookeeper.quorum和proxy provider配置。这些组件的连接池往往缓存了旧的NameNode地址切换后不重启任务就会报ConnectException。尤其是Elasticsearch的HDFS snapshot仓库连接超时时间往往设置得很短HDFS切换的几十秒窗口足够让它直接报错。说实话HDFS HA这套机制本身已经很成熟大多数故障都不是机制本身的锅而是部署细节、监控缺失和操作不规范。把JournalNode当回事、把磁盘监控做到位、把自动转移演练做到常态化这套集群才能真正让人睡得着觉。那次故障之后我给所有JournalNode挂了独立磁盘空间告警把自动故障转移在全集群铺开并且每季度做一次kill -9演练确保切换链路始终可用。还有一个小心得想分享每次故障演练之后记得把zkfc和NameNode的日志留一份归档下次再出问题时可以直接对比日志差异定位会快很多。高可用不是配完就完了而是要持续地“折腾”它让它时刻处于随时可切换的状态。
返回列表