很多第一次看到Secondary NameNode这个名词的人,都容易把它当成 NameNode 的“备胎”,觉得它是用来故障转移的热备节点。我在刚开始接触 HDFS 的时候也这么想过,直到有一次真把 NameNode 重启了,才意识到自己的想法错得有多离谱。那次集群的 edits 日志已经积累了非常多,NameNode 启动时回放日志整整花了四十多分钟,业务早就停了。后来有人提醒我“你可以配个 Secondary NameNode 做定期 checkpoint”,我才开始认真研究这个组件。
这篇文章就围绕 Secondary NameNode 展开,把它的工作机制、配置参数、运维手段,以及在高可用(HA)架构里的真实角色一次讲清楚。内容适合正在学 HDFS 的初学者,也适合已经在维护 Hadoop 集群、想搞清楚“SNN 到底有没有用”的工程师。下面会用到大量实操细节,也会从原理层面解释它为什么存在、为什么在 HA 时代经常被误解。
1. 先说结论:Secondary NameNode 不是 NameNode 的热备
1.1 一个被误解了很久的角色
很多教程把 Secondary NameNode 翻译成“辅助 NameNode”或者“次要名称节点”,光听名字就很容易误以为它是主节点的从节点,主节点挂了它能顶上。这个理解是错的,而且错得很彻底。
Secondary NameNode 不接收 HDFS 客户端的读写请求,也不参与任何故障转移,NameNode 进程挂掉之后,它不会自动接管元数据服务。它的职责只有一个,就是定期帮 NameNode 做“检查点”(checkpoint),把内存中的元数据落盘结果和最近产生的操作日志合并,生成一份新的元数据快照。
用生活里的话说,NameNode 是一个记账的人,它每天把所有收支记在脑子和一个流水账本(edits)里,Secondary NameNode 是那个定期帮它“对账、把流水账整理成总账本”的助理。助理不会替老板坐班,但要是没有助理定期整理账本,老板一旦重启,就得从头翻无数页流水账,耗时极长。
1.2 它真正解决的问题
要理解 Secondary NameNode 的价值,还得回到 HDFS 的元数据管理机制。NameNode 启动时会把整个文件系统的目录树、文件块信息加载到内存,同时持久化到磁盘上的fsimage文件。fsimage是一个静态快照,里面记录的是某一时刻的全量元数据;而之后产生的所有变更,比如新建文件、删除目录、修改副本数,都会追加写入edits日志。
问题是:如果edits一直增长,NameNode 重启时就得从fsimage开始,再把所有edits一条条回放一遍,才能恢复内存里的最新状态,这个时间会越来越长。更糟糕的是,edits无限膨胀还会占用大量磁盘空间,一旦写满,整个集群的元数据变更就全停了。Secondary NameNode 的 checkpoint 机制就是把edits合并进fsimage,让 NameNode 的启动时间保持在可控范围内。
可以把它理解为“全量备份 + 增量日志”的组合,fsimage是周末的全量备份,edits是每天的增量日志,Secondary NameNode 定期把增量日志合并进全量备份,删掉已经合并过的旧日志。这样每次重启需要回放的日志量就不会失控。
2. 工作机制深度拆解:checkpoint 到底是怎么做的
2.1 fsimage 和 edits 的分工逻辑
先把这个最基础的分工讲透,后面所有内容都建立在这上面。
fsimage是 HDFS 元数据在某个时间点的完整快照。它包含了文件系统的命名空间、文件与目录的映射关系、每个文件对应的 block 列表、副本数、权限等等。NameNode 在启动时会把这个文件加载进内存,之后所有的操作都在内存里进行。
edits是一份只追加(append-only)的日志文件,记录的是从fsimage生成之后发生的每一次元数据修改操作。为什么要单独搞一份日志?因为如果每次修改都立刻重写整个fsimage,文件系统一秒钟有成千上万个操作,磁盘根本扛不住。用追加日志的方式写磁盘非常高效,这也是很多系统都采用的方案,比如数据库里的 WAL(Write-Ahead Log),或者你熟悉的 Redis AOF,思路都是一样的。
问题正如前面说的,edits不合并就会越来越大。NameNode 启动时要先把fsimage载入内存,然后逐条重放edits,这个操作非常吃 CPU 和磁盘 IO。之前有个线上集群,因为半年多没做 checkpoint,edits文件膨胀到了二十多个 GB,一次滚动重启用了整整两个小时。
2.2 checkpoint 的完整流程
Secondary NameNode 的 checkpoint 不是简单把fsimage和edits拼在一起,它有一套完整的交互流程。我按实际执行顺序拆开来讲。
第一步,Secondary NameNode 定期向 NameNode 发起请求,让 NameNode 滚动当前的edits文件。也就是说,NameNode 会把正在写的edits重命名为edits_<起始事务ID>-<结束事务ID>,然后立刻新建一个空的edits文件供后续操作继续写入。这一步之后,新产生的元数据变更都写到新edits里了,旧edits的内容就固定了。
第二步,Secondary NameNode 通过 HTTP 接口,把 NameNode 上的fsimage和刚才滚动出来的旧edits下载到自己的本地目录。这里要注意,HDFS 的很多内部通信都走 RPC,但 checkpoint 的数据传输走的是 HTTP,所以配置里既有 RPC 地址也有 HTTP 地址。
第三步,Secondary NameNode 在内存中加载刚下载的fsimage,然后逐条重放edits,把增量操作合并到内存里的元数据上,最终生成一份新的、包含了最新状态的fsimage。这个动作看起来像重启了一次 NameNode,但不会影响线上 NameNode 的正常服务。
第四步,Secondary NameNode 把这株新的fsimage通过 HTTP 传回 NameNode,并原子性地替换掉 NameNode 上旧的fsimage。NameNode 收到新的fsimage后,会把已经合并过的旧edits清理掉。整个 checkpoint 到这里才算真正完成。
2.3 触发条件与默认参数
Secondary NameNode 不是每时每刻都在干活,它按条件触发 checkpoint。触发条件有两个维度:时间和事务量,哪个先到就执行哪个。
时间维度的参数是dfs.namenode.checkpoint.period,默认值 3600 秒,也就是每小时检查一次是否该做 checkpoint。事务量维度还有两个辅助参数,dfs.namenode.checkpoint.txns默认是 100 万,表示当 NameNode 收到的事务数量达到 100 万时触发一次 checkpoint;dfs.namenode.checkpoint.check.period默认 60 秒,表示 Secondary NameNode 每隔 60 秒检查一次当前事务数是否达到阈值。
这里注意一个容易看花眼的区分:dfs.namenode.checkpoint.period是“周期性检查”,但重点在“多久看一次”,真正的触发还取决于事务数;而dfs.namenode.checkpoint.txns决定的是“攒到多少事务就做一次”。生产环境里,如果集群写入量很大,一分钟就有几万条元数据操作,那 100 万事务很快就能攒够,checkpoint 的频次就会被事务数顶上来;如果是一个测试集群,每天没几个操作,那就只能靠每小时的时间周期来兜底。
另外还有一个参数dfs.namenode.checkpoint.size,控制的是edits文件大小的阈值,不同版本默认值可能不同,大约是 4MB 到 64MB 之间。它和事务数阈值是“或”的关系,edits文件增长太快也会触发。
3. 部署配置与关键参数:一台 SNN 从零上线
3.1 需要改的几个配置项
在实际环境里启用 Secondary NameNode,并不需要做太复杂的操作。这里以 Apache Hadoop 3.x 为例,把最关键的配置列出来。
第一步是确认hdfs-site.xml里的dfs.namenode.secondary.http-address,这个参数决定了 Secondary NameNode 的 Web UI 监听地址和端口,默认是0.0.0.0:9868。注意 Hadoop 2.x 时代默认端口是 50090,升级到 Hadoop 3.x 之后变成了 9868,很多老资料还在写 50090,排查问题的时候容易绕弯路。
第二步是设置 checkpoint 目录,也就是dfs.namenode.checkpoint.dir。这个目录用来存放 Secondary NameNode 从 NameNode 下载下来的fsimage和edits,以及最终生成的合并结果。建议配置多个目录,分别放在不同磁盘上,避免单块磁盘损坏导致 checkout 数据全部丢失。
第三步是编辑hdfs-site.xml中的dfs.namenode.checkpoint.edits.dir,这个参数用来指定下载的edits文件存放目录。如果没有单独配置,它会和dfs.namenode.checkpoint.dir共用。
配置完成后,在 Secondary NameNode 所在节点执行启动命令:
hdfs --daemon start secondarynamenode启动后可以通过jps命令看到SecondaryNameNode进程,同时访问http://<secondarynamenode主机>:9868/status查看它最近一次 checkpoint 的状态信息。
3.2 checkpoint 目录空间评估
规划 Secondary NameNode 磁盘空间时,很多人会低估用量。我见过一个集群,SNN 节点只给了 50GB 磁盘,结果跑了一个月磁盘就满了,checkpoint 一直失败,NameNode 的edits越积越多,最后把整个集群拖垮。
怎么评估?fsimage的大小和 NameNode 内存里的元数据规模相关,通常几百 MB 到几 GB 不等。edits则和写入量强相关,高峰期一天的增量可能就有几 GB。Secondary NameNode 在合并过程中,本地要同时保留旧的fsimage、下载的edits、合并中的临时文件,以及合并后的新fsimage,峰值空间消耗大约是原始文件总和的 2 到 3 倍。
所以建议给 SNN 节点预留的空间至少是 NameNode 元数据目录的 3 倍以上,更稳妥的做法是预留 5 倍,并且要配套监控磁盘使用率。这个节点平时看起来不起眼,一旦磁盘满了,影响的是整个 HDFS 的元数据安全。
3.3 单节点集群的完整配置示例
下面给一个可以直接参考的最小配置片段,省去不必要的参数,只保留必须项:
<configuration> <property> <name>dfs.namenode.secondary.http-address</name> <value>snn-host:9868</value> </property> <property> <name>dfs.namenode.checkpoint.dir</name> <value>/data/1/snn/checkpoint,/data/2/snn/checkpoint</value> </property> <property> <name>dfs.namenode.checkpoint.edits.dir</name> <value>/data/1/snn/checkpoint-edits,/data/2/snn/checkpoint-edits</value> </property> <property> <name>dfs.namenode.checkpoint.period</name> <value>3600</value> </property> <property> <name>dfs.namenode.checkpoint.txns</name> <value>1000000</value> </property> <property> <name>dfs.namenode.checkpoint.check.period</name> <value>60</value> </property> </configuration>完成之后,在配置了 SNN 的节点上启动服务,观察日志文件hadoop-hadoop-secondarynamenode-<hostname>.log,看到类似下面的输出,说明 checkpoint 已经正常执行:
Checkpointing transaction 12345678 Checkpoint complete. New Image Size: 123456789还需要注意一个细节:dfs.http.address或者dfs.namenode.http-address必须配置成 NameNode 节点的真实地址,因为 Secondary NameNode 要下载fsimage和edits,下载地址就是从这个参数里读出来的。如果配成了0.0.0.0或者 localhost,SNN 会连不上 NameNode,checkpoint 永远无法完成。
4. 高可用(HA)架构下 SNN 的真实角色
4.1 HA 架构的核心组件
讲完单机场景,接下来是很多生产环境真正在用的 HA 架构。HDFS 高可用方案的核心思路,是用两个 NameNode 节点组成一个对,一个 Active,一个 Standby。
Active NameNode 对外提供服务,处理所有客户端请求;Standby NameNode 实时同步 Active 的元数据状态,随时准备在 Active 故障时接替。为了实现这个“同步”,光靠两台机器互相复制是不够的,还引入了 JournalNode 集群,专门负责存储 edits 日志。Active NameNode 把每一条元数据变更都写到 JournalNode 上,Standby NameNode 从 JournalNode 上持续读取这些日志,并在自己的内存里重放,从而保持和 Active 状态一致。
Active 和 Standby 之间通过 ZKFC(ZooKeeper Failover Controller)做自动故障转移。两个 ZKFC 进程在 ZooKeeper 里抢锁,谁抢到锁,对应的 NameNode 就是 Active;Active 挂了之后,锁会被释放,另一个 ZKFC 抢到锁,把 Standby 提升为 Active。
4.2 为什么 HA 下不再需要 SNN
现在关键在于:Standby NameNode 的职责和 Secondary NameNode 是不是重合了?答案是高度重合,而且 Standby 比 SNN 做得更好。
在 HA 模式下,Standby NameNode 本身就在不断读取 JournalNode 上的 edits 日志并回放,它天然掌握着和 Active 几乎同步的元数据状态。同时,Standby 也会定期执行 checkpoint,把自己内存里的元数据写入一个新的fsimage,然后通过 HTTP 上传给 Active NameNode。这个“周期性合并”的活,Standby 自己就干了。
所以你在 HA 集群里再部署一个独立的 Secondary NameNode,不但多余,还会出问题。它很难像单机模式那样正常下载到一致的fsimage和edits,因为 HA 下 NameNode 的元数据合并逻辑已经变了,SNN 的 checkpooint 行为可能和 Standby 的 checkpoint 互相干扰,导致资源浪费或者出现难以排查的异常。
Apache 官方在 HA 文档里明确写了:在 HA 部署中,不应该再运行 Secondary NameNode。这是我自己的经验里最容易踩的坑,尤其是从旧版 CDH 集群迁移到 HA 模式的时候,原有的 SNN 进程没停掉,经常导致 NameNode 日志里出现各种异常堆栈。
4.3 SNN 在 HA 下的替代方案
既然不能再跑 SNN,那原来那台专门做 checkpoint 的机器能干什么?有两个方向。
第一个方向是把它转型为Checkpoint Node。这个角色的职责和单机模式下的 SNN 类似,也是定期创建 checkpoint,但它不和 NameNode 绑定在同一个高可用组里,不会参与故障转移,纯粹做一个周期合并工具。配置方式也很相似,启动命令是hdfs --daemon start checkpointnode。不过说实话,在已经有了 Standby NameNode 的 HA 集群里,Checkpoint Node 的边际价值并不高,除非你担心两台 NameNode 的fsimage都因为某种原因长时间不更新。
第二个方向是直接让这台机器退役,把资源释放给其他服务。HA 集群中,Standby NameNode 已经承担了元数据合并和快照更新的职责,再单独养一个 checkpoint 节点意义不大。
4.4 HA 模式下必须注意的隐患
在 HA 集群里,有两点还是得单独拎出来提醒一下。
第一,不要手动在 Standby NameNode 上执行hdfs dfsadmin -saveNamespace这类强制合并操作。因为 Standby 的fsimage是周期性上传给 Active 的,如果手动触发时机不对,可能生成一份并不完整的快照,反而影响后续恢复过程。正确做法是监控 Standby 是否按时完成了 checkpoint 操作,这可以从 NameNode 的 Web UI 上看到Image Status信息。
第二,两个 NameNode 的元数据目录和 checkpoint 目录要单独规划,不要让 Active 和 Standby 共用同一个存储目录。之前我看到有人为了方便,把两个 NameNode 放在同一台机器的同一块磁盘上,结果磁盘坏道直接让整个 HA 组全部瘫痪。HA 的“高可用”是建立在物理资源隔离基础之上的,所有副本策略都是同一个道理。
5. 实操记录:手动触发 checkpoint 与监控验证
5.1 手动触发 checkpoint 的方法
虽然 Secondary NameNode 会定期自动 checkpoint,但运维过程中经常需要手动触发,比如 NameNode 要重启升级,或者想赶在业务高峰前把元数据快照更新一下。
最常用的命令是在 Secondary NameNode 节点上执行:
hdfs dfsadmin -checkpoint <checkpoint目录>这个命令会让 Secondary NameNode 主动向 NameNode 发起一次 checkpoint 过程。你可以把它理解成一个“强制对账”的按钮。执行之后,去logs目录看hadoop-hadoop-secondarynamenode-<hostname>.log,会出现新的 checkpoint 记录。
还有一个辅助命令是滚动 edits,如果你只想让 NameNode 把当前 edits 滚动一下,而不想执行完整 checkpoint,可以使用:
hdfs dfsadmin -rollEdits这个操作在生产环境里也有用处,比如你想对某个历史时间段做排查,可以先滚动 edits,形成边界清晰的日志文件。
5.2 从 Web UI 和日志验证结果
判断 checkpoint 是否成功,最直观的方式是看 Secondary NameNode 的 Web 界面。访问http://<secondarynamenode主机>:9868/status,页面上会有Checkpoint Time、Image Size、Edits Size这些信息,还有一个醒目的进度条,显示当前是否正在执行 checkpoint。
命令行也有办法验证。在 NameNode 节点上执行:
hdfs dfsadmin -report输出里能看到 NameNode 的启动时间和当前元数据状态。更直接的方法是检查fsimage文件的时间戳:
hdfs dfsadmin -fetchImage /tmp/fsimage_download下载到的fsimage文件,用hdfs oiv工具可以查看其内容,确认里面包含的命令空间变更是否已经更新到最新事务:
hdfs oiv -i /tmp/fsimage_download -o /tmp/fsimage_dump.xml -p XML不过日常运维中,我一般习惯直接看 NameNode 日志。每次 checkpoint 完成,NameNode 日志里会出现类似下面的记录:
Image file /export/data/dfs/name/current/fsimage_0000000000123456789 of size 123456789 bytes saved in 2 seconds.看到这样的日志,基本可以确认合并过程是正常的。
5.3 故障恢复实战:用 SNN 的 checkpoint 目录恢复元数据
这里分享一个相对冷门但很有价值的操作:当 NameNode 的元数据全部丢失时,如何用 Secondary NameNode 的 checkpoint 目录做一次应急恢复。
注意,这个操作通常是在没有 HA 的单 NameNode 集群里用的。比如 NameNode 所在的机器磁盘损坏,fsimage和edits全部没了,但 SNN 节点上还保留着最近一次 checkpoint 下载的fsimage和edits副本。
恢复思路大致如下:
第一步,停掉 NameNode 进程,避免它继续写数据。
第二步,把 SNN 节点 checkpoint 目录下的fsimage和edits文件拷贝到 NameNode 的元数据目录。dfs.namenode.name.dir指定的就是 NameNode 元数据目录,可以配置多个,恢复时至少拷贝到一个目录里。
第三步,修改拷贝过来的文件名,让它们符合 NameNode 启动时的命名规则。通常需要把fsimage_<txid>改成fsimage,或者只保留包含最新完成 checkpoint 的那一份fsimage和对应的edits文件。
第四步,启动 NameNode,让它加载恢复的fsimage和edits。如果edits文件不完整,可能还需要用hdfs namenode -recover进入恢复模式,人工选择要保留的日志段。
这个方案能不能恢复成功,取决于 SNN 节点上的数据是否落后于 NameNode 发生故障的时间点。SNN 是一个“周期帮手”,它保存的fsimage是最近一次 checkpoint 的结果,所以一定不是百分之百最新的。做恢复前要有心理准备,有一部分最近的操作可能会丢失。这也是为什么生产环境首选 HA,而不是靠 SNN 来兜底。
6. 常见问题与排查技巧实录
6.1 问题速查表
我把实际运维中遇到过的、以及周围同事踩过的高频问题整理成了一张表,方便你直接对照排查。
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| SNN 进程启动后立即退出 | dfs.namenode.secondary.http-address端口被占用 | 更换端口,或用netstat -tlnp检查端口占用 |
| Web UI 无法访问 | 防火墙未放行 9868 端口 | 检查防火墙策略,确保 SNN 节点的 HTTP 端口可访问 |
| checkpoint 长期不执行 | dfs.namenode.checkpoint.txns事务数和period时间周期都没触发 | 查看配置值,手动执行hdfs dfsadmin -checkpoint验证 |
| checkpoint 失败,日志报连接超时 | NameNode 的 HTTP 地址配置错误 | 检查dfs.namenode.http-address是否为 NameNode 可访问的真实地址 |
| 合并后的 fsimage 时间戳不更新 | SNN 所在节点磁盘空间满了 | 清理磁盘,扩容 checkpoint 目录 |
| 重启 NameNode 非常慢 | 长时间未执行 checkpoint,edits 文件太大 | 先手动执行 checkpoint,再重启 NameNode |
| HA 集群里运行 SNN 导致异常 | 配置了不兼容的 SNN 或 Checkpoint Node | 停掉 SNN,确认 HA 模式下 Standby 正常执行 checkpoint |
这张表看着简单,但每一条背后都有真实的“事故”案例。比如“磁盘满了导致 checkpoint 一直失败”这个问题,因为 NameNode 的日志不一定报错,只是 silently 地跳过,所以特别难发现。建议把 SNN 节点的磁盘监控单独拎出来,磁盘使用率超过 80% 就要告警。
6.2 三个容易被忽视的坑
第一个坑,名字叫“手动删除 edits 文件”。有些同学看到edits文件越积越大,就手动去删除旧文件,想给磁盘腾空间。这个操作极其危险,NameNode 并不认识一个“不连续的 edits 序列”,你删掉的文件里很可能包含尚未合并到fsimage的事务,删掉之后元数据就永久丢了。正确的做法是执行 checkpoint,让系统自己清理已经合并过的旧日志,千万不能手工删。
第二个坑,是只盯着 NameNode 进程,不看 SNN 的日志。很多集群里 SNN 跑在单独的机器上,被遗忘的概率非常高。我处理过一例,SNN 进程已经 segfault 一个月了,但 NameNode 一直没有重启,所以表面上一切正常。等某次机房断电,NameNode 重启,edits 文件庞大到根本起不来,才发现 SNN 早就失效了。Secondary NameNode 这种组件,平时不显山露水,关键时刻缺了它,恢复成本高很多。
第三个坑,是忽略版本兼容性。Secondary NameNode 的 Hadoop 版本最好和 NameNode 保持一致,不然它下载的fsimage格式可能不兼容,合并时直接报错。不同大版本之间的 RPC 协议和镜像格式都可能不同,比如 Hadoop 2.x 和 3.x 混着用,SNN 很容易出现Incompatible namespaceID这类诡异报错,排查起来相当耗时。
6.3 日常巡检建议
最后给一条实践经验:把 Secondary NameNode 的检查流程固化到日常巡检脚本里。不需要多复杂,定时执行下面两步就够了。
第一步,检查 SNN 进程是否存活,可以用jps或者ps -ef | grep SecondaryNameNode。
第二步,检查最近一次 checkpoint 的时间,通过访问http://<secondarynamenode>:9868/status获取,或者解析日志里的Checkpoint complete关键字。如果发现最近 24 小时都没有新的 checkpoint 成功记录,就自动告警。
这个习惯看起来简单,但能避免绝大多数与元数据膨胀相关的“慢性死亡”问题。等到 NameNode 重启才发现 edits 已经大到无法回放,那时候业务已经停了,处理起来就相当被动。
结尾
我自己的感受是,Secondary NameNode 是一个特别容易被误解、又特别容易被遗忘的组件。它不像 DataNode 那样每天处理大量数据流,也不像 HA 里的 Standby NameNode 那样能随时顶上,但它用最朴素的方式守护着 NameNode 的元数据安全。我在实际操作中,现在检查任何集群的第一件事,就是看一眼最近一次 checkpoint 的时间戳。这个习惯救过我很多次,也让我少熬了很多次夜。如果你正在维护 HDFS 集群,不管用的是独立 SNN 还是 HA 方案,都建议把“定期合并元数据”这件事放在心里,它比表面上的集群状态更有价值。