
在 HDFS 客户端写入过程中,仅通过 AddBlockRequestProto.ExcludeNodes 无法跳过已失败的 DataNode;必须先显式废弃(abandon)原 Block,再发起带排除列表的新请求,否则 NameNode 仍可能复用旧分配策略。
在 hdfs 客户端写入过程中,仅通过 `addblockrequestproto.excludenodes` 无法跳过已失败的 datanode;必须先显式废弃(abandon)原 block,再发起带排除列表的新请求,否则 namenode 仍可能复用旧分配策略。
HDFS 的块分配机制并非完全“无状态”——当客户端发起 addBlock 请求时,NameNode 并非总从零开始进行副本放置(block placement),而是会结合当前租约(lease)、待写入块的生命周期状态及已有分配上下文进行决策。若客户端未主动放弃(abandon)此前已分配但因 DataNode 故障而失效的 Block,NameNode 可能仍将该 Block 视为“仍在租约中”,并尝试沿用原有位置信息(包括已失败节点),导致 ExcludeNodes 被忽略。
✅ 正确做法是严格遵循两阶段容错流程:
-
显式废弃原 Block:调用
abandonBlock(blockId, fileId, src)RPC,通知 NameNode 该 Block 已不可用,解除其与租约和目标 DataNode 的绑定; -
发起新 AddBlock 请求:在
AddBlockRequestProto中设置ExcludeNodes字段,传入已确认故障的 DataNode 列表(格式需为host:port或host,与 NameNode 配置的dfs.namenode.datanode.registration.ip-hostname-check一致)。
示例 Go 代码片段(基于 Hadoop Protobuf API):
// 1. Abandon the failed block
abandonReq := &hdfs.AbandonBlockRequestProto{
Block: &hdfs.BlockProto{
BlockId: proto.Int64(oldBlockID),
NumBytes: proto.Int64(expectedSize),
GenerationStamp: proto.Int64(genStamp),
},
FileName: proto.String(bw.src),
FileId: proto.Int64(fileID),
}
_, err := client.AbandonBlock(context.Background(), abandonReq)
if err != nil {
log.Printf("Failed to abandon block %d: %v", oldBlockID, err)
return err
}
// 2. Request new block with exclusion
req := &hdfs.AddBlockRequestProto{
Src: proto.String(bw.src),
ClientName: proto.String(bw.clientName),
ExcludeNodes: failedDatanodes, // e.g., []string{"dn1.example.com:9866", "dn2.example.com:9866"}
}
resp, err := client.AddBlock(context.Background(), req)
if err != nil {
return err
}
⚠️ 注意事项:
-
ExcludeNodes中的地址必须与 NameNode 记录的 DataNode 注册地址完全匹配(建议统一使用host:port格式,并确保 DNS/hosts 解析一致); -
abandonBlock是幂等操作,但必须在新addBlock前完成,否则 NameNode 可能拒绝新请求或返回旧分配; - 若启用
dfs.client.block.write.replace-datanode-on-failure.policy(如NEVER/ALWAYS),需注意该配置仅影响自动重试逻辑,不替代手动容错流程; - 日志验证:检查 NameNode
namenode.log中是否出现"Excluding nodes"和"Placing block"相关条目,确认排除生效。
总结:HDFS 的写入容错依赖显式的状态管理。ExcludeNodes 不是“覆盖式调度指令”,而是“约束式提示”——它仅在 NameNode 执行全新块放置(placement)时生效。因此,废弃旧块是启用排除逻辑的前提条件。忽略此步骤将导致容错机制形同虚设。











