Apache Hadoop — 在一个 Pod 里搭起并运维 HDFS 与 YARN
NameNode 把记忆分写在 fsimage 和 edits 两处
一句话总结
NameNode 把整个命名空间放在内存里,而在磁盘上,分开写入某个时间点的照片(fsimage)与此后的变更日志(edits)。把两者合并的工作就是检查点;而块位于哪个 DataNode,则哪里都不记录,所以每次启动都要在安全模式中等待报告。
为什么一个文件不够
如果每创建一个文件,都把整个命名空间重新写一遍到磁盘上,会怎样?在有一千万个文件的集群里,一次 mkdir 就会变成好几 GB 的写入。反过来,如果只不断追加变更,从来不保存完整的样子,重启时就得把几个月的日志从头重放一遍。
HDFS 把两种方式结合了起来。HDFS 设计文档说明,NameNode 把元数据的所有变更记入名为 EditLog 的事务日志,而包含块与文件的对应关系和文件属性在内的整个命名空间,则存放在名为 FsImage 的文件中。同一份文档还简短地给出了原因——读取 FsImage 是高效的,但把变更逐条反映到 FsImage 上并不高效。所以变更只追加到日志末尾(顺序写入,速度很快),照片则偶尔重新拍一次。
不了解这种结构,就会遇到两种事故。一种是重启要花一个小时。正如用户指南警告的,edits 越大,下一次重启就越慢。另一种是丢失 NameNode 目录。这个目录就是 HDFS 的全部——即使 DataNode 上的块文件完好无损,能知道它是哪个文件的第几片的地方,也只有这里。
工作原理
打开 NameNode 存储目录中的 current/,大致是这样的。
current/
VERSION
seen_txid
fsimage_0000000000000000000
fsimage_0000000000000000000.md5
edits_0000000000000000001-0000000000000000042
edits_inprogress_0000000000000000043
文件名中的数字是事务编号(txid)。fsimage_N 是反映到第 N 号事务为止的照片,edits_A-B 是从 A 到 B 的已关闭日志分段,edits_inprogress_C 是当前正在写入的分段。启动时,NameNode 把最近的 fsimage 载入内存,再按顺序重新应用该编号之后的 edits。这样,停止之前那一刻的命名空间就在内存中复原了。
检查点就是提前把这种合并做好。根据设计文档,检查点在设定的时间间隔(dfs.namenode.checkpoint.period)或累积的事务数(dfs.namenode.checkpoint.txns)中先到达的一方触发。hdfs-default.xml 中的默认值是 3600 秒和 1,000,000 个事务。这项合并工作不是 NameNode 自己做,而是通常由 Secondary NameNode(HA 架构中是 Standby NameNode)来做。因为名字的缘故,它常被误认为备用 NameNode,但 Secondary 即使出了故障,也不会代替它提供服务。它只是重新拍摄照片并送回来的助手。
保留的照片数量也是有规定的。dfs.namenode.num.checkpoints.retained 的默认值是 2,同时会保留从最旧的照片起复原到当前所需的 edits。这样,即使一张照片损坏,也还留有退回上一张的路。
这里必须强调一点。fsimage 中没有块位于哪个 DataNode 的信息。其中写着文件由哪些块 ID 组成,但这些块的副本位于哪台服务器的哪块磁盘上,则是每次由 DataNode 启动时发送的块报告(Blockreport)重新填充的。位置会随着磁盘损坏、服务器更换不断变化,写下来反而容易变成错误的信息。
安全模式在等什么
因此,刚启动的 NameNode 知道所有的名字,却不知道块在哪里。如果在这种状态下判断副本不足并开始复制,就会把尚未报告的 DataNode 上完好持有的块也多余地复制一遍。设计文档所说的 Safemode,就是防止这种误判的等待状态。命令参考总结道:处于安全模式的 NameNode 不接受命名空间的变更(只读),也不复制或删除块。
退出的条件由配置决定。dfs.namenode.safemode.threshold-pct 的默认值是 0.999f。当 99.9% 的块已按最小副本数被报告时,条件就满足了,再额外等待 dfs.namenode.safemode.extension 的默认值 30000ms(30 秒)之后退出(只有一个节点的实验镜像把这份余量设为 0)。管理员也可以用 hdfs dfsadmin -safemode enter 有意进入。
有意进入的典型原因是 -saveNamespace。根据命令参考,这条命令会把当前命名空间以新的 fsimage 写入存储目录,并重新开始 edits,而且需要安全模式。因为在拍照期间命名空间如果发生变化,照片到底是哪个 txid 时的样子,就变得模糊了。
离线读取的方法
fsimage 和 edits 是二进制文件,不能用 cat 读取。为此有两个离线工具。离线镜像查看器(oiv)会把 fsimage 转换成人可读的格式,而且不要求集群处于运行状态。默认的处理器是启动只读 WebHDFS 的 Web,-p XML 输出包含全部信息、体积最大的结果,-p Delimited 则输出把路径、副本数、块大小、块数、文件大小、配额、权限逐行用分隔符排开的结果。想用脚本统计和筛选,Delimited 最方便。
离线编辑日志查看器(oev)用来读取 edits 分段。默认处理器是 xml,所以每个 RECORD 都能看到 OPCODE(例如 OP_MKDIR、OP_ADD、OP_CLOSE、OP_DELETE)和 TXID,而 -p stats 只按操作码统计个数。想审计谁在什么时候删除了什么,或者想弄清命名空间为什么突然变大时,就用这个工具。
在现场相遇的样子
第一,默认存储位置在 /tmp 之下。dfs.namenode.name.dir 的默认值是 file://${hadoop.tmp.dir}/dfs/name,而 hadoop.tmp.dir 是 /tmp/hadoop-${user.name}。不做任何配置就启动的 NameNode,一旦服务器重启、/tmp 被清空,整个命名空间就会全部丢失。生产环境必须修改这个值,而用逗号给出多个目录时,同样的内容会被复制到所有目录中。
第二,检查点停了,一段时间内也没有任何症状。如果 Secondary 已经挂掉,edits 只会不断变长,几周后重启的那天,NameNode 会花好几个小时重新读取日志。这就是要把最近一次检查点的时间纳入监控项的原因。
第三,退不出安全模式,说明块报告不够。这是 DataNode 还没有启动完,或者丢了一块磁盘,达不到 99.9% 的情况。在用 -safemode leave 强行退出之前,先看缺了什么。
第四,总有一天要亲手修改 edits。如果 NameNode 因为日志末尾损坏而无法启动,就用 oev 把它解开成 XML 来看,这个工具也提供了再转换回二进制的途径。虽然希望没有需要这样做的一天,但要了解这个工具。
实际工作中真正重要的事
- fsimage 是照片,edits 是此后的日志。复原就是在照片之上重新应用日志。
- 块位置不在磁盘上。所以启动之后要在安全模式中等待块报告。
- 检查点在时间和事务数中先到达的一方触发。默认是 3600 秒和 100 万条。
- saveNamespace 只能在安全模式下执行。因为拍照期间命名空间必须保持静止。
- name.dir 的默认值在 /tmp 之下。生产环境中如果不改,一次重启就会全部丢失。
- 二进制文件要用 oiv 和 oev 来读。不启动集群也能读取。
下一项实验要做什么
先确认 Pod 中运行的 NameNode 现在是否处于安全模式,再亲自进入安全模式,看到创建目录被拒绝。在这个状态下用 saveNamespace 生成新的 fsimage,读出文件名中的 txid,然后退出安全模式并创建一个目录。接着用 oiv 把这个 fsimage 解析成 XML 和分隔符格式,读取命名空间。最后用 rollEdits 关闭当前正在写入的 edits 分段,再用 oev 解析包含刚才那次创建目录的已关闭分段,数出其中有多少条 OP_MKDIR 记录。