HDFS 写文件关闭时副本数不足异常分析


1. 问题现象

Spark 跑批任务偶尔失败,异常信息如下:

Exception in thread "main" java.io.IOException:
Unable to close file because the last block
BP-2064594970-172.16.76.254-1582895310342:blk_1313413793_239728256
does not have enough number of replicas.

从报错信息看,失败点不是 Spark SQL 语法,也不是 Executor 计算逻辑,而是写 HDFS 文件时,在关闭文件阶段发现最后一个 block 的副本数不够。

这个问题通常会出现在下面几类场景中:

  1. Spark 写 HDFS、Hive、Parquet、ORC 时任务失败。
  2. 写入过程中某些 DataNode 短暂不可用。
  3. HDFS 集群磁盘空间不足,或者部分节点磁盘满。
  4. 文件写完 close 时,最后一个 block 没有达到最小副本要求。

2. 先理解 HDFS 写文件过程

HDFS 写文件时,客户端不是直接把数据写到 NameNode。NameNode 负责元数据和 block 分配,真实数据会写到 DataNode。

一个文件会被切分成多个 block,每个 block 按照副本数写入多个 DataNode。默认情况下,HDFS 副本数通常是 3。

写入流程可以简单理解为:

客户端请求创建文件
NameNode 分配 block 和 DataNode
客户端向 DataNode pipeline 写入数据
DataNode 之间按 pipeline 复制副本
文件写完后客户端 close 文件
NameNode 检查 block 状态

如果最后一个 block 在 close 时没有达到足够副本,客户端就可能看到:

Unable to close file because the last block does not have enough number of replicas

3. 这个异常说明什么

这个异常不是简单的“Spark 失败”,而是 HDFS 对文件完整性检查没有通过。

重点是这几个词:

close file
last block
not have enough number of replicas

含义是:

关键词 含义
close file 文件已经写到最后阶段,准备关闭
last block 出问题的是文件最后一个 block
replicas HDFS block 副本
not enough 当前可用副本数没有达到要求

所以排查方向应该先放到 HDFS 集群健康状态,而不是先改 Spark 参数。

4. 常见原因

4.1 DataNode 不稳定

写入过程中,如果某个 DataNode 宕机、网络抖动、磁盘故障,pipeline 中的副本写入就可能失败。

可以先看 DataNode 状态:

hdfs dfsadmin -report

重点看:

Live datanodes
Dead datanodes
Decommissioning datanodes

如果任务失败时刚好有 DataNode 掉线,基本就能解释这个异常。

4.2 HDFS 空间不足

如果集群剩余空间不足,或者部分 DataNode 磁盘打满,HDFS 可能无法为最后一个 block 补齐副本。

查看 HDFS 空间:

hdfs dfsadmin -report

也可以查看具体目录:

hdfs dfs -du -h /path/to/output

如果 DataNode 之间空间不均衡,还可以考虑执行 balancer:

hdfs balancer

4.3 副本数设置过高

如果集群可用 DataNode 数量不足,但文件副本数要求较高,也会导致副本无法满足。

查看目录或文件副本数:

hdfs dfs -stat %r /path/to/file

如果只是测试环境,可以临时降低副本数:

hdfs dfs -setrep -w 2 /path/to/file

生产环境不建议盲目降低副本数。副本数降低之后,容错能力也会下降。

4.4 存在损坏 block 或丢失 block

草稿里提到“hdfs 丢 block 了吧”“副本不够了”,这个判断方向是对的。

可以通过 fsck 检查目标路径:

hdfs fsck /path/to/output -files -blocks -locations

如果想看缺失 block:

hdfs fsck / -list-corruptfileblocks

如果输出中出现 MISSINGCORRUPTUnder replicated,说明 HDFS 本身已经存在 block 健康问题。

5. 解决办法

5.1 先确认集群是否健康

优先执行:

hdfs dfsadmin -report
hdfs fsck /path/to/output -files -blocks -locations

确认:

  1. DataNode 是否全部存活。
  2. 磁盘空间是否充足。
  3. 是否存在 under replicated block。
  4. 是否存在 corrupt block。

5.2 等待 HDFS 自动补副本

如果只是短暂副本不足,并且 DataNode 已恢复,HDFS 通常会自动补副本。

可以观察一段时间后再次执行:

hdfs fsck /path/to/output -files -blocks -locations

如果 under replicated 数量下降,说明集群正在自动恢复。

5.3 删除失败输出目录后重跑任务

Spark 写 HDFS 失败后,目标目录可能只写了一部分数据。不要直接在这个目录上继续跑。

建议先删除失败输出:

hdfs dfs -rm -r /path/to/output_tmp

然后重新提交任务。

如果是 Hive 分区写入,建议先清理对应分区目录,再重跑该分区。

5.4 修复 DataNode 或磁盘问题

如果 dfsadmin -report 显示某些 DataNode 已经不可用,需要先处理节点:

systemctl status hadoop-hdfs-datanode

查看 DataNode 日志:

tail -200 $HADOOP_LOG_DIR/hadoop-*-datanode-*.log

重点关注:

DiskErrorException
Volume failed
DataXceiver
PacketResponder

如果是磁盘故障,要先处理磁盘或下线故障 DataNode,再让 HDFS 重新复制副本。

6. Spark 侧可以怎么处理

Spark 侧能做的是减少这类问题的放大影响,而不是修复 HDFS。

可以检查输出路径是否使用临时目录:

/path/to/table/.spark-staging-xxx

任务失败后要清理临时目录,避免下次任务读到半成品数据。

如果输出数据量很大,也可以适当控制写文件数量:

df.repartition(200)
  .write
  .mode("overwrite")
  .parquet("/path/to/output")

文件数太多会带来更多 block 和 NameNode 元数据压力,但文件数太少又可能导致单文件过大。这个参数需要结合数据量和集群规模调整。

7. 总结

这个异常的根因不是 Spark 计算错误,而是 HDFS 写文件 close 阶段发现最后一个 block 副本数不足:

Unable to close file because the last block does not have enough number of replicas

排查时优先看 HDFS:

hdfs dfsadmin -report
hdfs fsck /path/to/output -files -blocks -locations
hdfs fsck / -list-corruptfileblocks

如果是偶发问题,通常和 DataNode 抖动、磁盘空间不足、block 副本恢复不及时有关。生产上不要只重跑 Spark 任务,应该把 HDFS 副本不足的原因查清楚,否则后续跑批还会继续偶发失败。

8. 参考资料


文章作者: hnbian
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 hnbian !
评论
  目录