HDFS BlockMissingException 问题排查

1. 问题描述

在 DataX 数据同步(通过 DataX 调用 Spark 引擎)时,任务失败并抛出以下异常(某日志节选):

org.apache.hadoop.hdfs.BlockMissingException:

Could not obtain block: BP-xxxxxx:blk_xxxxxxxxx_xxxxxx 
file=/user/hive/.../part-xxxxx-xxxx.snappy.parquet 

No live nodes contain current block

Block locations: DatanodeInfoWithStorage[<ip1>: 9866,<ip2>: 9866, ...]

Dead nodes: DatanodeInfoWithStorage[<ip1>: 9866,<ip2>: 9866, ...]

其中 DatanodeInfoWithStorage 中的 <IP> 因任务而异,可能涉及集群中多个节点。

初步排查知道,

  • 所有相关数据文件的 HDFS 副本数均为 1(即每个 block 仅存于单个节点)。
  • 任务失败多集中于高并发场景(如 DataX 同时启动多个子任务),且均涉及复杂的 SQL 查询,并通过单个 DataX 任务一次性执行完整的计算逻辑。

2. 日志分析

BlockMissingException 并不表示数据块在磁盘上被删除或物理损坏,而是指 HDFS 客户端无法从任何可用副本节点成功读取该 block。其发生过程如下:

(1)HDFS 客户端读取 block 的标准流程

当 Spark(通过 DataX)尝试读取一个 HDFS block 时,会执行以下步骤:

第一步,向 NameNode 查询 block 位置。

      NameNode 返回 LocatedBlock 对象,例如:[DatanodeInfoWithStorage[ip1:9866], ip2:9866, ...],表明该 block 的元数据记录其存储在 ip1:9866 和 ip2:9866。

第二步,尝试连接该 DataNode 的 9866 端口读取数据。

  • 若连接成功 → 正常读取 block;
  • 若连接失败(如 Connection refused、timeout)→ 客户端将该节点标记为 “dead”。

第三步,若所有副本节点均不可达 → 抛出 BlockMissingException

DataX 任务日志中的关键信息印证了上述过程:

Block locations: DatanodeInfoWithStorage[<ip>:9866, ..., DISK] Dead nodes: DatanodeInfoWithStorage[<ip>:9866, ..., DISK]

结论1:NameNode 正常返回了 block 位置(元数据无异常)。

结论2:客户端尝试连接 10.167.137.156:9866 后失败,故将其归为 “dead nodes”。

(2)Hadoop DataNode 关键端口说明(Hadoop 3.x 默认)

端口服务模块作用
9864DataNode HTTP Server(旧版 UI / JMX)提供 Web UI、指标接口
9866DataNode HTTP Data Server(Jetty)提供 block 数据读写服务
9867DataNode IPC Server(基于 Protobuf 的 RPC)与 NameNode 心跳、块上报、命令交互
39552BlockReceiver / Pipeline 临时端口用于 DataNode 之间复制 block 副本(写入时使用)

(3)服务端排查确认:9866 端口实际未监听

仅凭客户端日志无法确定连接失败的具体原因(可能是网络、防火墙或服务未启动)。
因此,我们登录华为云堡垒机,在目标节点 <ip> 上进行了服务端验证:

第一步,通过 jps 获取 DataNode 进程 PID(例如 2624)。

jps

第二步,​​检查该 PID 监听的端口。

netstat -tunlp | grep 2624

正常情况下,输出包含 4 行,对应端口 9864、9866、9867 和一个临时端口(如 39552);但是实际却发生了异常情况,即仅输出 3 行,缺少 9866。

第三步,确认进程仍在运行。

ps -ef | grep DataNode

    结论:DataNode 主进程仍在运行,但其 9866 数据服务端口未监听。

    (3)副本数为 1 导致无容错能力

    由于该 block 的 HDFS 副本数为 1,集群中不存在其他副本节点可供尝试读取。
    因此,当唯一副本所在节点的 9866 服务失效时,客户端无法获取数据,最终抛出 BlockMissingException

    💡 最终结论
    元数据正确 + DataNode 的 9866 端口未监听 + 单副本架构 = 服务不可用,而非物理丢失

    3. 原因分析

    在 Hadoop 3.x 中,DataNode 的 9866 端口由内嵌的 Jetty HTTP Server 提供,专门用于响应客户端(如 Spark、DataX)的 block 读取请求。该服务具有以下特性:

    • 使用固定大小的线程池处理并发请求(默认最大线程数约 1000);
    • 当单个 DataNode 在短时间内接收大量并发读请求(例如一个复杂 SQL 涉及该节点上 TB 级数据),Jetty 线程池会迅速耗尽;
    • 若请求队列堆积或处理过程中发生未捕获异常(如 I/O 错误、内存压力、压缩库异常等),Jetty 默认行为是关闭其连接器(即停止监听 9866 端口);
    • 但 DataNode 主进程(负责 9867 心跳服务)仍继续运行,因此 NameNode 仍认为该节点“存活”,而客户端却无法读取数据。

    结合 DataX 原始任务特征:

    • 单个复杂 SQL 涉及大量 HDFS block,且这些 block 恰好集中存储在同一 DataNode 上;
    • 由于 Spark 的数据本地性优化,相关计算任务被调度到该 DataNode 所在节点执行,所有读请求均指向同一 DataNode 的 9866 端口;
    • 同时,DataX 任务配置了高并发 channel,在短时间内向该 DataNode 发起大量并发 block 读取请求。

    因此,导致:

    • 该 DataNode 的 Jetty HTTP Server(9866 端口)因瞬时请求量超出处理能力而异常退出;
    • 9866 端口停止监听 → 客户端连接失败 → 节点被标记为 “Dead node”;
    • 由于涉及的 block 副本数为 1,集群中无其他副本可供读取 → 客户端无法恢复 → 最终抛出 BlockMissingException

    4. 解决方案

    (1)拆分复杂计算逻辑,中间结果显式落地

    • 原状:单个 SQL 文件包含完整复杂逻辑(多层 JOIN/聚合),一次性执行并写入目标表。
    • 调整:将计算拆分为两个阶段:

      • 阶段一:执行前半部分计算,结果插入临时表。
      • 阶段二:从临时表读取数据,执行后半部分计算,最终插入目标表。

    (2)合理控制 DataX 并发度(非强制串行)

    • 原状:DataX 配置高并发 channel,加剧单节点读压力。
    • 调整:降低并发数为 1。

    5. 验证结果

    实施上述优化后,对多个业务线的数仓每日定时 DataX 任务进行持续观察:

    • 未再出现 BlockMissingException 异常。
    • DataNode 的 9866 端口持续稳定监听,无服务中断记录。
    评论
    成就一亿技术人!
    拼手气红包6.0元
    还能输入1000个字符
     
    红包 添加红包
    表情包 插入表情
     条评论被折叠 查看
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

    当前余额3.43前往充值 >
    需支付:10.00
    成就一亿技术人!
    领取后你会自动成为博主和红包主的粉丝 规则
    hope_wisdom
    发出的红包
    实付
    使用余额支付
    点击重新获取
    扫码支付
    钱包余额 0

    抵扣说明:

    1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
    2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

    余额充值