1. 故障现场:所有服务都活着,唯独少了 SecondaryNameNode
说实话,Hadoop 集群里最容易被忽视的进程,我觉得就是 SecondaryNameNode。它不像 NameNode 挂了全网瘫痪,也不像 DataNode 少了就触发副本补偿,它平时只是默默做 checkpoint,存在感本来就低——所以当它真的没起来的时候,很多人第一反应是“这玩意儿是不是本来就不用起”。我这次就踩了一个非常典型的坑:一套刚搭好的测试集群,执行完start-dfs.sh,刷了一堆启动日志,我以为一切正常,结果jps一看,NameNode、DataNode 都在,SecondaryNameNode 连影子都没有。翻hadoop-hdfs日志,一行刺眼的异常赫然在列:
java.lang.IllegalArgumentException: Does not contain a valid host:port authority: hdfs:看到这个报错我就明白了,这跟内存不够、端口冲突统统无关,是某个地址配置被解析成了一个残缺的 URI。今天就把这个问题的完整排查链路、根因原理和修复方法写出来,希望能帮到同样被 “SNN 神秘失踪” 折磨的人。
1.1 第一步:看进程,确认 SNN 确实缺失
遇到“某某进程没起来”的问题,第一步永远是确认进程状态,别急着看配置。我在出问题的节点上跑了一下jps,输出大概是这样的:
15678 NameNode 15992 DataNode 16321 ResourceManager 16634 NodeManager注意看,NameNode 和 DataNode 都在,ResourceManager、NodeManager 也在,唯独少了SecondaryNameNode这一行。为了排除是jps偶尔抽风,我又用ps确认了一遍:
ps -ef | grep -i secondarynamenode结果没有任何匹配的进程。这说明不是进程残留、不是假死,而是 SecondaryNameNode 压根没活下来。确认了这一点,下一步就直接去翻日志。
1.2 第二步:翻日志,锁定异常发生的准确时间线
HDFS 各组件的日志默认放在$HADOOP_LOG_DIR目录下,如果不确定目录位置,可以执行:
ls -l "$HADOOP_LOG_DIR/" | grep -i secondary一般会看到类似hadoop-{用户名}-secondarynamenode-{主机名}.log的文件。我们这边 SNN 的日志文件名是hadoop-hdfs-secondarynamenode-test-node-01.log。用tail看末尾,就能抓到启动失败的现场:
tail -n 200 "$HADOOP_LOG_DIR/hadoop-hdfs-secondarynamenode-$(hostname).log"日志末尾就是上面那段IllegalArgumentException,而且能明显看到堆栈里出现了DFSUtil.getNamenodeServiceAddr和DFSClient.<init>。看到这两个类出现在报错堆栈里,问题的方向基本就锁定了:不是 SNN 专用的 checkpoint 模块出问题,而是 HDFS 客户端在初始化 NameNode 地址时失败了。
1.3 为什么这类问题容易被误判为“SNN 没被拉起”
这里有个非常容易误判的点,值得单独拿出来说:start-dfs.sh启动 SNN 的行为,依赖于$HADOOP_CONF_DIR/masters文件。
- 如果
masters文件不存在,start-dfs.sh根本不会尝试启动 SecondaryNameNode,这时候集群里没有这个进程是“正常”的,但很多新手会以为是故障。 - 如果
masters文件存在,但里面的主机名写错、或者 SSH 不通,SSH 启动步骤会失败,SNN 同样是没起来。
我这次的情况属于第三种:masters文件没问题,SNN 确实被尝试启动了,但在启动早期就抛了异常、直接退出。区分“没被拉起”和“拉起后退出”很简单,就看有没有main线程的异常堆栈日志。有堆栈,说明进程启动过,是启动即崩;没有堆栈,说明压根没人叫它起床。
2. 报错真相:hdfs: 这个残体 URI,卡在了 DFSClient 的 authority 校验上
很多人在排查报错时,习惯先搜“怎么解决”,却很少问“这个报错到底是谁抛出来的、为什么这么写”。如果不把这一层想清楚,改配置大概率是瞎试。所以这一节我们专门拆这个Does not contain a valid host:port authority: hdfs:。
2.1 URI 的固定结构:没有 host:port,地址就无从谈起
HDFS 里的地址有两种典型写法:
- 完整 URI 形式:
hdfs://nn-host:8020/,由 scheme(hdfs)、authority(nn-host:8020)、path(/)组成。 - 纯 host:port 形式:
nn-host:8020,比如dfs.namenode.rpc-address的写法。
报错里的hdfs:是URI.create("hdfs:")的结果——只有 scheme,连双斜杠都没有,更别提 authority 了。你可以自己在任意一台机器上用 Java 验证:
URI uri = URI.create("hdfs:"); System.out.println("authority = " + uri.getAuthority()); // 输出 nullgetAuthority()返回null,意味着整个 URI 里没有任何“主机名:端口”信息。这就好比一张快递单上只写了“快递:”两个字,没有收件人、没有城市、没有街道,快递公司的系统在录单阶段就直接拒收了,根本不需要等快递员出门。
2.2 DFSClient 构造器里那行硬校验
这个报错在 HDFS 源码里非常明确。DFSClient构造时,会拿用户配置里解析出来的 URI 去获取 NameNode 的地址,核心逻辑简化下来是下面这个样子:
public DFSClient(URI nameNodeUri, Configuration conf, FileSystem.Statistics stats) throws IOException { this(DFSUtil.getNamenodeServiceAddr(nameNodeUri, conf), conf, stats); } // DFSUtil.getNamenodeServiceAddr 内部 private static String getNamenodeServiceAddr(URI uri, Configuration conf) { String authority = uri.getAuthority(); if (authority == null) { throw new IllegalArgumentException( "Does not contain a valid host:port authority: " + uri); } // ... 后续逻辑 }注意最后一行异常消息直接拼接了uri的toString(),所以当传入的 URI 是hdfs:时,异常消息就会原样显示hdfs:。这种“启动早期校验”的设计其实很合理:与其等到真正连接 NameNode 时才报一个莫名其妙的UnknownHostException,不如在客户端对象创建的瞬间就告诉你“地址不完整,别浪费时间了”。
2.3 SNN 启动时为什么会被“客户端初始化”绊倒
这里引出一个关键问题:SecondaryNameNode 不是服务端进程吗?为什么它会去创建DFSClient?
因为 SNN 的本质就是一个特殊的 HDFS 客户端。它的核心工作是周期性地从 NameNode 下载 Edits 日志、合并 FSImage、再把新的 FSImage 上传回去。整个流程里,它必须作为客户端和 NameNode 建立 RPC 连接。
在实际代码里,SecondaryNameNode的构造函数会创建Checkpoint对象,而Checkpoint内部会调用FileSystem.get(conf)来获取文件系统实例。当fs.defaultFS这个配置被解析成hdfs:这种残体时,FileSystem.get走到DistributedFileSystem的初始化,紧接着就触发DFSClient构造,于是 authority 校验失败,异常一路抛回main方法,进程退出。
所以整个现象链条是:fs.defaultFS坏了 → SNN 创建 HDFS 客户端失败 → SNN 启动即退出 → 集群里没有 SecondaryNameNode。NameNode 和 DataNode 不受影响,是因为它们启动时并不依赖fs.defaultFS去创建DFSClient。
3. 逐步排查:从 SNN 日志倒推出 fs.defaultFS 的问题
知道了报错机制,排查就有的放矢了。我们要回答的核心问题是:运行时真正生效的fs.defaultFS到底被解析成了什么?这里只靠眼睛看配置文件是不够的,因为 Hadoop 的配置加载有多层覆盖逻辑。
3.1 用 getconf 拿运行时值,不要只盯着 xml
Hadoop 提供了一条很实用的命令,能直接读某个配置项的最终生效值:
hdfs getconf -confKey fs.defaultFS我当时执行的结果就是:
hdfs:看到这个输出,基本就实锤了:fs.defaultFS在运行时被解析成了hdfs:。但这里有个值得注意的点——这个值不一定是你写在core-site.xml里的那个值。Hadoop 的Configuration加载顺序大概是:
- 通过代码或类路径加载的各个 xml 配置文件;
- 同一个 xml 文件里如果存在多个同名
<property>,后出现的会覆盖先出现的; -D等 JVM 系统属性参数可以覆盖 xml 里的值;- 某些环境变量、启动脚本注入的内容也可能影响最终结果。
所以遇到配置“看起来没问题,行为却很怪”的情况,优先用getconf打印运行时值,不要对着文件猜。
3.2 配置里的变量替换与空格陷阱
我自己排查时,第一件事是打开core-site.xml,找到fs.defaultFS那一块,看看有没有明显被写坏的内容。通常最直接的成因是手滑,把<value>写成了:
<property> <name>fs.defaultFS</name> <value>hdfs:</value> </property>另一个很常见的场景是用了变量占位符,但变量没有定义。比如模板里写的是:
<property> <name>fs.defaultFS</name> <value>hdfs://${nn.host}:8020</value> </property>如果${nn.host}没有被任何配置定义,Hadoop 的Configuration会把未定义的变量替换成空字符串,最终结果变成hdfs://:8020。这种结果是另一种报错形态,但同样属于“地址残缺”家族。
还有一种隐蔽情况是值的前后带了不可见字符,比如<value> hdfs://nn-host:8020</value>前面有个空格。Configuration.get返回的字符串不会自动 trim 前后空格,URI.create对空格很敏感,很容易抛出URISyntaxException。这些都属于配置被“污染”的类型。
3.3 一条命令复现同类错误,确认影响范围
为了确认坏的配置不是只影响 SNN,而是影响所有 HDFS 客户端,我做了一个最小复现实验。在出问题的节点上直接执行:
hdfs dfs -ls /结果同样抛出:
java.lang.IllegalArgumentException: Does not contain a valid host:port authority: hdfs:这说明问题已经污染了全局 HDFS 客户端配置。反过来,如果我在命令里显式指定完整的 URI:
hdfs dfs -ls hdfs://nn-host:8020/这条命令大概率能正常列出文件。因为命令行显式传入了完整 authority,绕开了fs.defaultFS。这一步实验非常有价值:它证明了 NameNode 的 RPC 服务本身是好的,坏掉的是客户端侧的默认地址配置。
4. 修复与验证:改回合法的 host:port 并重启 SNN
定位到根因之后,修复本身并不复杂。麻烦的是很多人改完配置不验证,重启之后发现还是不行,又得回到原点。所以这一节我把改配置、同步、重启、验证的完整流程都过一遍。
4.1 正确的 fs.defaultFS 写法与集群内同步
当时我在core-site.xml里看到坏掉的配置,直接改成合法的写法:
<property> <name>fs.defaultFS</name> <value>hdfs://nn-host:8020</value> </property>这里的nn-host要替换成你集群里实际运行 NameNode 的主机名,端口要和dfs.namenode.rpc-address保持一致。如果记不清端口,可以先执行:
hdfs getconf -confKey dfs.namenode.rpc-address拿到正确的 host:port 之后再往上填。改完之后,绝对不要只改当前节点,要把core-site.xml(以及所有涉及地址的配置文件)同步到所有 HDFS 相关节点上,包括 NameNode、DataNode,当然也包括 SNN 所在的节点。我当时的做法是直接用scp推送到各节点,同时备份了一份修改前的版本,方便回滚。
4.2 重启 SNN 的推荐方式与验证清单
配置同步完成后,我执行了下面的命令重启 SNN。Hadoop 3.x 可以直接用:
hdfs --daemon stop secondarynamenode hdfs --daemon start secondarynamenode如果是老版本,则用hadoop-daemon.sh start secondarynamenode这类传统脚本。如果测试环境允许,直接sbin/start-dfs.sh一次性重启整套 HDFS 也没问题,反正我对配置有把握。
重启之后,我按顺序做了四步验证,缺一不可:
- 执行
jps,确认SecondaryNameNode进程已经出现。 - 执行
hdfs getconf -confKey fs.defaultFS,确认输出的是hdfs://nn-host:8020而不是hdfs:。 - 执行
hdfs dfs -ls /,确认全局客户端恢复正常。 - 继续
tailSNN 日志,等待一个 checkpoint 周期,看是否出现Checkpoint done、Uploading new image这类关键动作。
4.3 别忘了先确认是否为 HA 集群
这里额外提醒一种很容易让人白忙的情况:如果你的集群配置了 HA(比如有dfs.nameservices、多个 NameNode),那么按照官方架构,本来就不需要独立跑一个 SecondaryNameNode。NameNode 自身会承担 checkpoint 职责,由dfs.namenode.checkpoint.period等参数控制。
在这种 HA 集群里,你看到没有 SecondaryNameNode 进程,不等于故障。如果非要往masters文件里塞一个主机名去强制启动 SNN,反而可能制造出各种奇怪问题。所以动手排查前,先确认自己的集群是单 NameNode 还是 HA 模式,别把正确架构当成错误来修。
5. 举一反三:HDFS 地址配置中容易踩中的相似坑
处理完这个问题之后,我顺手把集群里所有跟地址相关的配置全部过了一遍,发现这类“地址残缺”问题其实是一个家族,只是每种配置的报错形态不一样。把这一层搞明白,以后就不会再被类似问题难住。
5.1 哪些地址配置绝不能带 scheme 前缀
先列一个我在实际运维中反复遇到的对照表:
| 配置项 | 正确写法 | 常见错误写法 | 可能的报错方向 |
|---|---|---|---|
| fs.defaultFS | hdfs://nn-host:8020 | hdfs:、hdfs:// | authority 校验失败 |
| dfs.namenode.rpc-address | nn-host:8020 | hdfs://nn-host:8020 | 解析异常/无法连接 |
| dfs.namenode.http-address | nn-host:9870 | hdfs://nn-host:9870 | 解析异常/无法连接 |
| dfs.namenode.secondary.http-address | nn-host:9868 | hdfs://nn-host:9868 | 解析异常/无法连接 |
| dfs.datanode.http.address | nn-host:9864 | hdfs://nn-host:9864 | 解析异常/无法连接 |
这里面有一个容易混淆的判断标准:凡是最终要转成InetSocketAddress的配置,一律只写 host:port,不要画蛇添足加hdfs://;凡是配置里要求填完整 URI 的,比如fs.defaultFS,才需要写hdfs://authority。
很多人在dfs.namenode.rpc-address里写了hdfs://host:8020之后,表面上看进程也能起来,但某些内部解析逻辑会把hdfs当成主机名的一部分去处理,导致后续 RPC 连接失败或者拿到的地址完全不对。这种问题比hdfs:报错更隐蔽,因为不是第一时间崩溃,而是运行一段时间后才暴露。
5.2 变量替换、多环境注入导致地址残缺的常见场景
我的个人体会是,配置模板化是这类问题的重灾区。很多团队会用同一套配置模板同时管理开发、测试、生产环境,靠${env.host}之类的占位符区分环境。一旦某个环境少定义了一个变量,或者变量引用的 key 拼错了,最终生成的配置里就会出现hdfs://:8020、hdfs:这种残体。
还有一种常见场景是从别的集群直接拷贝配置,拷完只改了fs.defaultFS的主机名,却漏掉了dfs.namenode.rpc-address、dfs.namenode.http-address等配套项。于是出现“默认地址指向新集群,实际通信地址指向旧集群”的割裂状态,SNN 连不上 NameNode、客户端连不进集群,各种诡异故障轮番来。所以我现在的习惯是,任何地址相关的配置变更,都要先列一份清单,把所有带 host:port 的项都过一遍,再决定改哪个。
5.3 我实际踩过坑之后养成的排查习惯
最后分享几个我自己沉淀下来的排查习惯,不一定多高级,但很管用:
- 改完任何地址配置,第一件事永远是
hdfs getconf -confKey <配置项>,确认运行时生效值,而不是满足于“xml 文件里看着是对的”。 - 看到
Does not contain a valid host:port authority这个关键字,第一反应不是去改别的配置,而是顺着堆栈找到传入的 URI,反推它来自哪一份配置、哪个变量。 - 遇到“某进程没起来”,先做三分类:压根没被拉起、启动即退出、启动后失联。三类问题的排查路径完全不同,乱了顺序就是浪费时间。
- 检查同类配置项时,顺手把空格、变量占位符、重复 property 这三个隐患一起扫掉,很多倒霉问题其实是这三兄弟合伙搞出来的。
这次 SNN 缺席的问题,表面看是缺少一个进程,实际是fs.defaultFS被解析成了hdfs:这种残缺 URI,在 HDFS 客户端初始化的 authority 校验阶段就拦截了。只要你掌握了 URI 结构、校验位置、配置加载顺序这三件事,以后再遇到类似的地址解析类报错,基本都能一击命中。