说实话,但凡维护过HDFS高可用集群的人,看到“JournalNode连接失败”这几个字,心里多少都会咯噔一下。我去年在生产环境就实打实踩过一次:巡检时发现两个NameNode全部处于standby状态,HDFS完全不可写,跑在上面的Hive任务和定时调度瞬间全挂。查了半天,日志里反复出现“JournalNode connection failed”相关的报错,最后定位到根因是JournalNode所在节点的磁盘写满,导致共享编辑日志写不进去,Active NameNode在等待多数派确认时持续超时,最后放弃active身份,Standby又没能及时接管,整个集群卡死在“双Standby”的僵尸状态。
这篇文章就把这次故障的完整复盘、JournalNode在高可用架构里的作用、从手工恢复到自动故障转移的配置全过程都梳理一遍,最后附上我踩过的坑和问题速查表。不管你是正在搭建HA集群,还是已经在维护生产环境,这篇应该都能帮你少走不少弯路。
1. 故障复盘:一次JournalNode连接失败引发的HDFS写入瘫痪
1.1 现场现象:双Standby的诡异状态
先说当时的现场。集群是标准的HA架构:两台NameNode(nn1、nn2),三个JournalNode(jn1、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,通过QJM(Quorum 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默认端口有两个:
8485:RPC端口,Active NameNode写edit log、Standby NameNode读edit log都用它。8480:HTTP UI端口,可以看JournalNode的运行状态和最近同步的edit log事务ID。
我平时排查JournalNode问题时,第一件事就是先打开http://jn-host:8480看它显示的Last Journal Id和Writer信息,能快速判断它是否还在正常接收Active NameNode写入。
2.2 ZKFC自动故障转移:从监控到切换的完整链路
有了JournalNode,两个NameNode的元数据可以保持同步。但故障切换谁来触发?早期版本需要运维手工执行hdfs haadmin -failover命令,现在生产环境基本都用自动故障转移。
自动故障转移的核心是ZKFC(ZooKeeper Failover Controller)进程。每个NameNode所在节点上都会跑一个ZKFC,它干三件事:
- 健康监控:周期性地向本机的NameNode进程发送健康检查请求,确认NameNode是活着的、能正常响应RPC。
- ZooKeeper会话管理:Active NameNode的ZKFC会在ZooKeeper里创建一个锁节点(路径形如
/hdfs-ha/<nameservice>),持有锁的NameNode才是合法Active;Standby的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,我在规划集群时也认真对比过几种主流做法:
| 方案 | 共享存储 | 需要组件 | 优点 | 缺点 |
|---|---|---|---|---|
| QJM(JournalNode) | 无,自身分布式 | 至少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不同。
| 角色 | 主机名 | IP |
|---|---|---|
| NameNode + ZKFC | nn1 | 10.10.0.11 |
| NameNode + ZKFC | nn2 | 10.10.0.12 |
| JournalNode + ZooKeeper | jn1 | 10.10.0.21 |
| JournalNode + ZooKeeper | jn2 | 10.10.0.22 |
| JournalNode + ZooKeeper | jn3 | 10.10.0.23 |
| DataNode | dn1/dn2/dn3 | 10.10.0.31-33 |
版本用的是Apache Hadoop 3.3.x,ZooKeeper 3.6.x,JDK 8或11都可以。这里为了演示把ZooKeeper和JournalNode放在同一批节点上,生产上可以分开也可以合并,核心原则是JournalNode和ZooKeeper都必须保证奇数个。
4.2 core-site.xml与hdfs-site.xml关键参数
自动故障转移的配置核心在两个文件里:core-site.xml和hdfs-site.xml。下面是我在生产验证过的配置,逐段注释关键参数含义。
先看core-site.xml:
<configuration> <property> <name>fs.defaultFS</name> <value>hdfs://mycluster</value> </property> <property> <name>ha.zookeeper.quorum</name> <value>jn1:2181,jn2:2181,jn3:2181</value> </property> </configuration>ha.zookeeper.quorum是ZooKeeper集群地址列表,只写ZooKeeper的地址和端口,不要跟JournalNode的8485端口混在一起。
再看hdfs-site.xml,这是核心中的核心:
<configuration> <!-- 命名服务ID --> <property> <name>dfs.nameservices</name> <value>mycluster</value> </property> <!-- 该命名服务下有哪些NameNode --> <property> <name>dfs.ha.namenodes.mycluster</name> <value>nn1,nn2</value> </property> <!-- nn1和nn2的RPC地址 --> <property> <name>dfs.namenode.rpc-address.mycluster.nn1</name> <value>nn1:8020</value> </property> <property> <name>dfs.namenode.rpc-address.mycluster.nn2</name> <value>nn2:8020</value> </property> <!-- nn1和nn2的HTTP UI地址 --> <property> <name>dfs.namenode.http-address.mycluster.nn1</name> <value>nn1:9870</value> </property> <property> <name>dfs.namenode.http-address.mycluster.nn2</name> <value>nn2:9870</value> </property> <!-- 关键:共享编辑日志目录,使用QJM协议 --> <property> <name>dfs.namenode.shared.edits.dir</name> <value>qjournal://jn1:8485;jn2:8485;jn3:8485/mycluster</value> </property> <!-- JournalNode本地数据目录 --> <property> <name>dfs.journalnode.edits.dir</name> <value>/data/hdfs/jn</value> </property> <!-- 开启自动故障转移 --> <property> <name>dfs.ha.automatic-failover.enabled</name> <value>true</value> </property> <!-- 客户端failover代理,实现无缝切换 --> <property> <name>dfs.client.failover.proxy.provider.mycluster</name> <value>org.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvider</value> </property> <!-- fencing方法:sshfence --> <property> <name>dfs.ha.fencing.methods</name> <value>sshfence</value> </property> <!-- sshfence需要免密登录到对端NameNode --> <property> <name>dfs.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。这个开关必须设置为true,ZKFC才会在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启动后会因为缺元数据一直处于安全模式或不健康状态。
最后启动ZKFC:
hdfs --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执行fencing:SSH到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切换为active |
hdfs haadmin -transitionToStandby nn1 | 手工将nn1切换为standby |
hdfs 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都是standby | JournalNode写日志失败、ZKFC选举异常 | 检查JN磁盘、日志;检查ZooKeeper会话;手工transitionToActive |
| ZKFC进程启动失败 | ZooKeeper连不上、锁节点冲突 | 检查ha.zookeeper.quorum;查看zkfc日志 |
启动报Address already in use | 端口被占用 | 检查8020/9870/8485端口占用进程 |
| 切换后客户端长时间失败 | 客户端proxy provider配置缺失 | 检查dfs.client.failover.proxy.provider |
| sshfence失败 | 免密登录没配好 | 手动测试SSH命令;检查私钥路径 |
| NameNode频繁切换(flapping) | 健康检查参数过短、JVM GC过长 | 调整健康检查频率和JVM堆参数 |
JournalNode报Too many open files | 文件句柄不够 | 调大ulimit -n,JournalNode建议65535以上 |
| edits日志堆积导致磁盘写满 | 未配置合理的checkpoint策略 | 配置dfs.namenode.num.extra.edits.retained,配合周期checkpoint |
5.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的日志留一份归档,下次再出问题时可以直接对比日志差异,定位会快很多。高可用不是配完就完了,而是要持续地“折腾”它,让它时刻处于随时可切换的状态。