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.parquetNo 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 默认)
| 端口 | 服务模块 | 作用 |
| 9864 | DataNode HTTP Server(旧版 UI / JMX) | 提供 Web UI、指标接口 |
| 9866 | DataNode HTTP Data Server(Jetty) | 提供 block 数据读写服务 |
| 9867 | DataNode IPC Server(基于 Protobuf 的 RPC) | 与 NameNode 心跳、块上报、命令交互 |
| 39552 | BlockReceiver / 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 端口持续稳定监听,无服务中断记录。
1024

被折叠的 条评论
为什么被折叠?



